openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
812 lines (811 loc) • 42.2 kB
JavaScript
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 };