UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

812 lines (811 loc) • 42.2 kB
import { l as normalizeOptionalString } from "./string-coerce-1VuiLaaR.mjs"; import { j as resolveIntegerOption } from "./number-coercion-CkhMiRw8.mjs"; import { c as isRecord, i as asOptionalObjectRecord } from "./record-coerce-TIsquoVR.mjs"; import { n as sliceUtf16Safe, r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.mjs"; import { a as isSensitiveFieldKey, h as redactSensitiveFieldValueWithConfig, o as prepareModelVisibleToolTextBlock, x as redactToolPayloadTextWithConfig } from "./redact-BV3wTtTv.mjs"; import { o as extractToolCallsFromAssistant, s as extractToolResultId, u as makeMissingToolResult } from "./tool-result-pairing--BK1RaBE.mjs"; import { l as resolveTerminalAssistantTranscriptRunId, n as attachSessionTranscriptRunId } from "./transcript-events-DSEdnn1V.mjs"; import { _ as readAgentAssistantSource, n as bindAgentAssistantSource } from "./agent-events-jWdQ_ESE.mjs"; import "./messages-1Ao6vvqg.mjs"; import { i as jsonUtf8BytesOrInfinity, n as firstEnumerableOwnKeys, t as boundedJsonUtf8Bytes } from "./json-utf8-bytes-C0-PtdPX.mjs"; import { n as getGlobalHookRunner } from "./hook-runner-global-Cn3xHNvt.mjs"; import { i as resolveLiveToolResultMaxChars, t as DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS } from "./tool-result-limits-DNBSu_jg.mjs"; import { t as resolveExecApprovalsFromFileInternal } from "./exec-approvals-resolver-C1cTECqF.mjs"; import { r as loadExecApprovals } from "./exec-approvals-store-B-TRUVtg.mjs"; import "./exec-approvals-BkzkbwrB.mjs"; import { E as withCodeModeSourceAppend, S as copyCodeModeSourceAppendOptions, h as redactTranscriptMessage, w as prepareCodeModeSourceAppend, x as copyCodeModeSourceAppend, y as resolveTranscriptLoggingConfig } from "./session-accessor.sqlite-transcript-store-D63Niwm2.mjs"; import { s as restorePreparedUserTurnOperationalMetaForRuntime } from "./user-turn-transcript.metadata-DHDwAwgF.mjs"; import { o as applyInputProvenanceToUserMessage } from "./input-provenance-B5YZS2Do.mjs"; import { c as isTranscriptOnlyOpenClawAssistantModel } from "./transcript-only-openclaw-assistant-CzjHCvH2.mjs"; import { y as prepareSessionManagerSync } from "./session-accessor.sqlite-transcript-write-ClP59_lJ.mjs"; import { n as publishTranscriptUpdate } from "./session-accessor.sqlite-events-x56_8OQX.mjs"; import "./session-accessor-C3ljJ974.mjs"; import { t as acknowledgeInternalToolResult } from "./internal-hooks-CSjTREUl.mjs"; import { n as rewriteToolResultIds } from "./tool-call-id-CBojAlOj.mjs"; import { n as runAgentHarnessBeforeMessageWriteHook } from "./hook-helpers-DQD6Kn21.mjs"; import "./user-turn-transcript-DuAfBkUt.mjs"; import { o as mergePreparedUserTurnMessageForRuntime } from "./user-turn-transcript.message--uwt9Vax.mjs"; import { t as resolveProcessToolScopeKey } from "./bash-process-scope-2cvMG8cQ.mjs"; import { n as sanitizeForPromptLiteral } from "./sanitize-for-prompt-bzCyHFCH.mjs"; import { n as sanitizeToolCallInputs } from "./session-transcript-repair-BkR36ZME.mjs"; import { b as setSessionToolTextPreparer } from "./settings-manager-2KjnbEXR.mjs"; import { C as formatContextLimitTruncationNotice, d as truncateToolResultMessage } from "./tool-result-truncation-CgpKdRPP.mjs"; import { a as setRawSessionAppendMessageAsync, i as setRawSessionAppendMessage, n as getRawSessionAppendMessage, r as getRawSessionAppendMessageAsync } from "./transcript-rewrite-28vuJHxU.mjs"; import { t as listActiveProcessSessionReferences } from "./bash-process-references-QYajFIVi.mjs"; import { a as takeRuntimeUserTurnTranscriptRecorder, c as withRuntimeUserTurnTranscriptRecorder, i as takeRuntimeUserTurnTranscriptContext, n as attachRuntimeUserTurnTranscriptRecorder, o as withCurrentRuntimeUserTurnTranscriptRecorder } from "./user-turn-transcript-runtime-context-BtMCMUja.mjs"; import { c as withSessionCompactionPersistenceAsync, s as withSessionCompactionPersistence } from "./session-manager-BpvVpTVC.mjs"; import { n as withSessionManagerWrite } from "./session-manager-write-admission-DQneVl5b.mjs"; import { t as projectAgentHarnessTranscriptMessageForDisplay } from "./transcript-visibility-sRYXACld.mjs"; import path from "node:path"; //#region src/agents/embedded-agent-runner/run/midturn-precheck.ts /** Stable message used to identify synthetic mid-turn overflow errors in session cleanup. */ const MID_TURN_PRECHECK_ERROR_MESSAGE = "Context overflow: prompt too large for the model (mid-turn precheck)."; /** * Internal control-flow signal thrown after a tool result makes the next prompt * exceed budget. The attempt runner catches it and routes through the overflow * recovery path instead of treating it as an ordinary provider failure. */ var MidTurnPrecheckSignal = class extends Error { constructor(request) { super(MID_TURN_PRECHECK_ERROR_MESSAGE); this.name = "MidTurnPrecheckSignal"; this.request = request; } }; function isMidTurnPrecheckSignal(error) { return error instanceof MidTurnPrecheckSignal; } /** This local routing signal is not a provider response or durable conversation item. */ function isMidTurnPrecheckAssistantError(message) { return message?.role === "assistant" && message.stopReason === "error" && message.errorMessage === MID_TURN_PRECHECK_ERROR_MESSAGE; } //#endregion //#region src/agents/session-tool-result-guard.payload.ts function resolveMaxToolResultChars(opts) { return resolveIntegerOption(opts?.maxToolResultChars, DEFAULT_MAX_LIVE_TOOL_RESULT_CHARS, { min: 1 }); } const MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES = 8192; const MAX_PERSISTED_DETAIL_STRING_CHARS = 2e3; const MAX_PERSISTED_DETAIL_SESSION_COUNT = 10; const MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS = 200; const MAX_PERSISTED_DETAIL_REDACTION_LOOKAHEAD_CHARS = 1024; const MAX_PERSISTED_DETAIL_BOUNDARY_OVERLAP_CHARS = 512; const PERSISTED_DETAIL_REDACTION_BOUNDARY = "\0OPENCLAW_PERSISTED_DETAIL_BOUNDARY\0"; const PARTIAL_STRUCTURED_SECRET_VALUE_RE = /(?:["']?(?:api[-_]?key|apikey|token|secret|password|passwd|access[-_]?token|accesstoken|refresh[-_]?token|refreshtoken|auth[-_]?token|authtoken|client[-_]?secret|clientsecret|app[-_]?secret|appsecret|card[-_]?number|cardnumber|cvc|cvv)["']?\s*[:=]\s*["']?)(?!\*{3})(?=[^\s"',}\]]{8,})/i; const PARTIAL_PRIVATE_KEY_BLOCK_RE = /-----BEGIN [A-Z0-9 ]*(?:PRIVATE KEY|OPENSSH PRIVATE KEY|RSA PRIVATE KEY|EC PRIVATE KEY|DSA PRIVATE KEY)-----/i; function originalDetailsSizeFields(size) { return size.complete ? { originalDetailsBytes: size.bytes } : { originalDetailsBytesAtLeast: size.bytes }; } function redactPersistedDetailString(value, maxChars = MAX_PERSISTED_DETAIL_STRING_CHARS, redactionConfig) { if (value.length <= maxChars) return redactToolPayloadTextWithConfig(value, redactionConfig); const scan = `${sliceUtf16Safe(value, 0, maxChars)}${PERSISTED_DETAIL_REDACTION_BOUNDARY}${sliceUtf16Safe(value, maxChars, maxChars + MAX_PERSISTED_DETAIL_REDACTION_LOOKAHEAD_CHARS)}`; const redactedScan = redactToolPayloadTextWithConfig(scan, redactionConfig); const boundaryIndex = redactedScan.indexOf(PERSISTED_DETAIL_REDACTION_BOUNDARY); const redactedPrefix = boundaryIndex >= 0 ? redactedScan.slice(0, boundaryIndex) : "[OpenClaw persisted detail redacted: boundary marker removed]"; const safePrefixChars = Math.max(0, maxChars - Math.min(maxChars, MAX_PERSISTED_DETAIL_BOUNDARY_OVERLAP_CHARS)); const initialPersistedPrefix = truncateUtf16Safe(redactedPrefix, safePrefixChars); const persistedPrefix = PARTIAL_STRUCTURED_SECRET_VALUE_RE.test(initialPersistedPrefix) || PARTIAL_PRIVATE_KEY_BLOCK_RE.test(initialPersistedPrefix) ? "[OpenClaw persisted detail redacted: partial secret span omitted]" : initialPersistedPrefix; return `${persistedPrefix}${persistedPrefix ? "\n" : ""}[OpenClaw persisted detail redacted: boundary overlap omitted]\n\n[OpenClaw persisted detail truncated: ${Math.max(0, value.length - maxChars)} original chars omitted]`; } function selectPersistedDetailRedactionKey(key, inheritedKey) { return isSensitiveFieldKey(key) ? key : inheritedKey; } function redactedOriginalDetailKeys(src, redactionConfig) { return firstEnumerableOwnKeys(src, 40).map((key) => redactToolPayloadTextWithConfig(key, redactionConfig)); } function redactPersistedDetailValue(value, depth = 0, redactionKey, redactionConfig) { if (typeof value === "string") return redactionKey ? redactSensitiveFieldValueWithConfig(redactionKey, value, redactionConfig) : redactToolPayloadTextWithConfig(value, redactionConfig); if (redactionKey && (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint")) return redactSensitiveFieldValueWithConfig(redactionKey, String(value), redactionConfig); const source = asOptionalObjectRecord(value); if (!source) return value; if (depth >= 8) return "[OpenClaw persisted detail redacted: max depth exceeded]"; if (Array.isArray(value)) { let changed = false; const next = value.map((item) => { const redacted = redactPersistedDetailValue(item, depth + 1, redactionKey, redactionConfig); changed ||= redacted !== item; return redacted; }); return changed ? next : value; } let changed = false; const next = {}; for (const [key, field] of Object.entries(source)) { const redactedKey = redactToolPayloadTextWithConfig(key, redactionConfig); const redacted = redactPersistedDetailValue(field, depth + 1, selectPersistedDetailRedactionKey(key, redactionKey), redactionConfig); changed ||= redactedKey !== key || redacted !== field; next[redactedKey] = redacted; } return changed ? next : value; } function copyPersistedSummaryFields(params) { for (const key of params.keys) { const value = params.source[key]; if (value !== void 0) params.target[key] = typeof value === "string" ? redactPersistedDetailString(value, params.maxChars, params.redactionConfig) : redactPersistedDetailValue(value, 0, selectPersistedDetailRedactionKey(key, void 0), params.redactionConfig); } } function sanitizePersistedSessionDetail(value, redactionConfig) { const src = asOptionalObjectRecord(value); if (!src) return value; const out = {}; copyPersistedSummaryFields({ target: out, source: src, keys: [ "sessionId", "status", "pid", "startedAt", "endedAt", "runtimeMs", "cwd", "name", "truncated", "exitCode", "exitSignal" ], maxChars: 500, redactionConfig }); if (typeof src.command === "string") out.command = redactPersistedDetailString(src.command, 500, redactionConfig); return out; } function copyPersistedResultStateFields(out, src, maxStringChars, redactionConfig) { for (const key of [ "disabled", "unavailable", "success" ]) if (typeof src[key] === "boolean") out[key] = src[key]; if (typeof src.error === "string" && src.error) out.error = redactPersistedDetailString(src.error, maxStringChars, redactionConfig); else if (src.error) out.error = true; } function buildPersistedDetailsFallback(src, originalSize, sanitizedBytes, redactionConfig) { const fallback = { persistedDetailsTruncated: true, finalDetailsTruncated: true, ...originalDetailsSizeFields(originalSize) }; if (sanitizedBytes !== void 0) fallback.sanitizedDetailsBytes = sanitizedBytes; if (src) { fallback.originalDetailKeys = redactedOriginalDetailKeys(src, redactionConfig); copyPersistedSummaryFields({ target: fallback, source: src, keys: [ "status", "sessionId", "pid", "exitCode", "exitSignal", "truncated", "spill", "fullOutputPath", "spilledChars", "spillTruncated" ], maxChars: MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS, redactionConfig }); copyPersistedResultStateFields(fallback, src, MAX_PERSISTED_DETAIL_FALLBACK_STRING_CHARS, redactionConfig); } return fallback; } function enforcePersistedDetailsByteCap(value, originalDetails, originalSize, redactionConfig) { const sanitizedBytes = jsonUtf8BytesOrInfinity(value); if (sanitizedBytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) return value; const fallback = buildPersistedDetailsFallback(isRecord(originalDetails) ? originalDetails : void 0, originalSize, sanitizedBytes, redactionConfig); return jsonUtf8BytesOrInfinity(fallback) <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES ? fallback : buildPersistedDetailsFallback(void 0, originalSize, sanitizedBytes); } function sanitizeToolResultDetailsForPersistence(details, redactionConfig) { if (details === void 0 || details === null) return details; const originalSize = boundedJsonUtf8Bytes(details, MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES); if (originalSize.complete && originalSize.bytes <= MAX_PERSISTED_TOOL_RESULT_DETAILS_BYTES) return enforcePersistedDetailsByteCap(redactPersistedDetailValue(details, 0, void 0, redactionConfig), details, originalSize, redactionConfig); const src = asOptionalObjectRecord(details); if (!src) return enforcePersistedDetailsByteCap({ persistedDetailsTruncated: true, ...originalDetailsSizeFields(originalSize), valueType: typeof details }, void 0, originalSize, redactionConfig); const out = { persistedDetailsTruncated: true, ...originalDetailsSizeFields(originalSize), originalDetailKeys: redactedOriginalDetailKeys(src, redactionConfig) }; copyPersistedSummaryFields({ target: out, source: src, keys: [ "status", "sessionId", "pid", "startedAt", "endedAt", "cwd", "name", "exitCode", "exitSignal", "retryInMs", "total", "totalLines", "totalChars", "truncated", "spill", "fullOutputPath", "spilledChars", "spillTruncated", "truncation" ], maxChars: MAX_PERSISTED_DETAIL_STRING_CHARS, redactionConfig }); copyPersistedResultStateFields(out, src, MAX_PERSISTED_DETAIL_STRING_CHARS, redactionConfig); if (typeof src.tail === "string") out.tail = redactPersistedDetailString(src.tail, MAX_PERSISTED_DETAIL_STRING_CHARS, redactionConfig); if (Array.isArray(src.sessions)) { out.sessions = src.sessions.slice(0, MAX_PERSISTED_DETAIL_SESSION_COUNT).map((session) => sanitizePersistedSessionDetail(session, redactionConfig)); if (src.sessions.length > MAX_PERSISTED_DETAIL_SESSION_COUNT) out.sessionsTruncated = src.sessions.length - MAX_PERSISTED_DETAIL_SESSION_COUNT; } return enforcePersistedDetailsByteCap(out, src, originalSize, redactionConfig); } function capToolResultForPersistence(msg, maxChars, redactionConfig) { const capped = msg.role === "toolResult" ? truncateToolResultMessage(msg, maxChars, { suffix: formatContextLimitTruncationNotice, minKeepChars: 2e3 }) : msg; if (capped.role !== "toolResult") return capped; const details = capped.details; const sanitizedDetails = sanitizeToolResultDetailsForPersistence(details, redactionConfig); return sanitizedDetails === details ? capped : { ...capped, details: sanitizedDetails }; } function normalizePersistedToolResultName(message, fallbackName, fallbackId) { if (message.role !== "toolResult") return message; const rawToolName = message.toolName; const normalizedToolName = normalizeOptionalString(rawToolName); const normalizedFallback = normalizeOptionalString(fallbackName); const toolName = normalizedToolName ?? normalizedFallback ?? "unknown"; const rawToolCallIdValue = message.toolCallId; const rawToolCallId = typeof rawToolCallIdValue === "string" ? rawToolCallIdValue : void 0; const toolCallId = rawToolCallId ?? normalizeOptionalString(fallbackId); const isError = typeof message.isError === "boolean" ? message.isError : false; if (rawToolName === toolName && rawToolCallId === toolCallId && message.isError === isError) return message; return { ...message, ...toolCallId ? { toolCallId } : {}, toolName, isError }; } //#endregion //#region src/agents/session-tool-result-guard.transcript-seq.ts function resolveEntryTranscriptSeq(sessionManager, entryId, seqByEntryId) { if (!entryId) return 0; const cached = seqByEntryId.get(entryId); if (cached !== void 0) return cached; let seq = 0; for (const entry of sessionManager.getBranch(entryId)) { if (entry.type === "message" || entry.type === "compaction") seq += 1; seqByEntryId.set(entry.id, seq); } return seqByEntryId.get(entryId); } function resolveAppendedMessageSeq(params) { if (typeof params.entryId !== "string") return; const parentSeq = resolveEntryTranscriptSeq(params.sessionManager, params.parentEntryId, params.seqByEntryId); if (parentSeq === void 0) return; const messageSeq = parentSeq + 1; params.seqByEntryId.set(params.entryId, messageSeq); return messageSeq; } //#endregion //#region src/agents/session-tool-result-guard.ts function extractPendingAssistantToolCalls(message) { return message.role === "assistant" && message.stopReason !== "aborted" && message.stopReason !== "error" ? extractToolCallsFromAssistant(message) : []; } /** * Identities of one streamed assistant response. The runtime turnId is carried by every * fragment; the provider responseId can first appear on a later fragment. */ function assistantResponseIds(message) { if (message.role !== "assistant") return []; return [message.responseId, message.turnId].flatMap((id) => normalizeOptionalString(id) ?? []); } function clearsPendingToolCalls(message, toolCalls, allowSyntheticToolResults, pendingResponseIds) { if (message.role === "toolResult") return false; if (assistantResponseIds(message).some((id) => pendingResponseIds.includes(id))) return false; return !(message.role === "custom" && "excludeFromContext" in message && message.excludeFromContext === true || message.role === "assistant" && toolCalls.length === 0 && isTranscriptOnlyOpenClawAssistantModel(normalizeOptionalString(message.provider) ?? "", normalizeOptionalString(message.model) ?? "")) && (toolCalls.length === 0 || message.role !== "assistant") || !allowSyntheticToolResults && toolCalls.length > 0; } function installSessionToolResultGuard(sessionManager, opts) { const originalAppend = getRawSessionAppendMessage(sessionManager); const originalAppendWithTranscriptAnchor = sessionManager.appendMessageWithTranscriptAnchor.bind(sessionManager); const originalAppendWithTranscriptAnchorAsync = sessionManager.appendMessageWithTranscriptAnchorAsync.bind(sessionManager); setRawSessionAppendMessage(sessionManager, originalAppend); setRawSessionAppendMessageAsync(sessionManager, getRawSessionAppendMessageAsync(sessionManager)); const pending = /* @__PURE__ */ new Map(); let pendingResponseIds = []; const persistMessage = (message, sourceAppend) => { const transformer = opts?.transformMessageForPersistence; const persisted = transformer ? transformer(message) : message; copyCodeModeSourceAppend(message, persisted, sourceAppend); return persisted; }; const persistToolResult = (message, meta) => { const transformer = opts?.transformToolResultForPersistence; return transformer ? transformer(message, meta) : message; }; const allowSyntheticToolResults = opts?.allowSyntheticToolResults ?? true; const missingToolResultText = opts?.missingToolResultText; const beforeWrite = opts?.beforeMessageWriteHook; const toolResultTransformerMayMutate = opts?.transformToolResultForPersistence !== void 0; const redactionConfig = opts?.config?.logging; const maxToolResultChars = resolveMaxToolResultChars(opts); const transcriptSeqByEntryId = /* @__PURE__ */ new Map(); let transcriptRunId = opts?.runId; let assistantErrorTranscript = opts?.assistantErrorTranscript; let nextUserMessagePersistence = opts?.suppressNextUserMessagePersistence ? "suppress" : "normal"; const appendRequest = (request, append) => withRuntimeUserTurnTranscriptRecorder(request.message, (beforeFreshMessageCommit) => { const appendOptions = opts?.config || beforeFreshMessageCommit ? copyCodeModeSourceAppendOptions(request.options, { ...request.options, ...opts?.config ? { config: opts.config } : {}, ...beforeFreshMessageCommit ? { beforeFreshMessageCommit } : {} }) : request.options; return append(request.message, request.sourceAppend ? prepareCodeModeSourceAppend(appendOptions ?? {}, request.message, request.sourceAppend) : appendOptions); }); const runSync = (operation) => { let next = operation.next(); while (!next.done) next = operation.next(appendRequest(next.value, originalAppendWithTranscriptAnchor)); return next.value; }; const runAsync = async (operation) => { let next = operation.next(); while (!next.done) { const request = next.value; next = operation.next(await withCurrentRuntimeUserTurnTranscriptRecorder(request.message, () => appendRequest(request, originalAppendWithTranscriptAnchorAsync))); } return next.value; }; const updatePending = (message, calls = extractPendingAssistantToolCalls(message)) => { const resultId = message.role === "toolResult" ? extractToolResultId(message) : null; if (resultId) pending.delete(resultId); for (const call of calls) pending.set(call.id, call.name); if (calls.length > 0) pendingResponseIds = assistantResponseIds(message); }; const recordPendingReceipt = (entryId, message, viewWasSuperseded) => { if (!viewWasSuperseded) { updatePending(message); return; } const branch = sessionManager.getBranch(); const committedIndex = branch.findIndex((entry) => entry.id === entryId); if (committedIndex < 0) return; for (let index = committedIndex; index < branch.length; index++) { const entry = branch[index]; if (entry?.type !== "message") continue; const calls = extractPendingAssistantToolCalls(entry.message); if (clearsPendingToolCalls(entry.message, calls, allowSyntheticToolResults, pendingResponseIds)) pending.clear(); updatePending(entry.message, calls); } }; function* appendMessageAndCacheTranscriptSeq(message, options, sourceAppend, acknowledgementSource = message) { const assistantSource = readAgentAssistantSource(acknowledgementSource); const runOwnedMessage = attachSessionTranscriptRunId(message, transcriptRunId); copyCodeModeSourceAppend(message, runOwnedMessage, sourceAppend); const parentEntryId = sessionManager.getLeafId(); const originalTarget = sessionManager.getSessionTarget(); const { entryId, anchor, appended, lifecycleRevision, message: persistedMessage, viewWasSuperseded } = yield { message: runOwnedMessage, options, sourceAppend }; const sessionTarget = anchor ? { agentId: anchor.agentId, sessionId: anchor.sessionId, sessionKey: anchor.sessionKey, storePath: anchor.storePath } : originalTarget; if (viewWasSuperseded) transcriptSeqByEntryId.clear(); const persistedEntry = sessionManager.getEntry(entryId); const messageSeq = appended && sessionTarget && (!viewWasSuperseded || persistedEntry) ? resolveAppendedMessageSeq({ sessionManager, entryId, parentEntryId: persistedEntry ? persistedEntry.parentId : parentEntryId, seqByEntryId: transcriptSeqByEntryId }) : void 0; acknowledgeInternalToolResult(acknowledgementSource); if (assistantSource && appended) { assistantSource.committedMessageSeq = messageSeq ?? (anchor ? anchor.activeMessagePosition + 1 : null); bindAgentAssistantSource(persistedMessage, assistantSource); } recordPendingReceipt(entryId, persistedMessage, viewWasSuperseded === true); if (appended) opts?.onMessagePersisted?.(persistedMessage); if (!appended || !sessionTarget) return { entryId, message: persistedMessage, appended, ...anchor ? { anchor } : {} }; return { entryId, appended, lifecycleRevision, message: persistedMessage, ...anchor ? { anchor } : {}, sessionTarget, messageSeq }; } const originalAppendCompaction = sessionManager.appendCompaction.bind(sessionManager); const originalAppendCompactionAsync = sessionManager.appendCompactionAsync.bind(sessionManager); const guardedAppendCompaction = ((...args) => { args[5] = { runId: transcriptRunId, ...args[5] }; return withSessionCompactionPersistence(sessionManager, opts?.withCompactionPersistence, () => originalAppendCompaction(...args)); }); const guardedAppendCompactionAsync = (...args) => { args[5] = { runId: transcriptRunId, ...args[5] }; return withSessionCompactionPersistenceAsync(sessionManager, opts?.withCompactionPersistenceAsync, () => originalAppendCompactionAsync(...args)); }; const applyBeforeWriteHook = (msg, sourceAppend) => { const result = beforeWrite ? beforeWrite({ message: msg }, sourceAppend) : void 0; if (result?.block) return null; return result?.message ? { message: result.message, changed: true } : { message: msg, changed: false }; }; function* flushPendingToolResultsOperation() { if (pending.size === 0) return; if (allowSyntheticToolResults) for (const [id, name] of pending.entries()) { const synthetic = makeMissingToolResult({ toolCallId: id, toolName: name, text: missingToolResultText }); const persistedSynthetic = persistMessage(synthetic); const transformed = persistToolResult(persistedSynthetic, { toolCallId: id, toolName: name, isSynthetic: true }); const flushed = applyBeforeWriteHook(transformed); if (flushed) { const canonical = flushed.message.role === "toolResult" ? rewriteToolResultIds({ message: flushed.message, resolveId: () => id }) : flushed.message; yield* appendMessageAndCacheTranscriptSeq(capToolResultForPersistence(canonical, maxToolResultChars, redactionConfig), { invalidateSerializedPrefixCache: persistedSynthetic !== synthetic || toolResultTransformerMayMutate || canonical !== flushed.message || flushed.changed }); } } pending.clear(); } const flushPendingToolResults = () => runSync(flushPendingToolResultsOperation()); const flushPendingToolResultsAsync = () => withSessionManagerWrite(sessionManager, () => runAsync(flushPendingToolResultsOperation())); function* guardedAppend(message, callerOptions, sourceAppend) { const callerInvalidatesCache = callerOptions?.invalidateSerializedPrefixCache === true; let nextMessage = message; if (message.role === "assistant") { const sanitized = sanitizeToolCallInputs([message], { allowedToolNames: opts?.allowedToolNames }); if (sanitized.length === 0) { if (pending.size > 0) yield* flushPendingToolResultsOperation(); return; } nextMessage = sanitized[0]; copyCodeModeSourceAppend(message, nextMessage, sourceAppend); } if (nextMessage.role === "toolResult") { const id = extractToolResultId(nextMessage); const toolName = id ? pending.get(id) : void 0; const normalizedToolResult = normalizePersistedToolResultName(nextMessage, toolName, id ?? void 0); const persistedToolResult = persistMessage(normalizedToolResult); const capped = capToolResultForPersistence(persistedToolResult, maxToolResultChars, redactionConfig); const transformed = persistToolResult(capped, { toolCallId: id ?? void 0, toolName, isSynthetic: false }); const persisted = applyBeforeWriteHook(transformed); if (!persisted) return; return (yield* appendMessageAndCacheTranscriptSeq(capToolResultForPersistence(persisted.message, maxToolResultChars, redactionConfig), { invalidateSerializedPrefixCache: callerInvalidatesCache || persistedToolResult !== normalizedToolResult || toolResultTransformerMayMutate || persisted.changed }, void 0, message)).entryId; } const toolCalls = extractPendingAssistantToolCalls(nextMessage); if (pending.size > 0 && clearsPendingToolCalls(nextMessage, toolCalls, allowSyntheticToolResults, pendingResponseIds)) yield* flushPendingToolResultsOperation(); if (nextMessage.role === "user" && nextUserMessagePersistence === "runtime") { nextUserMessagePersistence = "normal"; Object.assign(nextMessage, { provenance: { kind: "internal_system" }, display: false }); } const transformedMessage = persistMessage(nextMessage, sourceAppend); const finalWrite = applyBeforeWriteHook(transformedMessage, sourceAppend); if (!finalWrite) { if (transformedMessage.role === "user") opts?.onUserMessageBlocked?.(transformedMessage); return; } let finalMessage = finalWrite.message; if (finalMessage.role === "assistant" && toolCalls.length === 0 && opts?.suppressTranscriptOnlyAssistantPersistence === true) return; if (finalMessage.role === "assistant" && assistantErrorTranscript && finalMessage.stopReason === "error") { const target = sessionManager.getSessionTarget(); if (target) { const replayMessage = assistantErrorTranscript.record(finalMessage, target, message); if (!replayMessage) return; copyCodeModeSourceAppend(finalMessage, replayMessage, sourceAppend); finalMessage = replayMessage; } } if (finalMessage.role === "user") { const suppress = nextUserMessagePersistence === "suppress"; nextUserMessagePersistence = "normal"; if (suppress) { opts?.onUserMessagePersistenceSuppressed?.(finalMessage); return; } } const { anchor, appended, entryId: result, lifecycleRevision, message: persistedMessage, messageSeq, sessionTarget } = yield* appendMessageAndCacheTranscriptSeq(finalMessage, { invalidateSerializedPrefixCache: callerInvalidatesCache || transformedMessage !== nextMessage || finalWrite.changed || finalMessage !== finalWrite.message }, sourceAppend, message); if (sessionTarget) { const runId = resolveTerminalAssistantTranscriptRunId(persistedMessage, transcriptRunId); publishTranscriptUpdate(sessionTarget, { lifecycleRevision, message: persistedMessage, messageId: typeof result === "string" ? result : void 0, ...messageSeq !== void 0 ? { messageSeq } : {}, ...runId ? { runId } : {} }); } if (finalMessage.role === "user" && persistedMessage.role === "user") opts?.onUserMessagePersisted?.(finalMessage, { ...anchor ? { anchor } : {}, appended, entryId: result, persistedMessage, ...sessionTarget ? { sessionTarget } : {} }); return result; } sessionManager.appendMessage = ((message, options) => { prepareSessionManagerSync("appendMessage", sessionManager.getSessionTarget(), sessionManager); return withCodeModeSourceAppend(message, options, (sourceAppend) => runSync(guardedAppend(message, options, sourceAppend))); }); sessionManager.appendMessageAsync = (message, options) => withSessionManagerWrite(sessionManager, () => withCodeModeSourceAppend(message, options, (sourceAppend) => runAsync(guardedAppend(message, options, sourceAppend)))); sessionManager.appendCompaction = guardedAppendCompaction; sessionManager.appendCompactionAsync = guardedAppendCompactionAsync; return { hasPendingToolResults: () => pending.size > 0, flushPendingToolResults, flushPendingToolResultsAsync, clearPendingToolResults: () => pending.clear(), setNextUserMessagePersistence: (mode) => { nextUserMessagePersistence = mode; }, getPendingIds: () => Array.from(pending.keys()), setTranscriptRunId: (runId, errors) => { transcriptRunId = runId; assistantErrorTranscript = errors; } }; } //#endregion //#region src/agents/session-tool-result-guard-wrapper.ts /** * Apply the tool-result guard to a SessionManager exactly once and expose * a flush method on the instance for easy teardown handling. */ function guardSessionManager(sessionManager, opts) { const guardedSessionManager = sessionManager; let prepareAssistantTranscriptMessage = opts?.trigger === "memory" ? void 0 : opts?.prepareAssistantTranscriptMessage; let skipBeforeMessageWriteHooks = opts?.skipBeforeMessageWriteHooks; let inputProvenance = opts?.inputProvenance; let pendingPreparedUserTurnMessage = opts?.preparedUserTurnMessage; let preparedUserTurnTranscriptRecorder = opts?.preparedUserTurnTranscriptRecorder; let preparedUserReplayKey = preparedUserTurnTranscriptRecorder?.getPersistedMessage?.()?.idempotencyKey === pendingPreparedUserTurnMessage?.idempotencyKey ? pendingPreparedUserTurnMessage?.idempotencyKey : void 0; if (typeof guardedSessionManager.flushPendingToolResultsAsync === "function") { guardedSessionManager.setTranscriptRunContext?.(opts?.runId, prepareAssistantTranscriptMessage, skipBeforeMessageWriteHooks, opts?.assistantErrorTranscript, inputProvenance, preparedUserReplayKey === void 0 && opts?.suppressNextUserMessagePersistence, { message: pendingPreparedUserTurnMessage, recorder: preparedUserTurnTranscriptRecorder, replayKey: preparedUserReplayKey }); return guardedSessionManager; } const hookRunner = getGlobalHookRunner(); let queuedUserTurnTranscriptRecorder; const runtimeUserMessageByPersistedMessage = /* @__PURE__ */ new WeakMap(); const beforeMessageWrite = (event, sourceAppend) => { if (isMidTurnPrecheckAssistantError(event.message)) return { block: true }; const runtimeUserMessage = runtimeUserMessageByPersistedMessage.get(event.message); let message = event.message; let changed = false; const skipUserWriteHook = skipBeforeMessageWriteHooks || message.role === "user" && queuedUserTurnTranscriptRecorder?.getPendingInputMessage?.() !== void 0; if (!skipUserWriteHook && hookRunner?.hasHooks("before_message_write") || prepareAssistantTranscriptMessage) { const preparedMessage = message.role === "user" ? { ...message, __openclaw: { ...Reflect.get(message, "__openclaw") } } : void 0; if (preparedMessage && (preparedMessage["__openclaw"].humanMentions !== void 0 || preparedMessage["__openclaw"].workContext !== void 0)) { preparedMessage.content = structuredClone(preparedMessage.content); preparedMessage["__openclaw"].humanMentions = structuredClone(preparedMessage["__openclaw"].humanMentions); } const next = runAgentHarnessBeforeMessageWriteHook({ message, agentId: opts?.agentId, sessionKey: opts?.sessionKey, prepareAssistantTranscriptMessage, skipBeforeMessageWriteHooks: skipUserWriteHook }); if (!next) { runtimeUserMessageByPersistedMessage.delete(event.message); queuedUserTurnTranscriptRecorder?.markBlocked(); queuedUserTurnTranscriptRecorder = void 0; return { block: true }; } message = restorePreparedUserTurnOperationalMetaForRuntime({ runtimeMessage: next, preparedMessage }); changed = true; } copyCodeModeSourceAppend(event.message, message, sourceAppend); const redacted = redactTranscriptMessage(message, opts?.config, sourceAppend); if (redacted !== message) { message = redacted; changed = true; } const projectedMessage = projectAgentHarnessTranscriptMessageForDisplay({ hidden: opts?.trigger === "memory", inputProvenance, message }); if (projectedMessage !== message) { copyCodeModeSourceAppend(message, projectedMessage, sourceAppend); message = projectedMessage; changed = true; } if (message.role !== "user" && queuedUserTurnTranscriptRecorder) { queuedUserTurnTranscriptRecorder.markBlocked(); queuedUserTurnTranscriptRecorder = void 0; } if (message.role === "user" && queuedUserTurnTranscriptRecorder) { message = attachRuntimeUserTurnTranscriptRecorder(message, queuedUserTurnTranscriptRecorder); queuedUserTurnTranscriptRecorder = void 0; } if (runtimeUserMessage && message.role === "user") runtimeUserMessageByPersistedMessage.set(message, runtimeUserMessage); return changed ? { message } : void 0; }; const transform = hookRunner?.hasHooks("tool_result_persist") ? (message, meta) => { return hookRunner.runToolResultPersist({ toolName: meta.toolName, toolCallId: meta.toolCallId, message, isSynthetic: meta.isSynthetic }, { agentId: opts?.agentId, sessionKey: opts?.sessionKey, toolName: meta.toolName, toolCallId: meta.toolCallId })?.message ?? message; } : void 0; const guard = installSessionToolResultGuard(sessionManager, { sessionKey: opts?.sessionKey, agentId: opts?.agentId, runId: opts?.runId, transformMessageForPersistence: (message) => { queuedUserTurnTranscriptRecorder = void 0; const withProvenance = applyInputProvenanceToUserMessage(message, inputProvenance); const runtimeContext = takeRuntimeUserTurnTranscriptContext(message); if (message.role === "user" && preparedUserReplayKey !== void 0 && Reflect.get(runtimeContext?.message ?? message, "idempotencyKey") !== preparedUserReplayKey) { pendingPreparedUserTurnMessage = void 0; guard.setNextUserMessagePersistence("normal"); } const prepared = runtimeContext?.message ?? pendingPreparedUserTurnMessage; const recorder = runtimeContext?.recorder ?? (prepared !== void 0 && prepared === pendingPreparedUserTurnMessage ? preparedUserTurnTranscriptRecorder : void 0); if (message.role === "user") opts?.onUserMessagePreparingForPersistence?.(message, recorder, prepared); const merged = mergePreparedUserTurnMessageForRuntime({ runtimeMessage: withProvenance, ...prepared ? { preparedMessage: prepared } : {} }); if (merged !== withProvenance) { queuedUserTurnTranscriptRecorder = recorder; if (!runtimeContext) pendingPreparedUserTurnMessage = void 0; } if (message.role === "user" && merged.role === "user") runtimeUserMessageByPersistedMessage.set(merged, message); return merged; }, transformToolResultForPersistence: transform, allowSyntheticToolResults: opts?.allowSyntheticToolResults, missingToolResultText: opts?.missingToolResultText, allowedToolNames: opts?.allowedToolNames, beforeMessageWriteHook: beforeMessageWrite, config: opts?.config, maxToolResultChars: typeof opts?.contextWindowTokens === "number" ? resolveLiveToolResultMaxChars({ contextWindowTokens: opts.contextWindowTokens }) : void 0, suppressNextUserMessagePersistence: opts?.suppressNextUserMessagePersistence, suppressTranscriptOnlyAssistantPersistence: opts?.suppressTranscriptOnlyAssistantPersistence, assistantErrorTranscript: opts?.assistantErrorTranscript, onMessagePersisted: opts?.onMessagePersisted, withCompactionPersistence: opts?.withCompactionPersistence, withCompactionPersistenceAsync: opts?.withCompactionPersistenceAsync, onUserMessagePersisted: async (message, persistence) => { const runtimeMessage = runtimeUserMessageByPersistedMessage.get(message); runtimeUserMessageByPersistedMessage.delete(message); takeRuntimeUserTurnTranscriptRecorder(message)?.markRuntimePersisted(persistence.persistedMessage, persistence.anchor, { appended: persistence.appended }); await opts?.onUserMessagePersisted?.(persistence.persistedMessage, runtimeMessage); }, onUserMessagePersistenceSuppressed: async (message) => { const runtimeMessage = runtimeUserMessageByPersistedMessage.get(message); runtimeUserMessageByPersistedMessage.delete(message); await opts?.onUserMessagePersistenceSuppressed?.(message, runtimeMessage); }, onUserMessageBlocked: opts?.onUserMessageBlocked }); setSessionToolTextPreparer(guardedSessionManager, (block) => prepareModelVisibleToolTextBlock(block, resolveTranscriptLoggingConfig(opts?.config))); guardedSessionManager.hasPendingToolResults = guard.hasPendingToolResults; guardedSessionManager.flushPendingToolResults = guard.flushPendingToolResults; guardedSessionManager.flushPendingToolResultsAsync = guard.flushPendingToolResultsAsync; guardedSessionManager.clearPendingToolResults = guard.clearPendingToolResults; guardedSessionManager.setNextUserMessagePersistence = guard.setNextUserMessagePersistence; guardedSessionManager.setTranscriptRunContext = (runId, prepare, skipHooks, errors, provenance, suppressUserPersistence, preparedUserTurn) => { guard.setTranscriptRunId(runId, errors); guard.setNextUserMessagePersistence(suppressUserPersistence ? "suppress" : "normal"); prepareAssistantTranscriptMessage = prepare; skipBeforeMessageWriteHooks = skipHooks; inputProvenance = provenance; pendingPreparedUserTurnMessage = preparedUserTurn.message; preparedUserTurnTranscriptRecorder = preparedUserTurn.recorder; preparedUserReplayKey = preparedUserTurn.replayKey; }; return guardedSessionManager; } //#endregion //#region src/agents/runtime-execution-facts.ts function buildApprovedExecutablesRuntimeContext(agentId, includeEmptySnapshots) { const header = "## Approved executables"; try { const { allowlist } = resolveExecApprovalsFromFileInternal({ file: loadExecApprovals(), agentId }); const hints = allowlist.flatMap((entry) => { const pattern = entry.pattern.trim(); if (pattern.startsWith("=command:") || !/[\\/~]/.test(pattern)) return []; const name = path.win32.isAbsolute(pattern) ? pattern : path.win32.basename(pattern).replace(/\.exe$/i, ""); if (!name || name.length > 256 || sanitizeForPromptLiteral(name) !== name) return []; return [` ${name} ${entry.argPattern ? "(restricted args)" : "(any arguments)"}`]; }).toSorted().slice(0, 10); if (!hints.length && !includeEmptySnapshots) return; return [ header, hints.length ? "Pre-approved executables (exact arguments are enforced at runtime; no approval prompt needed when args match):" : "none", ...hints ].join("\n"); } catch { return `${header}\nunavailable`; } } function buildExecutionHostRuntimeFacts(params) { const sections = []; const includeEmptySnapshots = params.includeEmptySnapshots === true; if (process.platform === "win32" && params.capabilityToolNames.has("exec")) { const approved = buildApprovedExecutablesRuntimeContext(params.agentId, includeEmptySnapshots); if (approved) sections.push(approved); } if (params.capabilityToolNames.has("process")) { const sessions = listActiveProcessSessionReferences({ scopeKey: resolveProcessToolScopeKey(params) }).toSorted((a, b) => a.sessionId < b.sessionId ? -1 : a.sessionId > b.sessionId ? 1 : 0).map((session) => { const pid = typeof session.pid === "number" ? ` pid=${session.pid}` : ""; const cwd = session.cwd ? ` cwd=${truncateUtf16Safe(sanitizeForPromptLiteral(session.cwd), 256)}` : ""; return `- ${session.sessionId} ${session.status}${pid}${cwd} :: ${sanitizeForPromptLiteral(session.name)}`; }); if (sessions.length || includeEmptySnapshots) sections.push(`Active exec sessions:\n${sessions.join("\n") || "none"}`); } return sections.map((text) => ({ kind: "conversation-data", text })); } //#endregion export { isMidTurnPrecheckSignal as a, isMidTurnPrecheckAssistantError as i, guardSessionManager as n, MidTurnPrecheckSignal as r, buildExecutionHostRuntimeFacts as t };