openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
3,306 lines • 144 kB
JavaScript
import { a as addTimerTimeoutGraceMs, p as clampPositiveTimerTimeoutMs } from "./number-coercion-CLj0HTDM.js";
import { l as toErrorObject } from "./error-coercion-D_-xJ90S.js";
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { n as sliceUtf16Safe, r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { n as isTruthyEnvValue } from "./env-M3R40TOb.js";
import "./utils-P__uGsPB.js";
import { t as createAbortError } from "./abort-signal-D2k14JsD.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { D as freezeDiagnosticTraceContext, T as createDiagnosticTraceContextFromActiveScope, V as hasInternalDiagnosticEventListeners, o as emitTrustedDiagnosticEvent, s as emitTrustedDiagnosticEventWithPrivateData, t as areDiagnosticsEnabledForProcess } from "./diagnostic-events-Cwe92uV3.js";
import { n as compareValidSemver } from "./semver-aYpwYdrQ.js";
import { c as normalizeToolPolicyName } from "./tool-policy-shared-DIyS0iQC.js";
import "./tool-policy-Wi0C45cX.js";
import { a as resolveExecutablePath } from "./executable-path-BK-aU8Pd.js";
import { s as sanitizeHostExecEnv } from "./host-env-security-BAvlDaJf.js";
import { i as shouldLogVerbose } from "./globals-CTaGxEqj.js";
import { n as truncateUtf8Suffix } from "./utf8-truncate-CCgJrv8A.js";
import { t as NODE_AGENT_CLI_CLAUDE_RUN_COMMAND } from "./node-commands-BC8PhxqU.js";
import { i as emitAgentEvent, t as assertAgentRunLifecycleGenerationCurrent } from "./agent-events-CoxiItUi.js";
import { g as registerAgentRunDelegatedAuthorityClosedHandler } from "./agent-run-registry-CKYKdfNd.js";
import { a as getPluginRuntimeGatewayRequestScope } from "./gateway-request-scope-BCMYlsDI.js";
import { y as runBeforeToolCallHook } from "./agent-tools.before-tool-call-Bb7DJuFB.js";
import { l as requestHeartbeat } from "./heartbeat-wake-dlWl8dXS.js";
import { a as enqueueSystemEvent } from "./system-events-C03jsM0j.js";
import { n as applyPluginTextReplacements } from "./text-transforms.runtime-ClZrZnow.js";
import { t as classifyFailoverReason } from "./classify-GlkuT1ur.js";
import "./session-accessor-YsytfDtG.js";
import { u as loadSessionEntryReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { a as isFailoverError, o as isSignalTimeoutReason, t as FailoverError } from "./error-Bb_OF8ag.js";
import { g as resolveCliToolTerminalReason } from "./run-termination-jabvTOp5.js";
import { g as BLOCKED_TOOL_CALL_ABORT_FLOOR_MS, t as beginDiagnosticBackendActivity } from "./diagnostic-run-activity-CJrEba4R.js";
import { i as getAdmittedRunDelegatedAuthority, s as resolveAdmittedRunActiveAssertion } from "./admitted-run-context-CHY5SdVF.js";
import { a as withGatewayToolCallerIdentity, r as getGatewayToolCallerIdentity, t as createAdmittedGatewayToolCallerIdentity } from "./gateway-caller-context-BxVUFvkR.js";
import { n as getInstallationTarget, r as installationTargetEnv, t as LOCAL_INSTALLATION_TARGET_UNSUPPORTED } from "./installation-target-context-BP05Rs6C.js";
import { t as getProcessSupervisor } from "./supervisor--Gozeeeu.js";
import { n as getAgentScopedMediaLocalRoots } from "./local-roots-BphuGw-a.js";
import "./embedded-agent-helpers-D0eiT3IT.js";
import { p as resolveFailoverStatus } from "./failover-error-BkNxlp8A.js";
import { n as diagnosticErrorFailureKind, r as diagnosticErrorMessage, t as diagnosticErrorCategory } from "./diagnostic-error-metadata-BgNm5seU.js";
import { n as resolveDiagnosticModelContentCapturePolicy, t as cloneDiagnosticContentValue } from "./diagnostic-llm-content-CAc71KJ1.js";
import { o as readToolResultDetails } from "./tool-result-error-CbDLJzG-.js";
import { a as sanitizeExecApprovalWarningTextWithStatus, t as exceedsApprovalTextLimit } from "./exec-approval-text-sanitize-C1BzZQH8.js";
import { n as DEFAULT_PLUGIN_APPROVAL_TIMEOUT_MS, p as truncatePluginApprovalDetail } from "./plugin-approvals-BUrr4V_c.js";
import { t as callGatewayTool } from "./gateway-CB9m4Gcq.js";
import { d as isMessagingToolSendAction, f as isMessagingToolTargetEvidenceAction, l as isMessagingTool, p as isPluginNativeMessagingTool, u as isMessagingToolDeliveryAction } from "./tool-loop-detection-C39fRNx6.js";
import { l as withAgentQuestionAnswerAuthority } from "./host-private-capabilities-DSGdSEaS.js";
import { x as readEmbeddedMessageDeliveryFact } from "./openclaw-tools-CNOZOjlX.js";
import { n as applySkillEnvOverridesFromSnapshot } from "./env-overrides-C5ogyZ3W.js";
import { t as createNodeDuplexEndpoint } from "./node-duplex-framing-SEDM1SDZ.js";
import { l as resolveNodeCommandAllowlist, o as isNodeCommandAllowed } from "./node-command-policy-H4FpS0aA.js";
import { i as scopedHeartbeatWakeOptionsForPolicy, n as resolveEventSessionRoutingPolicy, t as resolveEventSessionKeyForPolicy } from "./event-session-routing-CJOO5dMS.js";
import { t as resolveExecDefaults } from "./exec-defaults-CET-_UfH.js";
import { n as hasHydratableMediaImages } from "./images.media-refs-DkoAIIi5.js";
import { r as detectImageReferences } from "./images-BbjK0c3L.js";
import { a as extractMessagingToolSend, c as isDeliveredMessagingToolSendToCurrentSource, i as filterToolResultMediaUrls, n as collectMessagingMediaUrlsFromToolResult, o as extractMessagingToolSendResult, r as extractToolResultMediaArtifact, s as extractMessagingToolSourceReplyPayload, t as collectMessagingMediaUrlsFromRecord } from "./embedded-agent-tool-media-_o_qAQ7H.js";
import { c as sanitizeToolResult, s as sanitizeToolArgs } from "./embedded-agent-tool-results-ZMp1V0-a.js";
import { r as normalizeAcceptedSessionSpawnResult } from "./accepted-session-spawn-CUc_zZp_.js";
import { a as resolveMessageToolSourceReplyFinal, n as isDeliveredMessageToolOnlySourceReplyResult, r as isDeliveredMessagingToolResult } from "./embedded-agent-message-tool-source-reply-BNQPPjZe.js";
import { t as createModelCallStreamProgressReporter } from "./diagnostic-model-stream-progress-DkFkdHqx.js";
import { t as resolveToolLoopDetectionConfig } from "./tool-loop-detection-config-Bg911KiY.js";
import { r as shouldUseInternalSourceReplySink } from "./message-action-runner-D-2Vw1B0.js";
import { a as resolveRegisteredExecApprovalDecision, i as registerExecApprovalRequestForHostOrThrow } from "./bash-tools.exec-approval-request-DYkIh7HP.js";
import { c as prepareSystemRunMutableFileBinding, d as revalidateSystemRunMutableFileBinding } from "./system-run-approval-binding-D8O27dhf.js";
import { n as stripOpenClawMcpToolPrefix } from "./tool-policy-CErHvAIq.js";
import { t as createPluginToolsMcpHandlers } from "./plugin-tools-handlers-Co8hxKUr.js";
import { a as resolveCliRuntimeOwnerFingerprint, o as resolveCliExecutableIdentity, t as fingerprintCliRuntimeArtifact } from "./cli-auth-epoch-DsTr8_ej.js";
import { n as hashCliImageTurnEntryId } from "./cli-image-turn-correlation-Dxv2z8kT.js";
import { i as formatCliBackendOutputDigest, r as cliBackendLog } from "./log-Bl5Aodkw.js";
import { c as resolvePromptInput, d as writeCliSystemPromptFile, f as buildCliSupervisorScopeKey, i as isClaudeCliBackendId, l as resolveSessionIdToSend, m as resolveCliRunTimeoutOverrideMs, n as buildCliArgs, o as prepareCliPromptImagePayload, p as resolveCliNoOutputTimeoutMs, r as enqueueCliRun, s as resolveCliRunQueueKey, u as resolveSystemPromptUsage } from "./helpers-D0iW2O3m.js";
import { n as compileStructuredInputQuestions, o as runStructuredInput } from "./structured-input-BvzwNGZ1.js";
import { n as prepareCliBundleMcpCaptureAttempt, t as composeCliPromptContext } from "./prompt-context-DC4Us2M0.js";
import { a as getCliLiveSessionApprovalGrants, c as createCliExitFailoverError, d as formatCliOutputError, f as parseCliOutput, i as createCliLiveSessionCapability, l as createCliFailoverError, m as createCliRunCurrentAssertion, n as buildCliLiveOwnerKey, p as createCliJsonlStreamingParser, r as closeCliLiveSession, t as acceptsCliLiveSession, u as resolveCliResumeAtError } from "./cli-live-session-registry-BHTgbpiF.js";
import { c as encodeNodeClaudeSkillMessage, i as NODE_CLAUDE_WORKSHOP_RESULT_BYTES, n as NODE_CLAUDE_SKILLS_MESSAGE_BYTES, o as NodeClaudeSkillUpstreamSchema } from "./node-claude-skill-protocol-C_HjDV4_.js";
import { n as prepareSkillResourceDelivery } from "./resources-BYST4V_Q.js";
import { t as buildMcpToolSchema } from "./mcp-http.schema-C_AbOzAG.js";
import { a as isCurrentPlacementTurnClaim } from "./placement-record-BOfTzlFO.js";
import { g as waitForMcpLoopbackToolCallCaptureIdle, r as clearMcpLoopbackToolCallCapture, t as beginMcpLoopbackToolCallCapture } from "./mcp-http.loopback-runtime-DuLBVvrT.js";
import { t as attachCliMessagingDeliveryEvidence } from "./delivery-evidence-MzTu8jZB.js";
import { isDeepStrictEqual } from "node:util";
import crypto, { randomUUID } from "node:crypto";
import { parse } from "semver";
import { Value } from "typebox/value";
import { stripSystemPromptCacheBoundary } from "@openclaw/ai/internal/shared";
//#region src/gateway/node-claude-skill-runtime.ts
/** Captures the real session assignment, connection, and admitted owner before any file work. */
async function prepareNodeClaudeSkillRuntime(context, signal) {
const run = context.params;
if (run.controlOperation || !run.skillsSnapshot?.librarySelections?.length && !context.nodeSkillWorkshop) return;
const gateway = getPluginRuntimeGatewayRequestScope()?.context;
const target = context.executionTarget;
const node = target.kind === "node" ? gateway?.nodeRegistry.get(target.placement.nodeId) : void 0;
if (!gateway || !node || !node.caps.includes("claude-cli-skills-v1")) throw new Error("Paired node needs claude-cli-skills-v1. Upgrade OpenClaw on the paired node, restart its node host, and retry this turn.");
const assertRun = resolveAdmittedRunActiveAssertion(run.admittedRunContext, signal);
if (!assertRun || !run.sessionKey) throw new Error("Paired-node skills require a live admitted session. Send a fresh message.");
const sessionScope = {
sessionKey: run.sessionKey,
agentId: run.agentId,
storePath: run.storePath,
hydrateSkillPromptRefs: false,
readConsistency: "latest"
};
const session = loadSessionEntryReadOnly(sessionScope);
const connectionId = node.connId;
const pairingGeneration = node.pairingGeneration;
const caller = getGatewayToolCallerIdentity();
const placements = gateway.workerSessionPlacementService;
const readPlacement = () => placements?.getMany([run.sessionId]).get(run.sessionId);
const placement = readPlacement();
const persistedClaim = placement?.turnClaim;
const claim = persistedClaim?.owner === "local" ? {
sessionId: run.sessionId,
claimId: persistedClaim.claimId,
runId: persistedClaim.runId,
placementGeneration: persistedClaim.generation,
owner: {
kind: "local",
...placement?.environmentId && placement.activeOwnerEpoch !== null ? {
environmentId: placement.environmentId,
ownerEpoch: placement.activeOwnerEpoch
} : {}
}
} : void 0;
const controller = new AbortController();
const owner = getAdmittedRunDelegatedAuthority(run.admittedRunContext);
const stop = registerAgentRunDelegatedAuthorityClosedHandler((closed) => {
if (closed === owner) controller.abort(/* @__PURE__ */ new Error("Paired-node skill run authority closed."));
});
const stopClaim = claim && placement ? placements?.registerTurnClaimClosedHandler?.((ended) => {
if (ended.sessionId === run.sessionId && isCurrentPlacementTurnClaim(placement, ended)) controller.abort(/* @__PURE__ */ new Error("Paired-node skill placement claim closed."));
}) : void 0;
const combinedSignal = AbortSignal.any([signal, controller.signal]);
let closed = false;
const close = () => {
closed = true;
stop();
stopClaim?.();
controller.abort();
};
const assertCurrent = () => {
assertRun();
combinedSignal.throwIfAborted();
const current = loadSessionEntryReadOnly(sessionScope);
const currentPlacement = readPlacement();
if (closed || gateway.nodeRegistry.get(node.nodeId) !== node || node.connId !== connectionId || node.pairingGeneration !== pairingGeneration || node.client.socket.readyState !== 1 || !node.caps.includes("claude-cli-skills-v1") || !isNodeCommandAllowed({
command: "agent.cli.claude.run.v1",
declaredCommands: node.commands,
allowlist: resolveNodeCommandAllowlist(gateway.getRuntimeConfig(), {
...node,
approvedCommands: node.commands
})
}).ok || !session || session.sessionId !== run.sessionId || (run.expectedLifecycleRevision ?? run.sessionEntry?.lifecycleRevision) !== void 0 && session.lifecycleRevision !== (run.expectedLifecycleRevision ?? run.sessionEntry?.lifecycleRevision) || current?.sessionId !== session.sessionId || current.lifecycleRevision !== session.lifecycleRevision || current.execHost !== "node" || current.execNode?.trim() !== node.nodeId || (current.execCwd?.trim() || void 0) !== (target.kind === "node" ? target.placement.cwd : void 0) || caller?.receiptAuthority && caller.receiptAuthority() === false || currentPlacement?.generation !== placement?.generation || currentPlacement?.environmentId !== placement?.environmentId || currentPlacement?.activeOwnerEpoch !== placement?.activeOwnerEpoch || placement && (!claim || !stopClaim || claim.runId !== run.runId || !currentPlacement || !isCurrentPlacementTurnClaim(currentPlacement, claim))) throw new Error("Paired-node skill assignment or run authority changed. Retry from a fresh turn.");
};
try {
assertCurrent();
const resources = run.skillsSnapshot?.librarySelections?.length ? await prepareSkillResourceDelivery(run.skillsSnapshot, assertCurrent) : void 0;
assertCurrent();
const workshop = context.nodeSkillWorkshop;
const descriptor = workshop ? buildMcpToolSchema([workshop])[0] : void 0;
const init = {
type: "init",
...resources ? { resources } : {},
...descriptor ? { workshop: {
description: descriptor.description ?? "",
inputSchema: descriptor.inputSchema
} } : {}
};
encodeNodeClaudeSkillMessage(init, NODE_CLAUDE_SKILLS_MESSAGE_BYTES);
return {
node,
run,
init,
workshop,
assertCurrent,
signal: combinedSignal,
close
};
} catch (error) {
close();
throw error;
}
}
/** One invocation owns resource delivery, stdout, and host-only Workshop callbacks. */
async function invokeNodeClaudeSkillRuntime(params) {
const { runtime, registry } = params;
runtime.assertCurrent();
const controller = new AbortController();
const signal = AbortSignal.any([runtime.signal, controller.signal]);
let invokeId;
let open = true;
const assertCurrent = () => {
runtime.assertCurrent();
signal.throwIfAborted();
if (!open || !invokeId || !registry.isInvokeCurrent(invokeId, runtime.node.nodeId, runtime.node.connId)) throw new Error("Paired-node Workshop invocation is no longer active.");
};
const identity = createAdmittedGatewayToolCallerIdentity({
admittedRunContext: runtime.run.admittedRunContext,
agentId: runtime.run.agentId,
sessionKey: runtime.run.sessionKey,
receiptAuthority: assertCurrent
});
const handlers = createPluginToolsMcpHandlers(runtime.workshop ? [runtime.workshop] : []);
const calls = /* @__PURE__ */ new Set();
const callbacks = /* @__PURE__ */ new Set();
const endpoint = createNodeDuplexEndpoint({
requireReady: true,
maxMessageBytes: NODE_CLAUDE_SKILLS_MESSAGE_BYTES,
sendFrame(frame) {
assertCurrent();
registry.sendInvokeInput(invokeId, JSON.parse(frame));
},
onReady() {
assertCurrent();
endpoint.send(encodeNodeClaudeSkillMessage(runtime.init, NODE_CLAUDE_SKILLS_MESSAGE_BYTES)).catch((error) => controller.abort(error));
},
onError: (error) => controller.abort(error)
});
const receive = async (bytes) => {
assertCurrent();
if (bytes.byteLength > 67108864) throw new Error("Paired-node Workshop request exceeds its limit.");
const message = JSON.parse(Buffer.from(bytes).toString("utf8"));
if (!Value.Check(NodeClaudeSkillUpstreamSchema, message)) throw new Error("Invalid paired-node skill message.");
if (message.type === "stdout") {
params.onProgress(message.text);
return;
}
if (!runtime.workshop || !identity || calls.has(message.id) || calls.size >= 64) throw new Error("Paired-node Workshop callback is unavailable or already consumed. Send a fresh message.");
calls.add(message.id);
const result = await withGatewayToolCallerIdentity(identity, () => handlers.callTool({
name: "skill_workshop",
arguments: message.arguments
}, signal));
assertCurrent();
await endpoint.send(encodeNodeClaudeSkillMessage({
type: "result",
id: message.id,
result
}, NODE_CLAUDE_WORKSHOP_RESULT_BYTES));
};
endpoint.onMessage((bytes) => {
const callback = receive(bytes).finally(() => callbacks.delete(callback));
callbacks.add(callback);
return callback;
});
const abort = () => endpoint.close();
signal.addEventListener("abort", abort, { once: true });
try {
return await registry.invoke({
...params.invocation,
signal,
expectedConnId: runtime.node.connId,
expectedPairingGeneration: runtime.node.pairingGeneration,
isDispatchAuthorized: () => {
runtime.assertCurrent();
return !signal.aborted && params.invocation.isDispatchAuthorized?.() !== false;
},
onDispatchReady: (id) => {
invokeId = id;
},
onProgress: (chunk) => {
assertCurrent();
endpoint.receive(chunk);
}
});
} finally {
open = false;
controller.abort();
signal.removeEventListener("abort", abort);
endpoint.close();
await Promise.allSettled(callbacks);
}
}
//#endregion
//#region src/gateway/node-agent-cli-runtime.ts
/** In-process Gateway seam for streaming a Claude CLI turn from a paired node. */
async function invokeNodeClaudeCliRun(params) {
const context = getPluginRuntimeGatewayRequestScope()?.context;
if (!context) return {
ok: false,
error: {
code: "UNAVAILABLE",
message: "Gateway node runtime unavailable"
}
};
const node = context.nodeRegistry.get(params.nodeId);
if (!node || !node.commands.includes("agent.cli.claude.run.v1")) return {
ok: false,
error: {
code: "UNAVAILABLE",
message: "paired node does not advertise Claude CLI agent runs"
}
};
const allowlist = resolveNodeCommandAllowlist(context.getRuntimeConfig(), {
...node,
approvedCommands: node.commands
});
const allowed = isNodeCommandAllowed({
command: NODE_AGENT_CLI_CLAUDE_RUN_COMMAND,
declaredCommands: node.commands,
allowlist
});
if (!allowed.ok) return {
ok: false,
error: {
code: "PERMISSION_DENIED",
message: `paired-node Claude CLI agent runs are blocked by node command policy (${allowed.reason})`
}
};
params.skillRuntime?.assertCurrent();
const invocation = {
nodeId: params.nodeId,
expectedConnId: node.connId,
...node.pairingGeneration ? { expectedPairingGeneration: node.pairingGeneration } : {},
command: NODE_AGENT_CLI_CLAUDE_RUN_COMMAND,
params: {
argv: params.argv,
stdin: params.stdin,
...params.cwd ? { cwd: params.cwd } : {},
...params.env ? { env: params.env } : {},
...params.clearEnv ? { clearEnv: params.clearEnv } : {},
...params.systemPrompt !== void 0 ? { systemPrompt: params.systemPrompt } : {},
...params.agentId ? { agentId: params.agentId } : {},
...params.sessionKey ? { sessionKey: params.sessionKey } : {},
...params.approvalDecision ? { approvalDecision: params.approvalDecision } : {},
...params.systemRunPlan ? { systemRunPlan: params.systemRunPlan } : {},
idleTimeoutMs: params.idleTimeoutMs,
timeoutMs: params.timeoutMs,
...params.skillRuntime ? { skillRuntime: true } : {}
},
timeoutMs: params.timeoutMs,
idleTimeoutMs: params.idleTimeoutMs,
idempotencyKey: randomUUID(),
onProgress: params.onProgress,
...params.signal ? { signal: params.signal } : {},
isDispatchAuthorized: () => {
params.assertCurrent?.();
return true;
}
};
return params.skillRuntime ? await invokeNodeClaudeSkillRuntime({
registry: context.nodeRegistry,
invocation,
runtime: params.skillRuntime,
onProgress: params.onProgress
}) : await context.nodeRegistry.invoke(invocation);
}
//#endregion
//#region src/agents/cli-runner/execute-deps.ts
const executeDeps = {
getProcessSupervisor,
enqueueSystemEvent,
requestHeartbeat,
writeCliSystemPromptFile,
invokeNodeClaudeCliRun,
registerExecApprovalRequestForHostOrThrow,
resolveRegisteredExecApprovalDecision
};
//#endregion
//#region src/agents/cli-runner/execute-events.ts
function resolveCliToolSource(name, kind) {
return kind === "mcp_tool_use" || name.startsWith("mcp__") ? "mcp" : "core";
}
function createCliEventHandlers(params) {
const context = params.context;
const runParams = context.params;
const emitLiveEvents = runParams.executionMode !== "side-question";
let observedCliActivity = false;
let signaledToolExecutionStarted = false;
let signaledAssistantOutputStarted = false;
let commentaryCounter = 0;
const toolSummaryById = /* @__PURE__ */ new Map();
const toolArgsByCallId = /* @__PURE__ */ new Map();
const toolSummaryNames = [];
const toolSummaryNameSet = /* @__PURE__ */ new Set();
const activeParsedTools = /* @__PURE__ */ new Map();
const rememberToolName = (name) => {
if (!name || toolSummaryNameSet.has(name)) return;
toolSummaryNameSet.add(name);
toolSummaryNames.push(name);
};
const recordToolStart = (event) => {
if (event.args && Object.keys(event.args).length > 0) toolArgsByCallId.set(event.toolCallId, event.args);
const current = toolSummaryById.get(event.toolCallId);
if (!current) toolSummaryById.set(event.toolCallId, {
name: event.name,
failed: false
});
else if (!current.name && event.name) current.name = event.name;
rememberToolName(event.name);
};
const recordToolResult = (event) => {
const current = toolSummaryById.get(event.toolCallId);
if (current) {
current.failed ||= event.isError;
if (!current.name && event.name) current.name = event.name;
} else toolSummaryById.set(event.toolCallId, {
name: event.name,
failed: event.isError
});
rememberToolName(event.name);
};
const getToolSummary = () => ({
calls: toolSummaryById.size,
tools: toolSummaryNames.slice(),
failures: Array.from(toolSummaryById.values()).filter((entry) => entry.failed).length
});
const emitCliToolUseStart = (event) => {
observedCliActivity = true;
recordToolStart(event);
if (!signaledToolExecutionStarted) {
signaledToolExecutionStarted = true;
runParams.onExecutionPhase?.({
phase: "tool_execution_started",
provider: runParams.provider,
model: context.modelId,
backend: context.backendResolved.id
});
}
params.toolTracking.handleCliToolUseStart(event);
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "tool",
data: {
phase: "start",
name: event.name,
toolCallId: event.toolCallId,
args: sanitizeToolArgs(event.args)
}
});
};
const emitCliToolResult = (event) => {
observedCliActivity = true;
recordToolResult(event);
params.toolTracking.handleCliToolResult(event);
if (emitLiveEvents) {
const resultContentSource = context.resultContentSourceByToolName?.get(stripOpenClawMcpToolPrefix(event.name));
const startedArgs = toolArgsByCallId.get(event.toolCallId);
toolArgsByCallId.delete(event.toolCallId);
emitAgentEvent({
runId: runParams.runId,
stream: "tool",
data: {
phase: "result",
name: event.name,
toolCallId: event.toolCallId,
isError: event.isError,
result: sanitizeToolResult(event.result),
...startedArgs ? { args: sanitizeToolArgs(startedArgs) } : {},
...resultContentSource ? { resultContentSource } : {}
}
});
}
};
const emitCliDisplayToolUseStart = (event) => {
observedCliActivity = true;
recordToolStart(event);
if (!signaledToolExecutionStarted) {
signaledToolExecutionStarted = true;
runParams.onExecutionPhase?.({
phase: "tool_execution_started",
provider: runParams.provider,
model: context.modelId,
backend: context.backendResolved.id
});
}
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "tool",
data: {
phase: "start",
name: event.name,
toolCallId: event.toolCallId,
args: sanitizeToolArgs(event.args)
}
});
};
const emitCliDisplayToolResult = (event) => {
observedCliActivity = true;
recordToolResult(event);
if (emitLiveEvents) {
toolArgsByCallId.delete(event.toolCallId);
emitAgentEvent({
runId: runParams.runId,
stream: "tool",
data: {
phase: "result",
name: event.name,
toolCallId: event.toolCallId,
isError: event.isError,
result: sanitizeToolResult(event.result)
}
});
}
};
const emitParsedToolUseStart = (event) => {
const startedAt = Date.now();
activeParsedTools.set(event.toolCallId, {
startedAt,
toolName: event.name,
kind: event.kind
});
emitTrustedDiagnosticEvent({
type: "tool.execution.started",
runId: runParams.runId,
sessionId: runParams.sessionId,
...runParams.sessionKey ? { sessionKey: runParams.sessionKey } : {},
...runParams.agentId ? { agentId: runParams.agentId } : {},
toolName: event.name,
toolSource: resolveCliToolSource(event.name, event.kind),
toolOwner: "cli-runner",
toolCallId: event.toolCallId
});
emitCliToolUseStart(event);
};
const emitParsedToolTerminal = (event) => {
const activeTool = activeParsedTools.get(event.toolCallId);
activeParsedTools.delete(event.toolCallId);
const trustedOutcome = params.toolTracking.resolveCliLoopbackTerminalOutcome(event.toolCallId);
const toolName = activeTool?.toolName ?? event.name;
const now = Date.now();
const trustedTerminalReason = trustedOutcome && trustedOutcome.outcome !== "blocked" && trustedOutcome.outcome !== "completed" && trustedOutcome.outcome !== "unknown" ? trustedOutcome.outcome : void 0;
const runState = params.getRunState();
const terminalReason = trustedTerminalReason ?? resolveCliToolTerminalReason({
error: event.incomplete ? runState.error : void 0,
abortSignal: runParams.abortSignal
});
const useEnclosingTerminalReason = event.incomplete && runState.failed && activeTool !== void 0 && activeTool.kind !== "server_tool_use";
const diagnosticBase = {
runId: runParams.runId,
sessionId: runParams.sessionId,
...runParams.sessionKey ? { sessionKey: runParams.sessionKey } : {},
...runParams.agentId ? { agentId: runParams.agentId } : {},
toolName,
toolSource: resolveCliToolSource(toolName, activeTool?.kind),
toolOwner: "cli-runner",
toolCallId: event.toolCallId,
durationMs: Math.max(0, now - (activeTool?.startedAt ?? now))
};
if (trustedOutcome?.outcome === "unknown" && !useEnclosingTerminalReason) {
emitTrustedDiagnosticEvent({
type: "tool.execution.error",
...diagnosticBase,
errorCategory: "cli_tool_ambiguous",
errorCode: "tool_outcome_unknown"
});
return;
}
if (event.incomplete && activeTool?.kind === "server_tool_use" && !trustedOutcome) {
emitTrustedDiagnosticEvent({
type: "tool.execution.error",
...diagnosticBase,
errorCategory: "cli_tool_ambiguous",
errorCode: "tool_outcome_unknown"
});
return;
}
const trustedFailure = trustedOutcome !== void 0 && trustedOutcome.outcome !== "completed";
emitTrustedDiagnosticEvent(trustedOutcome?.outcome === "blocked" ? {
type: "tool.execution.blocked",
...diagnosticBase,
deniedReason: trustedOutcome.deniedReason,
reason: "blocked by before-tool policy"
} : trustedFailure || !trustedOutcome && event.isError ? {
type: "tool.execution.error",
...diagnosticBase,
errorCategory: terminalReason === "cancelled" ? "aborted" : event.incomplete && (!trustedOutcome || useEnclosingTerminalReason) ? "cli_tool_incomplete" : "cli_tool",
terminalReason
} : {
type: "tool.execution.completed",
...diagnosticBase
});
};
const emitParsedToolResult = (event) => {
emitParsedToolTerminal(event);
emitCliToolResult(event);
};
const emitCliCompaction = (event) => {
observedCliActivity = true;
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "compaction",
data: {
...event,
backend: context.backendResolved.id
}
});
};
const finalizeParsedTools = () => {
for (const [toolCallId, activeTool] of Array.from(activeParsedTools)) emitParsedToolTerminal({
toolCallId,
name: activeTool.toolName,
isError: true,
incomplete: true
});
};
const emitCliCommentaryText = (text) => {
if (!emitLiveEvents) return;
commentaryCounter += 1;
emitAgentEvent({
runId: runParams.runId,
stream: "item",
data: {
kind: "preamble",
itemId: `commentary-${runParams.runId}-${commentaryCounter}`,
phase: "end",
title: "commentary",
status: "running",
progressText: applyPluginTextReplacements(text, context.backendResolved.textTransforms?.output)
}
});
};
const emitCliAssistantDelta = ({ text, delta }) => {
if (text || delta) {
observedCliActivity = true;
if (!signaledAssistantOutputStarted) {
signaledAssistantOutputStarted = true;
runParams.onExecutionPhase?.({
phase: "assistant_output_started",
provider: runParams.provider,
model: context.modelId,
backend: context.backendResolved.id
});
}
}
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "assistant",
data: {
text: applyPluginTextReplacements(text, context.backendResolved.textTransforms?.output),
delta: applyPluginTextReplacements(delta, context.backendResolved.textTransforms?.output)
}
});
};
const emitCliThinkingDelta = ({ text, delta, isReasoningSnapshot }) => {
if (text || delta) observedCliActivity = true;
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "thinking",
data: {
text,
delta,
...isReasoningSnapshot ? { isReasoningSnapshot } : {}
}
});
};
const emitCliThinkingProgress = ({ progressTokens }) => {
observedCliActivity = true;
if (emitLiveEvents) emitAgentEvent({
runId: runParams.runId,
stream: "thinking",
data: { progressTokens }
});
};
return {
emitLiveEvents,
emitCliToolUseStart,
emitCliToolResult,
emitCliDisplayToolUseStart,
emitCliDisplayToolResult,
emitParsedToolUseStart,
emitParsedToolResult,
emitCliCompaction,
finalizeParsedTools,
emitCliCommentaryText,
emitCliAssistantDelta,
emitCliThinkingDelta,
emitCliThinkingProgress,
hasObservedCliActivity: () => observedCliActivity,
activeParsedToolCount: () => activeParsedTools.size,
getToolSummary
};
}
//#endregion
//#region src/agents/cli-runner/execute-logging.ts
function buildCliLogArgs(params) {
const logArgs = [];
for (let i = 0; i < params.args.length; i += 1) {
const arg = params.args[i] ?? "";
if (arg === params.systemPromptArg) {
const systemPromptValue = params.args[i + 1] ?? "";
logArgs.push(arg, `<systemPrompt:${systemPromptValue.length} chars>`);
i += 1;
continue;
}
if (arg === params.modelArg) {
logArgs.push(arg, params.args[i + 1] ?? "");
i += 1;
continue;
}
if (arg === params.imageArg) {
logArgs.push(arg, "<image>");
i += 1;
continue;
}
logArgs.push(arg);
}
if (params.argsPrompt) {
const promptIndex = logArgs.indexOf(params.argsPrompt);
if (promptIndex >= 0) logArgs[promptIndex] = `<prompt:${params.argsPrompt.length} chars>`;
}
return logArgs;
}
const CLI_ENV_AUTH_LOG_KEYS = [
"AI_GATEWAY_API_KEY",
"ANTHROPIC_API_KEY",
"ANTHROPIC_API_KEY_OLD",
"ANTHROPIC_API_TOKEN",
"ANTHROPIC_AUTH_TOKEN",
"ANTHROPIC_BASE_URL",
"ANTHROPIC_CUSTOM_HEADERS",
"ANTHROPIC_OAUTH_TOKEN",
"ANTHROPIC_UNIX_SOCKET",
"AZURE_OPENAI_API_KEY",
"CLAUDE_CODE_OAUTH_TOKEN",
"CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST",
"OPENAI_API_KEY",
"OPENAI_STEIPETE_API_KEY",
"OPENROUTER_API_KEY"
];
const CLI_ENV_RUNTIME_LOG_KEYS = ["GEMINI_CLI_HOME", "GEMINI_CLI_SYSTEM_SETTINGS_PATH"];
const CLAUDE_SELECTED_AUTH_ENV_KEYS = /* @__PURE__ */ new Set(["ANTHROPIC_API_KEY", "CLAUDE_CODE_OAUTH_TOKEN"]);
const NODE_CLAUDE_FORWARD_ENV_KEYS = /* @__PURE__ */ new Set(["CLAUDE_CODE_AUTO_COMPACT_WINDOW", "CLAUDE_CODE_DISABLE_1M_CONTEXT"]);
function resolveNodeClaudeAuthEnv(context) {
const secretInput = context.preparedBackend.secretInput;
if (!secretInput) return {};
const descriptorEnv = context.preparedBackend.env ?? {};
const requestEnv = Object.hasOwn(descriptorEnv, "CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR") ? "CLAUDE_CODE_OAUTH_TOKEN" : Object.hasOwn(descriptorEnv, "CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR") ? "ANTHROPIC_API_KEY" : void 0;
if (!requestEnv) return {};
const data = secretInput.createData();
try {
return { [requestEnv]: data.toString("utf8") };
} finally {
data.fill(0);
}
}
const CLI_BACKEND_PRESERVE_ENV = "OPENCLAW_LIVE_CLI_BACKEND_PRESERVE_ENV";
function parseCliBackendPreserveEnv(raw) {
const trimmed = raw?.trim();
if (!trimmed) return /* @__PURE__ */ new Set();
if (trimmed.startsWith("[")) try {
const parsed = JSON.parse(trimmed);
return new Set(Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : []);
} catch {
return /* @__PURE__ */ new Set();
}
return new Set(trimmed.split(/[,\s]+/).map((entry) => entry.trim()).filter((entry) => entry.length > 0));
}
function listPresentCliEnvKeys(env, keys) {
return keys.filter((key) => {
const value = env[key];
return typeof value === "string" && value.length > 0;
});
}
function formatCliEnvKeyList(keys) {
return keys.length > 0 ? keys.join(",") : "none";
}
function buildCliEnvMcpLog(childEnv) {
return [`token=${childEnv.OPENCLAW_MCP_TOKEN ? "set" : "missing"}`, `capture=${childEnv.OPENCLAW_MCP_CLI_CAPTURE_KEY ? "set" : "missing"}`].join(" ");
}
function fingerprintCliSessionId(sessionId) {
const trimmed = sessionId?.trim();
if (!trimmed) return "none";
return crypto.createHash("sha256").update(trimmed).digest("hex").slice(0, 12);
}
function formatCliSessionReuseLogState(reusableSession) {
switch (reusableSession.mode) {
case "reuse": return "reusable";
case "reuse-with-drift": return `reusable-drift:${reusableSession.drift.reasons.join(",")}`;
case "invalidate": return `invalidated:${reusableSession.invalidatedReason}`;
case "none": return "none";
}
return reusableSession;
}
/** Builds the compact execution summary logged before a CLI backend run. */
function buildCliExecLogLine(params) {
return [
`cli exec: provider=${params.provider}`,
`model=${params.model}`,
`promptChars=${params.promptChars}`,
`trigger=${params.trigger ?? "unknown"}`,
`useResume=${params.useResume ? "true" : "false"}`,
`session=${params.cliSessionId ? "present" : "none"}`,
`resumeSession=${params.useResume ? fingerprintCliSessionId(params.resolvedSessionId) : "none"}`,
`reuse=${formatCliSessionReuseLogState(params.reusableSession)}`,
`historyPrompt=${params.hasHistoryPrompt ? "present" : "none"}`
].join(" ");
}
/** Summarizes auth-related env keys preserved or cleared for a CLI child process. */
function buildCliEnvAuthLog(childEnv) {
const hostKeys = listPresentCliEnvKeys(process.env, CLI_ENV_AUTH_LOG_KEYS);
const childKeys = listPresentCliEnvKeys(childEnv, CLI_ENV_AUTH_LOG_KEYS);
const childKeySet = new Set(childKeys);
const clearedKeys = hostKeys.filter((key) => !childKeySet.has(key));
const runtimeHostKeys = listPresentCliEnvKeys(process.env, CLI_ENV_RUNTIME_LOG_KEYS);
const runtimeChildKeys = listPresentCliEnvKeys(childEnv, CLI_ENV_RUNTIME_LOG_KEYS);
const runtimeChildKeySet = new Set(runtimeChildKeys);
const runtimeClearedKeys = runtimeHostKeys.filter((key) => !runtimeChildKeySet.has(key));
return [
`host=${formatCliEnvKeyList(hostKeys)}`,
`child=${formatCliEnvKeyList(childKeys)}`,
`cleared=${formatCliEnvKeyList(clearedKeys)}`,
`runtimeHost=${formatCliEnvKeyList(runtimeHostKeys)}`,
`runtimeChild=${formatCliEnvKeyList(runtimeChildKeys)}`,
`runtimeCleared=${formatCliEnvKeyList(runtimeClearedKeys)}`
].join(" ");
}
function logCliInvocation(params) {
const logArgs = buildCliLogArgs(params);
params.log(`cli argv: ${params.command} ${logArgs.join(" ")}`);
params.log(`cli env auth: ${buildCliEnvAuthLog(params.env)}`);
if (params.env.OPENCLAW_MCP_TOKEN) params.log(`cli env mcp: ${buildCliEnvMcpLog(params.env)}`);
}
//#endregion
//#region src/agents/cli-runner/execute-node-claude.ts
const NODE_CLI_MAX_TIMEOUT_MS = 864e5;
const NODE_CLI_MAX_IDLE_TIMEOUT_MS = 18e5;
const NODE_CLI_OMIT_BARE_ARGS = /* @__PURE__ */ new Set(["--strict-mcp-config", "--exclude-dynamic-system-prompt-sections"]);
const NODE_CLI_OMIT_VALUE_ARGS = /* @__PURE__ */ new Set([
"--permission-mode",
"--plugin-dir",
"--plugin-dir-no-mcp"
]);
const NODE_CLI_OMIT_VARIADIC_ARGS = /* @__PURE__ */ new Set([
"--mcp-config",
"--allowedTools",
"--allowed-tools"
]);
/** Remove Gateway-local file, plugin, MCP, and allow-list arguments. */
function stripGatewayLocalClaudeArgs(args) {
const result = [];
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
const equalsIndex = arg.indexOf("=");
const name = equalsIndex > 0 ? arg.slice(0, equalsIndex) : arg;
if (NODE_CLI_OMIT_BARE_ARGS.has(name)) continue;
if (NODE_CLI_OMIT_VALUE_ARGS.has(name)) {
if (equalsIndex < 0) index += 1;
continue;
}
if (NODE_CLI_OMIT_VARIADIC_ARGS.has(name)) {
if (equalsIndex < 0) while (typeof args[index + 1] === "string" && !args[index + 1]?.startsWith("-")) index += 1;
continue;
}
result.push(arg);
}
return result;
}
function parseNodeClaudeResultPayload(result) {
const value = result.payloadJSON ? JSON.parse(result.payloadJSON) : result.payload;
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("paired node returned an invalid Claude CLI result");
const record = value;
if (!Number.isInteger(record.exitCode) || typeof record.stderrTail !== "string" || typeof record.truncated !== "boolean" || record.timeoutKind !== void 0 && record.timeoutKind !== "hard" && record.timeoutKind !== "idle") throw new Error("paired node returned an invalid Claude CLI result");
return {
exitCode: record.exitCode,
stderrTail: record.stderrTail,
truncated: record.truncated,
...record.timeoutKind ? { timeoutKind: record.timeoutKind } : {}
};
}
function parseNodeClaudeApprovalRequired(result) {
if (!result.ok) return null;
const value = result.payloadJSON ? JSON.parse(result.payloadJSON) : result.payload;
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
const record = value;
if (record.approvalRequired !== true || !record.systemRunPlan || typeof record.systemRunPlan !== "object" || Array.isArray(record.systemRunPlan) || record.security !== "deny" && record.security !== "allowlist" && record.security !== "full" || record.ask !== "off" && record.ask !== "on-miss" && record.ask !== "always") return null;
return {
systemRunPlan: record.systemRunPlan,
security: record.security,
ask: record.ask
};
}
function createCliAbortError() {
return createAbortError("CLI run aborted");
}
async function waitForNodeOperation(params) {
if (!params.signal) return await params.operation;
if (params.signal.aborted) throw createCliAbortError();
return await new Promise((resolve, reject) => {
const onAbort = () => reject(createCliAbortError());
params.signal?.addEventListener("abort", onAbort, { once: true });
params.operation.then(resolve, reject).finally(() => {
params.signal?.removeEventListener("abort", onAbort);
});
});
}
async function executeNodeClaudeRun(params) {
const contextParams = params.context.params;
const startedAt = Date.now();
const hardTimeoutMs = Math.min(contextParams.timeoutMs, NODE_CLI_MAX_TIMEOUT_MS);
const hardDeadlineAt = startedAt + hardTimeoutMs;
const nodeAbortController = new AbortController();
const nodeRunAbortSignal = nodeAbortController.signal;
const assertCurrent = createCliRunCurrentAssertion(contextParams, nodeRunAbortSignal);
let hardDeadlineReached = false;
const hardDeadlineTimer = setTimeout(() => {
hardDeadlineReached = true;
nodeAbortController.abort();
}, hardTimeoutMs);
const abortNodeRun = () => nodeAbortController.abort();
contextParams.abortSignal?.addEventListener("abort", abortNodeRun, { once: true });
if (contextParams.abortSignal?.aborted) abortNodeRun();
const replyBackendHandle = contextParams.replyOperation ? {
kind: "cli",
runId: contextParams.runId,
toolAuthorityFingerprint: contextParams.toolAuthorityFingerprint,
cancel: abortNodeRun
} : void 0;
if (replyBackendHandle) contextParams.replyOperation?.attachBackend(replyBackendHandle);
let nodeResult;
let skillRuntime;
try {
assertCurrent();
skillRuntime = await prepareNodeClaudeSkillRuntime(params.context, nodeRunAbortSignal);
const invokeNode = async (approval) => {
const remainingTimeoutMs = hardDeadlineAt - Date.now();
if (remainingTimeoutMs <= 0) {
hardDeadlineReached = true;
nodeAbortController.abort();
return {
ok: false,
error: {
code: "TIMEOUT",
message: "paired-node Claude CLI invocation exceeded its hard timeout"
}
};
}
assertCurrent();
return await params.deps.invokeNodeClaudeCliRun({
assertCurrent,
nodeId: params.nodePlacement.nodeId,
argv: params.executionArgs,
stdin: params.stdinPayload,
...params.nodePlacement.cwd ? { cwd: params.nodePlacement.cwd } : {},
...params.nodeSystemPrompt !== void 0 ? { systemPrompt: params.nodeSystemPrompt } : {},
...params.nodeEnv ? { env: params.nodeEnv } : {},
...params.nodeClearEnv ? { clearEnv: params.nodeClearEnv } : {},
...contextParams.agentId ? { agentId: contextParams.agentId } : {},
...contextParams.sessionKey ? { sessionKey: contextParams.sessionKey } : {},
...approval ? {
approvalDecision: approval.decision,
systemRunPlan: approval.plan
} : {},
timeoutMs: remainingTimeoutMs,
idleTimeoutMs: Math.max(1e3, Math.min(params.noOutputTimeoutMs, NODE_CLI_MAX_IDLE_TIMEOUT_MS)),
onProgress: params.consumeStdout,
signal: skillRuntime?.signal ?? nodeAbortController.signal,
...skillRuntime ? { skillRuntime } : {}
});
};
nodeResult = await invokeNode();
const approval = parseNodeClaudeApprovalRequired(nodeResult);
if (approval) {
skillRuntime?.assertCurrent();
const approvalId = crypto.randomUUID();
const registration = await waitForNodeOperation({
operation: params.deps.registerExecApprovalRequestForHostOrThrow({
approvalId,
command: approval.systemRunPlan.commandText,
commandArgv: approval.systemRunPlan.argv,
systemRunPlan: approval.systemRunPlan,
workdir: approval.systemRunPlan.cwd ?? void 0,
host: "node",
nodeId: params.nodePlacement.nodeId,
security: approval.security,
ask: approval.ask,
unavailableDecisions: ["allow-always"],
agentId: contextParams.agentId,
sessionKey: contextParams.sessionKey,
...contextParams.approvalReviewerDeviceId ? { approvalReviewerDeviceIds: [contextParams.approvalReviewerDeviceId] } : {}
}),
signal: skillRuntime?.signal ?? nodeAbortController.signal
});
const decision = await waitForNodeOperation({
operation: params.deps.resolveRegisteredExecApprovalDecision({
approvalId: registration.id,
preResolvedDecision: registration.finalDecision
}),
signal: skillRuntime?.signal ?? nodeAbortController.signal
});
if (decision === "allow-once" || decision === "allow-always") nodeResult = await invokeNode({
decision,
plan: approval.systemRunPlan
});
else nodeResult = {
ok: false,
error: {
code: "PERMISSION_DENIED",
message: "paired-node Claude CLI agent run was not approved"
}
};
}
} catch (error) {
if (!hardDeadlineReached) throw error;
nodeResult = {
ok: false,
error: {
code: "TIMEOUT",
message: "paired-node Claude CLI invocation exceeded its hard timeout"
}
};
} finally {
if (skillRuntime?.signal.aborted) nodeAbortController.abort();
skillRuntime?.close();
clearTimeout(hardDeadlineTimer);
if (replyBackendHandle) contextParams.replyOperation?.detachBackend(replyBackendHandle);
contextParams.abortSignal?.removeEventListener("abort", abortNodeRun);
}
if (hardDeadlineReached) nodeResult = {
ok: false,
error: {
code: "TIMEOUT",
message: "paired-node Claude CLI invocation exceeded its hard timeout"
}
};
if (!nodeResult.ok) {
const code = nodeResult.error?.code;
const timedOut = code === "TIMEOUT" || code === "IDLE_TIMEOUT";
const result = {
reason: code === "IDLE_TIMEOUT" ? "no-output-timeout" : code === "TIMEOUT" ? "overall-timeout" : code === "ABORTED" ? "manual-cancel" : "exit",
exitCode: timedOut || code === "ABORTED" ? null : 1,
exitSignal: null,
durationMs: Date.now() - startedAt,
stdout: "",
stderr: nodeResult.error?.message ?? "paired-node Claude CLI invocation failed",
timedOut,
noOutputTimedOut: code === "IDLE_TIMEOUT"
};
params.consumeStderr(result.stderr);
return {
result,
nodeRunAbortSignal,
nodeRunTruncated: false
};
}
const payload = parseNodeClaudeResultPayload(nodeResult);
if (payload.stderrTail) params.consumeStderr(payload.stderrTail);
return {
result: {
reason: payload.timeoutKind === "idle" ? "no-output-timeout" : payload.timeoutKind === "hard" ? "overall-timeout" : "exit",
exitCode: payload.timeoutKind ? null : payload.exitCode,
exitSignal: null,
durationMs: Date.now() - startedAt,
stdout: "",
stderr: payload.stderrTail,
timedOut: payload.timeoutKind !== void 0,
noOutputTimedOut: payload.timeoutKind === "idle"
},
nodeRunAbortSignal,
nodeRunTruncated: payload.truncated
};
}
//#endregion
//#region src/agents/cli-runner/execute-output-buffer.ts
const CLI_RUNNER_OUTPUT_TAIL_BYTES = 65536;
function appendCliOutputTail(tail, chunk) {
return truncateUtf8Suffix(`${tail}${chunk}`, CLI_RUNNER_OUTPUT_TAIL_BYTES);
}
//#endregion
//#region src/agents/cli-runner/cli-native-tool-approval.ts
const CLI_NATIVE_TOOL_DESCRIPTION_HEAD_CHARS = 300;
const CLI_NATIVE_TOOL_DESCRIPTION_TAIL_CHARS = 80;
const CLI_NATIVE_TOOL_DESCRIPTION_MAX_CHARS = 380;
const CLI_NATIVE_TOOL_APPROVAL_GATEWAY_GRACE_MS = 1e4;
const CLI_NATIVE_TOOL_ALLOWED_DECISIONS = [
"allow-once",
"allow-always",
"deny"
];
const CLI_NATIVE_TOOL_TRUNCATED_DECISIONS = ["allow-once", "deny"];
const CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL = "Bash";
function resolveCliNativeToolApprovalPlan(execPermission) {
if (execPermission.security === "deny") return "deny";
if (execPermission.ask === "off") return execPermission.security === "full" ? "allow" : "deny";
return "prompt";
}
/**
* The gateway caps approval descriptions (PLUGIN_APPROVAL_DESCRIPTION_MAX_LENGTH),
* so full inputs cannot ride this channel. Head+tail display defeats padded
* prefixes hiding an executable tail, and the quantified marker makes a partial
* view an explicit operator decision. Accepted tradeoff: the middle stays
* unreviewable; oversized inputs therefore never earn allow-always.
*/
function formatCliNativeToolDescription(toolInput) {
const compact = JSON.stringify(toolInput) ?? "{}";
if (compact.length <= CLI_NATIVE_TOOL_DESCRIPTION_MAX_CHARS) return {
compact,
text: compact,
truncated: false
};
const head = truncateUtf16Safe(compact, CLI_NATIVE_TOOL_DESCRIPTION_HEAD_CHARS);
const tail = sliceUtf16Safe(compact, compact.length - CLI_NATIVE_TOOL_DESCRIPTION_TAIL_CHARS);
return {
compact,
text: `${head} …[+${compact.length - head.length - tail.length} chars hidden]… ${tail}`,
truncated: true
};
}
function formatCliNativeToolTitle(pluginId, toolName) {
return truncateUtf16Safe(`${pluginId} native tool: ${toolName}`, 80);
}
function resolveCliNativeToolAllowedDecisions(params) {
return params.ask === "always" || params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL || params.descriptionTruncated ? CLI_NATIVE_TOOL_TRUNCATED_DECISIONS : CLI_NATIVE_TOOL_ALLOWED_DECISIONS;
}
function toAbortError(reason) {
return reason instanceof Error ? reason : /* @__PURE__ */ new Error("CLI native tool approval aborted");
}
async function raceCliNativeToolApprovalAbort(promise, abortSignal) {
if (!abortSignal) return promise;
let onAbort;
const abortPromise = new Promise((_, reject) => {
if (abortSignal.aborted) {
reject(toAbortError(abortSignal.reason));
return;
}
onAbort = () => reject(toAbortError(abortSignal.reason));
abortSignal.addEventListener("abort", onAbort, { once: true });
});
try {
return await Promise.race([promise, abortPromise]);
} finally {
if (onAbort) abortSignal.removeEventListener("abort", onAbort);
}
}
function waitForCliNativeToolApproval(params) {
return raceCliNativeToolApprovalAbort(callGatewayTool("plugin.approval.waitDecision", { timeoutMs: params.gatewayTimeoutMs }, { id: params.id }, { signal: params.abortSignal }), params.abortSignal);
}
async function requestCliNativeToolApproval(params) {
try {
const timeoutMs = DEFAULT_PLUGIN_APPROVAL_TIMEOUT_MS;
const gatewayTimeoutMs = addTimerTimeoutGraceMs(timeoutMs, CLI_NATIVE_TOOL_APPROVAL_GATEWAY_GRACE_MS) ?? timeoutMs + CLI_NATIVE_TOOL_APPROVAL_GATEWAY_GRACE_MS;
const description = formatCliNativeToolDescription(params.toolInput);
const detail = truncatePluginApprovalDetail(description.compact);
const detailSanitization = params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL ? sanitizeExecApprovalWarningTextWithStatus(description.compact) : null;
const summarySanitization = params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL ? sanitizeExecApprovalWarningTextWithStatus(description.text) : null;
if (params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL && (description.truncated || detailSanitization?.truncated === true || detailSanitization?.oversized === true || summarySanitization?.truncated === true || summarySanitization?.oversized === true || summarySanitization && exceedsApprovalTextLimit(summarySanitization.text, 512))) return {
kind: "deny",
reason: "policy-oversized"
};
const bashCommand = params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL && typeof params.toolInput.command === "string" ? params.toolInput.command : void 0;
let mutableFileBinding;
if (params.toolName === CLI_NATIVE_TOOL_ARBITRARY_EXECUTION_TOOL) {
const prepared = await prepareSystemRunMutableFileBinding({
command: {
kind: "shell",
text: bashCommand ?? ""
},
cwd: params.cwd
});
if (!prepared.ok) return {
kind: "deny",
reason: "operand-binding",
message: prepared.message
};
mutableFileBinding = prepared.binding.operands.length > 0 ? prepared.binding : void 0;
}
const allowedDecisions = resolveCliNativeToolAllowedDecisions({
ask: params.ask,
toolName: params.toolName,
descriptionTruncated: description.truncated
});
const requestResult = await raceCliNativeToolApprovalAbort(callGatewayTool("plugin.approval.request", { timeoutMs: gatewayTimeoutMs }, {
pluginId: params.pluginId,
toolName: params.toolName,
toolCallId: params.toolCallId,
agentId: params.agentId,
sessionKey: params.sessionKey,
title: formatCliNativeToolTitle(params.pluginId, params.toolName),
description: description.text,
detail,
severity: "warning",
allowedDecisions,
timeoutMs,
twoPhase: true
}, {
expectFinal: false,
signal: params.abortSignal
}), params.abortSignal);
const id = typeof requestResult?.id === "string" ? requestResult.id : "";
if (!id) return {
kind: "deny",
reason: "unavailable"
};
let decision;
if (Object.hasOwn(requestResult ?? {}, "decision")) decision = requestResult.decision;
else {
const waitResult = await waitForCliNativeToolApproval({
id,
gatewayTimeoutMs,
abortSignal: params.abortSignal
});
decision = waitResult?.id === id ? waitResult.decision : void 0;
}
if (params.abortSignal?.aborted) return {
kind: "deny",
reason: "unavailable"
};
if ((decision === "allow-once" || decision === "allow-always") && mutableFileBinding) {
const binding = await revalidateSystemRunMutableFileBinding({
binding: mutableFileBinding,
cwd: params.cwd
});
if (!binding.ok) return {
kind: "deny",
reason: "operand-binding",
message: binding.message
};
}
if (decision === "allow-once") return {
kind: "allow",
grantAlways: false
};
if (decision === "allow-always" && allowedDecisions.includes(decision)) return {
kind: "allow",
grantAlways: true
};
if (decision === "deny") return {
kind: "deny",
reason: "user"
};
return {
kind: "deny",
reason: "unavailable"
};
} catch {
return {
kind: "deny",
reason: "unavailable"
};
}
}
//#endregion
//#region src/agents/cli-runner/no-output-timeout-policy.ts
const isReplaySafeCliResumeControlOnly = (useResume, ...unsafe) => useResume && !unsafe.some(Boolean);
function resolveCliNoOutputTimeoutDecision(params) {
const outstandingWork = params.cliTimeout.activeToolCount + params.cliTimeout.backgroundTaskCount > 0;
const deferMs = outstandingWork && params.outstandingWorkGraceMs !== void 0 ? Math.max(params.timeoutMs, params.outstandingWorkGraceMs) - params.quietDurationMs : void 0;
const retryable = !params.cliTimeout.observedActivity && !params.hasOutputText || params.allowResumeControlOnlyRetry === true && isReplaySafeCliResumeControlOnly(params.useResume, params.hasOutputText, params.hasReplayUnsafeActivity, outstandingWork);
return {
...deferMs !== void 0 && deferMs > 0 ? { deferMs } : {},
error: createCliTimeoutError(params.context, params.cliTimeout, retryable ? "cli_no_output_timeout" : void 0)
};
}
function createCliTimeoutError(context, cliTimeout, code) {
return createCliFailoverError(cliTimeout.mode === "no-output" ? `CLI produced no output for ${cliTimeout.timeoutSeconds}s and was terminated.` : `CLI exceeded timeout (${cliTimeout.timeoutSeconds}s) and was terminated.`, "timeout", context, {
code,
cliTimeout,
timeout: { timeoutPhase: "provider" }
});
}
//#endregion
//#region src/agents/cli-runner/execute-plugin.ts
const PLUGIN_ITERATOR_CLOSE_TIMEOUT_MS = 5e3;
function denyTool(message) {
return {
behavior: "deny",
message
};
}
function createPluginToolPermissionHandler(params) {
const run = params.context.params;
const permission = resolveExecDefaults({
cfg: run.config,
sessionEntry: run.sessionEntry,
execOverrides: run.execOverrides,
agentId: run.agentId,
sessionKey: run.runtimePolicySessionKey ?? run.sessionKey
});
const grants = /* @__PURE__ */ new Set();
return async (request) => {
const signal = request.abortSignal ? AbortSignal.any([params.abortSignal, request.abortSignal]) : params.abortSignal;
const assertActive = createCliRunCurrentAssertion(run, signal);
try {
assertActive();
} catch {
return denyTool("OpenClaw denied native tool use: the admitted run is no longer active.");
}
const toolName = request.toolName.trim();
if (!toolName) return denyTool("OpenClaw denied an unnamed native tool.");
if (run.cliToolAvailability && !run.cliToolAvailability.native.includes(toolName)) return denyTool(`OpenClaw denied native tool ${toolName}: it is unavailable to this run.`);
const canonicalToolName = normalizeToolPolicyName(toolName.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z0-9])([A-Z])/g, "$1_$2"));
const nativeFileTool = [
"read",
"write",
"edit"
].includes(canonicalToolName) && Object.hasOwn(request.toolInput, "file_path");
let policyInput = request.toolInput;
if (nativeFileTool) {
const nativePath = request.toolInput.file_path;
if (typeof nativePath !== "string") return denyTool("OpenClaw denied native file tool use: invalid file path.");
if (Object.hasOwn(request.toolInput, "path") && request.toolInput.path !== nativePath) return denyTool("OpenClaw denied native file tool use: conflicting file paths.");
policyInput = {
...request.toolInput,
path: nativePath
};
if (canonicalToolName === "edit") {
const { old_string: oldText, new_string: newText, edits } = request.toolInput;
if (typeof oldText !== "string" || typeof newText !== "string") return denyTool("OpenClaw denied native edit tool use: invalid replacement.");
if (edits !== void 0 && (!Array.isArray(edits) || edits.length !== 1 || !isRecord(edits[0]) || edits[0].oldText !== oldText || edits[0].newText !== newText)) return denyTool("OpenClaw denied native edit tool use: conflicting replacements.");
policyInput.edits = [{
oldText,
newText
}];
}
}
const requester = {
...run.messageChannel ?? run.messageProvider ? { channel: run.messageChannel ?? run.messageProvider } : {},
...run.agentAccountId ? { accountId: run.agentAccountId } : {},
...run.senderId ? { senderId: run.senderId } : {},
...run.senderIsOwner !== void 0 ? { senderIsOwner: run.senderIsOwner } : {}
};
const hookResult = await runBeforeToolCallHook({
toolName: canonicalToolName,
params: policyInput,
...request.toolCallId ? { toolCallId: request.toolCallId } : {},
signal,
ctx: {
...run.agentId ? { agentId: run.agentId } : {},
...run.config ? { config: run.config } : {},
cwd: params.context.cwd ?? params.context.workspaceDir,
workspaceDir: params.context.workspaceDir,
...run.sessionKey ? { sessionKey: run.sessionKey } : {},
sessionId: run.sessionId,
runId: run.runId,
...run.trigger ? { trigger: run.trigger } : {},
...run.approvalReviewerDeviceId ? { approvalReviewerDeviceId: run.approvalReviewerDeviceId } : {},
...run.currentChannelId ? { channelId: run.currentChannelId } : {},
...Object.keys(requester).length > 0 ? { requester } : {},
turnSourceChannel: run.messageChannel ?? run.messageProvider,
turnSourceTo: run.chatId ?? run.currentChannelId,
turnSourceAccountId: run.agentAccountId,
turnSourceThreadId: run.currentThreadTs,
loopDetection: resolveToolLoopDetectionConfig({
cfg: run.config,
agentId: run.agentId
})
}
});
try {
assertActive();
} catch {
return denyTool("OpenClaw denied native tool use: the admitted run closed during policy.");
}
if (hookResult.blocked) return denyTool(hookResult.reason);
if (!isRecord(hookResult.params)) return denyTool("OpenClaw denied native tool use: before_tool_call returned invalid input.");
let toolInput = hookResult.params;
if (nativeFileTool) {
if (typeof toolInput.path !== "string") return denyTool("OpenClaw denied native file tool use: invalid rewritten file path.");
if (toolInput === policyInput) toolInput = request.toolInput;
else {
toolInput = {
...toolInput,
file_path: toolInput.path
};
if (!Object.hasOwn(request.toolInput, "path")) delete toolInput.path;
if (canonicalToolName === "edit") {
const edits = toolInput.edits;
if (!Array.isArray(edits) || edits.length !== 1 || !isRecord(edits[0]) || typeof edits[0].oldText !== "string" || typeof edits[0].newText !== "string") return denyTool("OpenClaw denied an unrepresentable native edit rewrite.");
toolInput.old_string = edits[0].oldText;
toolInput.new_string = edits[0].newText;
if (!Object.hasOwn(request.toolInput, "edits")) delete toolInput.edits;
}
}
}
const plan = resolveCliNativeToolApprovalPlan(permission);
if (plan === "deny") return denyTool(`OpenClaw exec policy denied native tool use (security=${permission.security}, ask=${permission.ask}).`);
const currentGrants = getCliLiveSessionApprovalGrants(params.context) ?? grants;
if (plan === "allow" || permission.ask !== "always" && currentGrants.has(toolName)) {
assertActive();
return {
behavior: "allow",
updatedInput: toolInput
};
}
params.onPendingApproval(1);
let outcome;
try {
outcome = await requestCliNativeToolApproval({
toolName,
toolInput,
pluginId: params.context.backendResolved.id,
sessionKey: run.sessionKey,
agentId: run.agentId,
toolCallId: request.toolCallId,
cwd: params.context.cwd ?? params.context.workspaceDir,
abortSignal: signal,
ask: permission.ask
});
} finally {
params.onPendingApproval(-1);
}
try {
assertActive();
} catch {
return denyTool("OpenClaw denied native tool use: the admitted run closed during approval.");
}
if (outcome.kind !== "allow") return denyTool(outcome.message ?? (outcome.reason === "user" ? `OpenClaw user denied native tool use (${toolName}).` : `OpenClaw approval was not granted for native tool use (${toolName}).`));
if (outcome.grantAlways) currentGrants.add(toolName);
return {
behavior: "allow",
updatedInput: toolInput
};
};
}
function cancelUserInput(message) {
return {
status: "cancelled",
message
};
}
function createPluginUserInputHandler(params) {
const run = params.context.params;
return async (request) => {
const signal = request.abortSignal ? AbortSignal.any([params.abortSignal, request.abortSignal]) : params.abortSignal;
const assertActive = createCliRunCurrentAssertion(run, signal);
try {
assertActive();
} catch {
return cancelUserInput("OpenClaw cancelled operator input: the admitted run is no longer active.");
}
const toolName = request.toolName.trim();
if (!toolName || run.cliToolAvailability && !run.cliToolAvailability.native.includes(toolName)) return cancelUserInput(toolName ? `OpenClaw cancelled operator input from ${toolName}: it is unavailable to this run.` : "OpenClaw cancelled an unnamed operator input request.");
if (request.questions.length === 0 || request.questions.length > 12) return cancelUserInput("OpenClaw cancelled an invalid operator input request.");
const questionAuthority = params.context.bindQuestionAnswerAuthority?.(assertActive);
const assertQuestionActive = () => {
assertActive();
questionAuthority?.assertActive();
};
params.onPendingInput(1);
try {
const result = await withAgentQuestionAnswerAuthority(questionAuthority, () => runStructuredInput({
input: compileStructuredInputQuestions({
questions: request.questions.map((question) => ({
...question,
isSecret: false
})),
intro: request.intro?.trim() || "Agent needs input:"
}),
sessionKey: run.sessionKey ?? run.sessionId,
agentId: run.agentId,
runId: run.runId,
timeoutMs: run.timeoutMs,
delivery: {
onBlockReply: run.onBlockReply,
onPartialReply: run.onPartialReply
},
signal,
isActive: () => {
try {
assertQuestionActive();
return true;
} catch {
return false;
}
},
questionId: request.toolCallId ? (batch) => `${request.toolCallId}:${batch}` : void 0
}));
try {
assertQuestionActive();
} catch {
return cancelUserInput("OpenClaw cancelled operator input: the admitted run closed before the answer was committed.");
}
return result.status === "answered" ? {
status: "answered",
answers: result.answers
} : cancelUserInput(result.message ?? "OpenClaw cancelled operator input; continue with your best judgment.");
} catch {
return cancelUserInput("OpenClaw could not collect operator input; continue with your best judgment.");
} finally {
params.onPendingInput(-1);
}
};
}
function waitForIteratorValue(iterator, signal) {
if (signal.aborted) return Promise.reject(toErrorObject(signal.reason, "CLI plugin execution was aborted."));
return new Promise((resolve, reject) => {
const rejectAborted = () => reject(toErrorObject(signal.reason, "CLI plugin execution was aborted."));
signal.addEventListener("abort", rejectAborted, { once: true });
iterator.next().then((value) => {
signal.removeEventListener("abort", rejectAborted);
resolve(value);
}, (error) => {
signal.removeEventListener("abort", rejectAborted);
reject(toErrorObject(error, "CLI plugin execution stream failed."));
});
});
}
async function closePluginIterator(iterator) {
if (!iterator?.return) return;
let timeout;
try {
await Promise.race([iterator.return(), new Promise((_, reject) => {
timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("CLI plugin runtime did not close after its run ended.")), PLUGIN_ITERATOR_CLOSE_TIMEOUT_MS);
timeout.unref();
})]);
} finally {
clearTimeout(timeout);
}
}
/** Runs a prepared plugin transport while keeping cancellation and approvals host-owned. */
async function executePluginOwnedProcess(params) {
const run = params.context.params;
const cwd = params.context.cwd ?? params.context.workspaceDir;
const command = resolveExecutablePath(params.executionCommand, {
cwd,
env: params.env
});
if (!command) throw new Error(`CLI backend executable could not be resolved: ${params.executionCommand}`);
const startedAt = Date.now();
const controller = new AbortController();
const signal = run.abortSignal ? AbortSignal.any([controller.signal, run.abortSignal]) : controller.signal;
const assertCurrent = createCliRunCurrentAssertion(run, signal);
const termination = { reason: "exit" };
const outstanding = {
approvals: 0,
background: 0,
lastOutputAt: startedAt,
observed: false,
replayUnsafe: false
};
const updatePendingApproval = (delta) => outstanding.approvals = Math.max(0, outstanding.approvals + delta);
let noOutputTimer;
const overallTimeoutMs = clampPositiveTimerTimeoutMs(run.timeoutMs);
const noOutputTimeoutMs = clampPositiveTimerTimeoutMs(params.noOutputTimeoutMs);
const overallTimer = overallTimeoutMs === void 0 ? void 0 : setTimeout(() => {
termination.reason = "overall-timeout";
controller.abort(/* @__PURE__ */ new Error("CLI plugin runtime exceeded its execution timeout."));
}, overallTimeoutMs);
const resetNoOutputTimer = (delayMs = noOutputTimeoutMs) => {
clearTimeout(noOutputTimer);
if (delayMs === void 0 || noOutputTimeoutMs === void 0) return;
noOutputTimer = setTimeout(() => {
const quietDurationMs = Date.now() - outstanding.lastOutputAt;
const decision = resolveCliNoOutputTimeoutDecision({
context: {
provider: run.provider,
model: params.context.modelId,
sessionId: run.sessionId,
lane: run.lane
},
timeoutMs: noOutputTimeoutMs,
quietDurationMs,
cliTimeout: {
mode: "no-output",
timeoutSeconds: Math.round(quietDurationMs / 1e3),
observedActivity: outstanding.observed,
activeToolCount: Math.max(params.activeToolCount?.() ?? 0, outstanding.approvals),
backgroundTaskCount: outstanding.background
},
hasOutputText: false,
useResume: params.useResume,
hasReplayUnsafeActivity: outstanding.replayUnsafe,
allowResumeControlOnlyRetry: true,
outstandingWorkGraceMs: BLOCKED_TOOL_CALL_ABORT_FLOOR_MS
});
if (decision.deferMs !== void 0) {
resetNoOutputTimer(decision.deferMs);
return;
}
termination.reason = "no-output-timeout";
params.onNoOutputTimeout?.(decision.error);
controller.abort(decision.error);
}, delayMs);
};
const replyBackendHandle = run.replyOperation ? {
kind: "cli",
runId: run.runId,
toolAuthorityFingerprint: run.toolAuthorityFingerprint,
cancel: () => {
termination.reason = "manual-cancel";
controller.abort(createCliAbortError());
}
} : void 0;
if (replyBackendHandle) run.replyOperation?.attachBackend(replyBackendHandle);
let iterator;
let liveSession;
let terminalResult = "none";
try {
assertCurrent();
resetNoOutputTimer();
if (params.liveSession && (params.forceNewSession || Boolean(params.context.preparedBackend.backend.resumeArgs?.length) && !params.useResume)) {
if (params.liveSession.requiredGeneration) throw new Error("The required CLI live session cannot be replaced by a fresh process.");
await closeCliLiveSession(params.context, "restart");
}
assertCurrent();
if (params.liveSession) liveSession = createCliLiveSessionCapability({
context: params.context,
argv: [command, ...params.executionArgs],
argv0: params.executionArgv0,
env: params.env,
...params.liveSession,
abortSignal: signal,
claimResources: params.context.preparedBackend.claimLiveSessionResources
});
run.assertCurrent?.();
signal.throwIfAborted();
iterator = params.execute({
command,
argv0: params.executionArgv0,
args: params.executionArgs,
cwd,
env: params.env,
prompt: params.prompt,
...params.promptContext ? { promptContext: params.promptContext } : {},
modelId: params.context.normalizedModel,
systemPrompt: stripSystemPromptCacheBoundary(params.context.systemPrompt).trim(),
...params.sessionId ? { sessionId: params.sessionId } : {},
useResume: params.useResume,
abortSignal: signal,
assertCurrent,
timeoutMs: run.timeoutMs,
...run.executionMode ? { executionMode: run.executionMode } : {},
...run.cliToolAvailability ? { toolAvailability: run.cliToolAvailability } : {},
...liveSession ? { liveSession } : {},
requestToolPermission: createPluginToolPermissionHandler({
context: params.context,
abortSignal: signal,
onPendingApproval: updatePendingApproval
}),
requestUserInput: createPluginUserInputHandler({
context: params.context,
abortSignal: signal,
onPendingInput: updatePendingApproval
})
})[Symbol.asyncIterator]();
for (;;) {
const next = await waitForIteratorValue(iterator, signal);
if (next.done) break;
if (!isRecord(next.value)) {
outstanding.replayUnsafe = true;
throw new Error("CLI plugin runtime emitted an invalid structured stream event.");
}
if (next.value.type === "result") terminalResult = terminalResult === "error" || next.value.is_error === true || typeof next.value.subtype === "string" && next.value.subtype.startsWith("error_") ? "error" : "success";
if (next.value.type === "system" && next.value.subtype === "background_tasks_changed" && Array.isArray(next.value.tasks)) outstanding.background = next.value.tasks.filter(isRecord).length;
params.consumeStdout(`${JSON.stringify(next.value)}\n`);
outstanding.observed = true;
if (!(next.value.type === "system" && next.value.subtype === "init") && next.value.type !== "command_lifecycle") outstanding.replayUnsafe = true;
outstanding.lastOutputAt = Date.now();
resetNoOutputTimer();
}
if (terminalResult === "none") throw new Error("CLI plugin runtime completed without a terminal result.");
} catch (error) {
if (run.abortSignal?.aborted || termination.reason === "manual-cancel") {
const reason = isSignalTimeoutReason(run.abortSignal?.reason) ? "timeout" : "aborted";
if (!params.onInterrupted?.(reason)) throw createCliAbortError();
termination.reason = "manual-cancel";
}
if (termination.reason === "exit" && terminalResult !== "error") {
if (params.liveSession?.requiredGeneration && isReplaySafeCliResumeControlOnly(params.useResume, outstanding.replayUnsafe, Boolean(outstanding.approvals || outstanding.background || params.activeToolCount?.()))) try {
liveSession?.current();
} catch (sessionError) {
if (sessionError instanceof FailoverError && sessionError.reason === "session_expired") {
const { code, message, reason } = sessionError;
throw createCliFailoverError(message, reason, sessionError, {
cause: error,
code
});
}
}
throw error;
}
} finally {
clearTimeout(overallTimer);
clearTimeout(noOutputTimer);
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("CLI plugin runtime turn is no longer active."));
if (replyBackendHandle) run.replyOperation?.detachBackend(replyBackendHandle);
await closePluginIterator(iterator);
}
return {
reason: termination.reason,
exitCode: termination.reason === "exit" ? 0 : null,
exitSignal: null,
durationMs: Date.now() - startedAt,
stdout: "",
stderr: "",
timedOut: termination.reason === "overall-timeout" || termination.reason === "no-output-timeout",
noOutputTimedOut: termination.reason === "no-output-timeout"
};
}
//#endregion
//#region src/agents/cli-runner/output-error.ts
function createCliOutputFailoverError(params) {
if (!params.output.errorText) return;
const message = formatCliOutputError(params.output, {
runId: params.runId,
sessionId: params.sessionId
});
const terminalFailure = params.output.terminalFailure?.reason;
const reason = terminalFailure ? terminalFailure === "synthetic_no_response" ? "format" : "unknown" : classifyFailoverReason(message, { provider: params.provider }) ?? "unknown";
const code = terminalFailure ? `cli_${terminalFailure}` : reason === "context_overflow" ? "cli_context_overflow" : void 0;
return new FailoverError(message, {
reason,
provider: params.provider,
model: params.model,
sessionId: params.sessionId,
lane: params.lane,
status: resolveFailoverStatus(reason),
code,
rawError: params.output.errorText
});
}
//#endregion
//#region src/agents/cli-runner/execute-process.ts
const CLI_RUNNER_OUTPUT_PARSE_BYTES = 1048576;
function appendCliOutputParseBuffer(buffer, chunk) {
if (!chunk) return {
buffer,
exceeded: false
};
const chunkBuffer = Buffer.from(chunk);
if (buffer.byteLength + chunkBuffer.byteLength <= CLI_RUNNER_OUTPUT_PARSE_BYTES) return {
buffer: Buffer.concat([buffer, chunkBuffer], buffer.byteLength + chunkBuffer.byteLength),
exceeded: false
};
const remainingBytes = CLI_RUNNER_OUTPUT_PARSE_BYTES - buffer.byteLength;
return {
buffer: remainingBytes <= 0 ? buffer : Buffer.concat([buffer, chunkBuffer.subarray(0, remainingBytes)], CLI_RUNNER_OUTPUT_PARSE_BYTES),
exceeded: true
};
}
async function executeCliProcess(params) {
const context = params.context;
const runParams = context.params;
const failoverContext = {
provider: runParams.provider,
model: context.modelId,
sessionId: runParams.sessionId,
lane: runParams.lane
};
const outputErrorContext = {
...failoverContext,
runId: runParams.runId
};
const resumeAtArg = params.useResume && runParams.cliSessionResumeAt ? params.backend.resumeAtArg : void 0;
const streamingParser = params.outputMode === "jsonl" ? createCliJsonlStreamingParser({
backend: params.backend,
providerId: context.backendResolved.id,
parseJsonlEvent: context.backendResolved.parseJsonlEvent,
parseJsonlLifecycleEvent: context.backendResolved.parseJsonlLifecycleEvent,
onAssistantDelta: params.events.emitCliAssistantDelta,
onThinkingDelta: params.events.emitCliThinkingDelta,
onThinkingProgress: params.events.emitCliThinkingProgress,
onCompaction: params.events.emitCliCompaction,
onToolUseStart: params.events.emitParsedToolUseStart,
onToolResult: params.events.emitParsedToolResult,
onDisplayToolUseStart: params.events.emitCliDisplayToolUseStart,
onDisplayToolResult: params.events.emitCliDisplayToolResult,
onCommentaryText: params.events.emitLiveEvents && runParams.emitCommentaryText ? params.events.emitCliCommentaryText : void 0,
onSessionId: params.observeForkSuccessor,
onNativeTools: context.preparedBackend.mcpClientGrantCapture?.captureNativeTools,
onAssistantMessage: params.diagnostics?.observeAssistantMessage,
onUsage: params.diagnostics?.observeUsage
}) : null;
let stdoutTail = "";
let stdoutParseBuffer = Buffer.alloc(0);
let stdoutBytes = 0;
const stdoutHash = crypto.createHash("sha256");
let stdoutParseExceeded = false;
let stderrTail = "";
let stderrParseBuffer = Buffer.alloc(0);
let stderrBytes = 0;
const stderrHash = crypto.createHash("sha256");
let stderrParseExceeded = false;
const reportStreamProgress = createModelCallStreamProgressReporter(() => backendActivity?.observeOutput(true) ?? false);
const streamProgressTarget = {
runId: runParams.runId,
...runParams.sessionKey ? { sessionKey: runParams.sessionKey } : {},
...runParams.sessionId ? { sessionId: runParams.sessionId } : {}
};
const consumeStdout = (chunk) => {
const chunkBytes = Buffer.byteLength(chunk);
params.diagnostics?.observeCliOutput(chunk, "stdout", chunkBytes);
if (chunkBytes > 0) {
if (params.events.activeParsedToolCount() === 0) reportStreamProgress(streamProgressTarget);
else backendActivity?.observeOutput(false);
}
stdoutBytes += chunkBytes;
stdoutHash.update(chunk);
stdoutTail = appendCliOutputTail(stdoutTail, chunk);
if (!stdoutParseExceeded) {
const next = appendCliOutputParseBuffer(stdoutParseBuffer, chunk);
stdoutParseBuffer = next.buffer;
stdoutParseExceeded = next.exceeded;
}
streamingParser?.push(chunk);
};
const consumeStderr = (chunk) => {
params.diagnostics?.observeCliOutput(chunk, "stderr");
stderrBytes += Buffer.byteLength(chunk);
stderrHash.update(chunk);
stderrTail = appendCliOutputTail(stderrTail, chunk);
if (!stderrParseExceeded) {
const next = appendCliOutputParseBuffer(stderrParseBuffer, chunk);
stderrParseBuffer = next.buffer;
stderrParseExceeded = next.exceeded;
}
};
runParams.onExecutionPhase?.({
phase: "process_spawned",
provider: runParams.provider,
model: context.modelId,
backend: context.backendResolved.id
});
let managedRunPid;
let nodeRunAbortSignal;
let nodeRunTruncated = false;
const pluginTimeout = {};
let terminalInterruption;
let result;
runParams.assertCurrent?.();
params.diagnostics?.observeRequestPayload(params.stdin ?? params.argsPrompt ?? "");
params.assertCurrent();
const backendActivity = runParams.diagnosticOwner ? beginDiagnosticBackendActivity({
owner: runParams.diagnosticOwner,
noOutputTimeoutMs: params.noOutputTimeoutMs,
assertCurrent: params.assertCurrent
}) : void 0;
try {
if (params.nodePlacement) {
const nodeRun = await executeNodeClaudeRun({
context,
nodePlacement: params.nodePlacement,
executionArgs: params.executionArgs,
stdinPayload: params.stdin ?? "",
...params.nodeSystemPrompt !== void 0 ? { nodeSystemPrompt: params.nodeSystemPrompt } : {},
...params.nodeEnv ? { nodeEnv: params.nodeEnv } : {},
...params.nodeClearEnv ? { nodeClearEnv: params.nodeClearEnv } : {},
noOutputTimeoutMs: params.noOutputTimeoutMs,
consumeStdout,
consumeStderr,
deps: params.deps
});
result = nodeRun.result;
nodeRunAbortSignal = nodeRun.nodeRunAbortSignal;
nodeRunTruncated = nodeRun.nodeRunTruncated;
} else if (context.executionTarget.kind === "plugin") result = await executePluginOwnedProcess({
context,
execute: context.executionTarget.execute,
executionCommand: params.executionCommand,
executionArgv0: params.executionArgv0,
executionArgs: [...params.executionLeadingArgv, ...params.executionArgs],
env: params.env,
prompt: params.prompt,
...params.promptContext ? { promptContext: params.promptContext } : {},
useResume: params.useResume,
forceNewSession: params.cliSessionIdToUse === void 0 && context.openClawHistoryPrompt !== void 0,
sessionId: params.resolvedSessionId,
noOutputTimeoutMs: params.noOutputTimeoutMs,
consumeStdout,
activeToolCount: params.events.activeParsedToolCount,
onNoOutputTimeout: (error) => {
pluginTimeout.error = error;
},
onInterrupted: (reason) => {
streamingParser?.finish();
const partialOutput = streamingParser?.getOutput();
if (!partialOutput?.text.trim() || partialOutput.errorText || partialOutput.terminalFailure) return false;
terminalInterruption = { reason };
return true;
},
...params.useManagedClaudeLiveSession ? { liveSession: {
captureKey: params.initialGatewayCaptureKey,
beginCapture: params.toolTracking.beginGatewayCapture,
requiredGeneration: params.cliSessionIdToUse ? context.requiredClaudeLiveSessionGeneration : void 0
} } : {}
}).catch((error) => {
runParams.assertCurrent?.();
if (runParams.abortSignal?.aborted || params.events.hasObservedCliActivity()) throw error;
throw resolveCliResumeAtError(error, resumeAtArg, failoverContext) ?? error;
});
else {
const supervisor = params.deps.getProcessSupervisor();
const scopeKey = buildCliSupervisorScopeKey({
backend: params.backend,
backendId: context.backendResolved.id,
cliSessionId: params.useResume ? params.resolvedSessionId : void 0
});
if (runParams.abortSignal?.aborted) throw createCliAbortError();
const abortManagedRun = () => supervisor.cancel(runParams.runId, "manual-cancel");
runParams.abortSignal?.addEventListener("abort", abortManagedRun, { once: true });
try {
const managedRun = await supervisor.spawn({
assertCurrent: params.assertCurrent,
runId: runParams.runId,
sessionId: runParams.sessionId,
backendId: context.backendResolved.id,
scopeKey,
replaceExistingScope: Boolean(params.useResume && scopeKey),
mode: "child",
argv: [
params.executionCommand,
...params.executionLeadingArgv,
...params.executionArgs
],
argv0: params.executionArgv0,
timeoutMs: runParams.timeoutMs,
noOutputTimeoutMs: params.noOutputTimeoutMs,
cwd: context.cwd ?? context.workspaceDir,
env: params.env,
input: params.stdin ?? "",
secretInput: context.preparedBackend.secretInput,
captureOutput: false,
onStdout: consumeStdout,
onStderr: consumeStderr
});
managedRunPid = managedRun.pid;
const replyBackendHandle = runParams.replyOperation ? {
kind: "cli",
runId: runParams.runId,
toolAuthorityFingerprint: runParams.toolAuthorityFingerprint,
cancel: () => managedRun.cancel("manual-cancel")
} : void 0;
if (replyBackendHandle) runParams.replyOperation?.attachBackend(replyBackendHandle);
try {
result = await managedRun.wait();
} finally {
if (replyBackendHandle) runParams.replyOperation?.detachBackend(replyBackendHandle);
}
} finally {
runParams.abortSignal?.removeEventListener("abort", abortManagedRun);
}
}
} finally {
backendActivity?.close();
}
if ((runParams.abortSignal?.aborted || nodeRunAbortSignal?.aborted) && result.reason === "manual-cancel" && !terminalInterruption) throw createCliAbortError();
params.options?.onPhase?.("resolve");
streamingParser?.finish();
const streamingParserErrorText = params.outputMode === "jsonl" ? streamingParser?.getErrorText() ?? null : null;
if (streamingParserErrorText) throw createCliFailoverError(streamingParserErrorText, "format", failoverContext);
if (nodeRunTruncated && result.exitCode === 0 && !result.timedOut && !streamingParser?.hasTerminalResult()) throw createCliFailoverError("paired node truncated the Claude CLI stream before the terminal result; refusing to accept partial output.", "format", failoverContext);
let stdout;
const readStdout = () => stdout ??= stdoutParseBuffer.toString("utf8").trim();
const stdoutDiagnostic = stdoutTail.trim();
const stderrDiagnostic = stderrTail.trim();
const processDiagnostics = {
backendId: context.backendResolved.id,
processReason: result.reason,
exitCode: result.exitCode,
exitSignal: result.exitSignal,
durationMs: result.durationMs,
stdoutBytes,
stdoutHash: stdoutHash.digest("hex").slice(0, 12),
stderrBytes,
stderrHash: stderrHash.digest("hex").slice(0, 12),
useResume: params.useResume
};
if (params.logOutputText) {
if (stdoutDiagnostic) cliBackendLog.info(`cli stdout:\n${stdoutDiagnostic}`);
if (stderrDiagnostic) cliBackendLog.info(`cli stderr:\n${stderrDiagnostic}`);
}
if (shouldLogVerbose()) {
if (stdoutDiagnostic) cliBackendLog.debug(`cli stdout:\n${stdoutDiagnostic}`);
if (stderrDiagnostic) cliBackendLog.debug(`cli stderr:\n${stderrDiagnostic}`);
}
const streamedJsonlOutput = params.outputMode === "jsonl" ? streamingParser?.getOutput() ?? null : null;
const parsedStructuredOutput = streamedJsonlOutput ?? (params.outputMode === "json" && !stdoutParseExceeded ? parseCliOutput({
raw: readStdout(),
backend: params.backend,
providerId: context.backendResolved.id,
outputMode: params.outputMode,
fallbackSessionId: params.resolvedSessionId
}) : null);
if (parsedStructuredOutput?.terminalFailure) {
const terminalError = createCliOutputFailoverError({
output: parsedStructuredOutput,
...outputErrorContext
});
if (terminalError) throw terminalError;
}
if (!terminalInterruption && (result.exitCode !== 0 || result.reason !== "exit")) {
params.options?.onPhase?.("send");
if (result.reason === "no-output-timeout" || result.noOutputTimedOut) {
const timeoutSeconds = Math.round(params.noOutputTimeoutMs / 1e3);
cliBackendLog.warn(`cli watchdog timeout: provider=${runParams.provider} model=${context.modelId} session=${params.resolvedSessionId ?? runParams.sessionId} noOutputTimeoutMs=${params.noOutputTimeoutMs} pid=${managedRunPid ?? "node"}`);
const observedActivity = params.events.hasObservedCliActivity();
const timeoutDecision = pluginTimeout.error ? { error: pluginTimeout.error } : resolveCliNoOutputTimeoutDecision({
context: failoverContext,
timeoutMs: params.noOutputTimeoutMs,
quietDurationMs: params.noOutputTimeoutMs,
cliTimeout: {
mode: "no-output",
timeoutSeconds,
observedActivity,
activeToolCount: params.events.activeParsedToolCount(),
backgroundTaskCount: 0
},
hasOutputText: Boolean(stdoutDiagnostic || stderrDiagnostic),
useResume: params.useResume,
hasReplayUnsafeActivity: observedActivity
});
const deferNotice = timeoutDecision.error.code === "cli_no_output_timeout" && Boolean(params.cliSessionIdToUse) && Boolean(params.resolvedSessionId) && Boolean(context.openClawHistoryPrompt) && Boolean(runParams.sessionKey) && runParams.timeoutMs - (Date.now() - context.started) > 0;
if (runParams.sessionKey && params.events.emitLiveEvents && !deferNotice) {
const stallNotice = [
`CLI agent (${runParams.provider}) produced no output for ${timeoutSeconds}s and was terminated.`,
"It may have been waiting for interactive input or an approval prompt.",
"Check CLI permission settings and OpenClaw approval prompts."
].join(" ");
const routing = resolveEventSessionRoutingPolicy({
cfg: runParams.config,
sessionKey: runParams.sessionKey,
channel: runParams.messageProvider,
accountId: runParams.agentAccountId
});
params.deps.enqueueSystemEvent(stallNotice, { sessionKey: resolveEventSessionKeyForPolicy(runParams.sessionKey, routing) });
params.deps.requestHeartbeat(scopedHeartbeatWakeOptionsForPolicy(runParams.sessionKey, {
source: "cli-watchdog",
intent: "event",
reason: "cli:watchdog:stall"
}, routing));
}
throw timeoutDecision.error;
}
if (result.reason === "overall-timeout") throw createCliTimeoutError(failoverContext, {
mode: "overall",
timeoutSeconds: Math.round(runParams.timeoutMs / 1e3),
observedActivity: params.events.hasObservedCliActivity(),
activeToolCount: params.events.activeParsedToolCount(),
backgroundTaskCount: 0
}, "cli_overall_timeout");
const retryEmptyFailure = result.reason === "exit" && !params.events.hasObservedCliActivity();
const stderr = stderrParseBuffer.toString("utf8").trim();
throw createCliExitFailoverError({
context: failoverContext,
candidates: [
stderr,
readStdout(),
stderrDiagnostic,
stdoutDiagnostic
],
fallbackMessage: "CLI failed.",
retryEmptyFailure,
resumeAtArg: retryEmptyFailure ? resumeAtArg : void 0
});
}
if (stdoutParseExceeded && !streamedJsonlOutput) throw createCliFailoverError(`CLI stdout exceeded ${CLI_RUNNER_OUTPUT_PARSE_BYTES} bytes; refusing to parse truncated output.`, "format", failoverContext);
if (runParams.controlOperation === "compact") {
const manualCompaction = context.backendResolved.manualCompaction;
if (!manualCompaction) throw new Error(`CLI backend ${context.backendResolved.id} does not support manual compaction`);
const validation = manualCompaction.validateOutput(readStdout());
if (!validation.ok) throw createCliFailoverError(validation.reason, "unknown", failoverContext);
return {
text: "",
rawText: "",
diagnostics: { process: processDiagnostics },
finalPromptText: params.prompt
};
}
const parsed = parsedStructuredOutput ?? parseCliOutput({
raw: readStdout(),
backend: params.backend,
providerId: context.backendResolved.id,
outputMode: params.outputMode,
fallbackSessionId: params.resolvedSessionId
});
const parsedError = createCliOutputFailoverError({
output: parsed,
...outputErrorContext
});
if (parsedError) throw parsedError;
const rawText = parsed.text;
cliBackendLog.info(`cli turn: provider=${runParams.provider} model=${context.modelId} durationMs=${Date.now() - params.cliTurnStartedAt} ${formatCliBackendOutputDigest(rawText)}`);
return {
...parsed,
...terminalInterruption ? { terminalInterruption } : {},
diagnostics: {
...parsed.diagnostics,
process: processDiagnostics
},
rawText,
finalPromptText: params.prompt,
text: applyPluginTextReplacements(rawText, context.backendResolved.textTransforms?.output)
};
}
const normalizeCliMessagingToolName = stripOpenClawMcpToolPrefix;
function extractCliMessagingTarget(context, toolName, args) {
const normalizedToolName = normalizeCliMessagingToolName(toolName);
const currentProvider = context.params.messageChannel ?? context.params.messageProvider;
const hasExplicitProvider = typeof args.provider === "string" && args.provider.trim().length > 0 || typeof args.channel === "string" && args.channel.trim().length > 0;
const targetArgs = normalizedToolName === "message" && currentProvider && !hasExplicitProvider ? {
...args,
provider: currentProvider
} : args;
if (!isMessagingToolTargetEvidenceAction(normalizedToolName, targetArgs)) return;
return extractMessagingToolSend(normalizedToolName, targetArgs, {
config: context.params.config,
currentChannelId: context.params.currentChannelId,
currentThreadId: context.params.currentThreadTs,
currentMessageId: context.params.currentMessageId
});
}
function buildMessagingToolSendEvidenceKey(send) {
return crypto.createHash("sha256").update(JSON.stringify([
send.tool,
send.provider,
send.accountId,
send.to,
send.threadId,
send.threadImplicit,
send.threadSuppressed,
send.text,
send.mediaUrls
])).digest("hex");
}
function extractCliMessagingContent(args, result) {
const text = [
"message",
"SendMessage",
"content",
"text",
"caption"
].map((key) => args[key]).find((value) => typeof value === "string" && value.trim().length > 0);
const mediaUrls = [...collectMessagingMediaUrlsFromRecord(args), ...collectMessagingMediaUrlsFromToolResult(result)].filter((url, index, all) => all.indexOf(url) === index);
return {
...text ? { text } : {},
...mediaUrls.length > 0 ? { mediaUrls } : {}
};
}
function appendUniqueCliMessagingEvidence(values, valueKeys, additions) {
for (const addition of additions) {
if (!addition || valueKeys.has(addition)) continue;
if (values.length >= 64) {
const removed = values.shift();
if (removed) valueKeys.delete(removed);
}
values.push(addition);
valueKeys.add(addition);
}
}
//#endregion
//#region src/agents/cli-runner/execute-tool-tracking.ts
const CLI_LOOPBACK_CORRELATION_MAX_CALLS = 64;
const CLI_MCP_DELIVERY_DRAIN_GRACE_MS = 5e3;
const CLI_MCP_REQUEST_ADMISSION_GRACE_MS = 250;
function createCliToolTracking(context) {
let gatewayCaptureKey;
let yielded = false;
let yieldAcknowledgment;
let didSendViaMessagingTool = false;
let didDeliverSourceReplyViaMessageTool = false;
let sourceReplyDelivered;
let inFlightUnclassifiedMcpRequests = 0;
let inFlightMessagingToolCalls = 0;
const inFlightPreparedMessagingCalls = /* @__PURE__ */ new Set();
const pendingMessagingCalls = /* @__PURE__ */ new Map();
const cliLoopbackCalls = [];
const activeCliTools = /* @__PURE__ */ new Map();
let cliLoopbackCorrelationOverflowed = false;
const messagingToolSentTexts = [];
const messagingToolSentTextKeys = /* @__PURE__ */ new Set();
const messagingToolSentMediaUrls = [];
const messagingToolSentMediaUrlKeys = /* @__PURE__ */ new Set();
const messagingToolSentTargets = [];
const messagingToolSentTargetKeys = /* @__PURE__ */ new Set();
const messagingToolSourceReplyPayloads = [];
const toolMediaUrls = [];
const toolMediaUrlKeys = /* @__PURE__ */ new Set();
let toolAudioAsVoice = false;
let toolTrustedLocalMedia = false;
const acceptedSessionSpawns = [];
const matchesCliLoopbackCall = (toolName, toolArgs, call) => normalizeCliMessagingToolName(toolName) === call.toolName && isDeepStrictEqual(toolArgs, call.args);
const markCliLoopbackCallsAmbiguous = (calls, activeEntries = Array.from(activeCliTools.entries()).filter(([, activeTool]) => activeTool.loopbackCall !== void 0 && calls.includes(activeTool.loopbackCall))) => {
const groups = /* @__PURE__ */ new Set();
for (const call of calls) if (call.ambiguityGroup) groups.add(call.ambiguityGroup);
for (const [, activeTool] of activeEntries) if (activeTool.ambiguityGroup) groups.add(activeTool.ambiguityGroup);
const group = groups.values().next().value ?? {
calls: /* @__PURE__ */ new Set(),
activeToolCallIds: /* @__PURE__ */ new Set()
};
for (const existing of groups) {
if (existing === group) continue;
for (const call of existing.calls) {
call.ambiguityGroup = group;
group.calls.add(call);
}
for (const toolCallId of existing.activeToolCallIds) {
const activeTool = activeCliTools.get(toolCallId);
if (activeTool) {
activeTool.ambiguityGroup = group;
group.activeToolCallIds.add(toolCallId);
}
}
existing.calls.clear();
existing.activeToolCallIds.clear();
}
for (const call of calls) {
call.ambiguous = true;
call.ambiguityGroup = group;
group.calls.add(call);
}
for (const [toolCallId, activeTool] of activeEntries) {
activeTool.loopbackAmbiguous = true;
activeTool.ambiguityGroup = group;
group.activeToolCallIds.add(toolCallId);
}
};
const matchingActiveCliTools = (call) => Array.from(activeCliTools.entries()).filter(([, activeTool]) => matchesCliLoopbackCall(activeTool.toolName, activeTool.args, call));
const markCliLoopbackSignatureAmbiguous = (call) => {
const calls = cliLoopbackCalls.filter((candidate) => matchesCliLoopbackCall(call.toolName, call.args, candidate.admitted));
markCliLoopbackCallsAmbiguous(calls, matchingActiveCliTools(call));
};
const retainCliLoopbackCall = (call) => {
if (cliLoopbackCalls.length >= CLI_LOOPBACK_CORRELATION_MAX_CALLS) {
cliLoopbackCorrelationOverflowed = true;
for (const activeTool of activeCliTools.values()) if (activeTool.loopbackCall || activeTool.toolName.startsWith("mcp__")) activeTool.loopbackAmbiguous = true;
cliLoopbackCalls.length = 0;
return;
}
const retained = {
admitted: call,
current: call,
ambiguous: false
};
cliLoopbackCalls.push(retained);
return retained;
};
const bindCliLoopbackCall = (call, toolCallId, activeTool) => {
call.boundToolCallId = toolCallId;
activeTool.loopbackCall = call;
activeTool.loopbackAmbiguous ||= call.ambiguous;
if (call.ambiguityGroup) {
activeTool.ambiguityGroup = call.ambiguityGroup;
call.ambiguityGroup.activeToolCallIds.add(toolCallId);
}
};
const removeCliLoopbackCall = (call) => {
if (!call) return;
const index = cliLoopbackCalls.indexOf(call);
if (index >= 0) cliLoopbackCalls.splice(index, 1);
};
const retireCliLoopbackCorrelation = (toolCallId, activeTool) => {
removeCliLoopbackCall(activeTool?.loopbackCall);
const group = activeTool?.ambiguityGroup;
if (!group) return;
group.activeToolCallIds.delete(toolCallId);
const hasUnboundCall = Array.from(group.calls).some((call) => call.boundToolCallId === void 0 && cliLoopbackCalls.includes(call));
if (group.activeToolCallIds.size > 0 || hasUnboundCall) return;
for (const call of group.calls) removeCliLoopbackCall(call);
group.calls.clear();
};
const commitMessagingToolResult = (params) => {
const deliveryFact = readEmbeddedMessageDeliveryFact(readToolResultDetails(params.result)?.messageDelivery);
if (!(deliveryFact ? deliveryFact.status === "settled" && (params.isError !== true || deliveryFact.partialDelivery) : isPluginNativeMessagingTool(params.toolName) && isDeliveredMessagingToolResult(params))) return;
didSendViaMessagingTool = true;
if (deliveryFact?.sourceReplyDelivered === true) sourceReplyDelivered = true;
const toolArgs = params.args ?? {};
const isMessagingSend = isMessagingToolSendAction(params.toolName, toolArgs);
const content = isMessagingSend ? extractCliMessagingContent(toolArgs, params.result) : {};
const confirmedTarget = params.target && extractMessagingToolSendResult(params.target, params.result);
const deliveredCurrentSourceReply = isDeliveredMessageToolOnlySourceReplyResult({
sourceReplyDeliveryMode: context.params.sourceReplyDeliveryMode,
toolName: params.toolName,
args: params.args,
result: params.result,
isError: params.isError,
allowExplicitSourceRoute: isDeliveredMessagingToolSendToCurrentSource({
send: confirmedTarget,
config: context.params.config,
currentProvider: context.params.messageChannel ?? context.params.messageProvider,
currentAccountId: context.params.agentAccountId,
currentChannelId: context.params.currentChannelId,
currentThreadId: context.params.currentThreadTs,
sessionKey: context.params.sessionKey,
deliveredPayload: params.result
}),
deliveryConfirmed: true
});
const sourceReplyFinal = deliveredCurrentSourceReply ? resolveMessageToolSourceReplyFinal(toolArgs) : void 0;
if (isMessagingSend) {
appendUniqueCliMessagingEvidence(messagingToolSentTexts, messagingToolSentTextKeys, content.text ? [content.text] : []);
appendUniqueCliMessagingEvidence(messagingToolSentMediaUrls, messagingToolSentMediaUrlKeys, content.mediaUrls ?? []);
}
if (deliveredCurrentSourceReply) {
didDeliverSourceReplyViaMessageTool = true;
const payload = extractMessagingToolSourceReplyPayload(params.result);
if (payload) {
if (messagingToolSourceReplyPayloads.length >= 64) messagingToolSourceReplyPayloads.shift();
messagingToolSourceReplyPayloads.push({
...payload,
...sourceReplyFinal !== void 0 ? { sourceReplyFinal } : {}
});
}
}
if (!confirmedTarget) return;
const targetWithContent = {
...confirmedTarget,
...content,
...sourceReplyFinal !== void 0 ? { sourceReplyFinal } : {}
};
const evidenceKey = buildMessagingToolSendEvidenceKey(targetWithContent);
if (messagingToolSentTargetKeys.has(evidenceKey)) return;
if (messagingToolSentTargets.length >= 64) {
const removed = messagingToolSentTargets.shift();
if (removed) messagingToolSentTargetKeys.delete(buildMessagingToolSendEvidenceKey(removed));
}
messagingToolSentTargets.push(targetWithContent);
messagingToolSentTargetKeys.add(evidenceKey);
};
const isPreparedInternalSourceReply = async (call) => {
if (context.params.sourceReplyDeliveryMode !== "message_tool_only" || normalizeCliMessagingToolName(call.toolName) !== "message" || call.args.action !== "send" || !context.params.config) return false;
return await shouldUseInternalSourceReplySink({
cfg: context.params.config,
action: "send",
sessionKey: context.params.sessionKey,
sourceReplyDeliveryMode: context.params.sourceReplyDeliveryMode,
toolContext: {
currentChannelProvider: context.params.messageChannel ?? context.params.messageProvider,
currentChannelId: context.params.currentChannelId,
currentThreadTs: context.params.currentThreadTs,
currentMessageId: context.params.currentMessageId,
replyToMode: context.params.replyToMode
}
}, call.args);
};
const beginGatewayCapture = (captureKey) => {
if (!captureKey || gatewayCaptureKey === captureKey) return;
if (gatewayCaptureKey) throw new Error("CLI MCP capture key changed during an active attempt");
context.preparedBackend.mcpClientGrantCapture?.activate(captureKey);
gatewayCaptureKey = captureKey;
const isPotentialDelivery = (toolName) => isMessagingTool(normalizeCliMessagingToolName(toolName));
const isPreparedDelivery = (toolName, toolArgs) => toolArgs.dryRun !== true && isMessagingToolDeliveryAction(normalizeCliMessagingToolName(toolName), toolArgs);
beginMcpLoopbackToolCallCapture({
captureKey,
onYield: (_message, acknowledgment) => {
yielded = true;
yieldAcknowledgment = acknowledgment;
},
onRequestStart: () => {
inFlightUnclassifiedMcpRequests += 1;
},
onRequestClassified: () => {
inFlightUnclassifiedMcpRequests = Math.max(0, inFlightUnclassifiedMcpRequests - 1);
},
onToolCallStart: (call) => {
const retained = retainCliLoopbackCall(call);
const candidates = matchingActiveCliTools(call);
let matched = retained && candidates.length === 1 && !candidates[0]?.[1].loopbackCall && !candidates[0]?.[1].loopbackAmbiguous ? candidates[0] : void 0;
if (retained && matched) bindCliLoopbackCall(retained, matched[0], matched[1]);
else if (retained && candidates.length > 0) {
markCliLoopbackSignatureAmbiguous(call);
matched = candidates.find(([, activeTool]) => !activeTool.loopbackCall);
if (matched) bindCliLoopbackCall(retained, matched[0], matched[1]);
}
if (isPotentialDelivery(call.toolName)) inFlightMessagingToolCalls += 1;
return matched?.[0];
},
onToolCallUpdate: ({ previous, current }) => {
const candidates = cliLoopbackCalls.filter((candidate) => matchesCliLoopbackCall(previous.toolName, previous.args, candidate.current));
const candidate = candidates.at(0);
if (candidates.length === 1 && candidate && !candidate.ambiguous) candidate.current = current;
else if (candidates.length > 0) markCliLoopbackCallsAmbiguous(candidates);
inFlightPreparedMessagingCalls.delete(previous);
const wasDelivery = isPotentialDelivery(previous.toolName);
const isDelivery = isPreparedDelivery(current.toolName, current.args);
if (wasDelivery !== isDelivery) inFlightMessagingToolCalls = Math.max(0, inFlightMessagingToolCalls + (isDelivery ? 1 : -1));
if (isDelivery) inFlightPreparedMessagingCalls.add(current);
},
onToolCallFinish: (call, { prepared }) => {
if (prepared ? isPreparedDelivery(call.toolName, call.args) : isPotentialDelivery(call.toolName)) inFlightMessagingToolCalls = Math.max(0, inFlightMessagingToolCalls - 1);
inFlightPreparedMessagingCalls.delete(call);
},
onToolCallResult: (call) => {
const terminalOutcome = call.outcome === "blocked" ? {
outcome: call.outcome,
deniedReason: call.deniedReason
} : { outcome: call.outcome };
const correlated = call.correlationId ? cliLoopbackCalls.find((candidate) => candidate.boundToolCallId === call.correlationId) : void 0;
const candidates = correlated ? [correlated] : cliLoopbackCalls.filter((candidate) => matchesCliLoopbackCall(call.toolName, call.args, candidate.current));
if (candidates.length === 1 && candidates[0]) candidates[0].outcome = terminalOutcome;
else if (candidates.length > 1) markCliLoopbackCallsAmbiguous(candidates);
const toolName = normalizeCliMessagingToolName(call.toolName);
const acceptedSessionSpawn = toolName === "sessions_spawn" && call.outcome === "completed" && "result" in call ? normalizeAcceptedSessionSpawnResult(call.result) : null;
if (acceptedSessionSpawn && acceptedSessionSpawns.length < CLI_LOOPBACK_CORRELATION_MAX_CALLS) acceptedSessionSpawns.push(acceptedSessionSpawn);
if (isMessagingToolDeliveryAction(toolName, call.args)) commitMessagingToolResult({
toolName,
target: extractCliMessagingTarget(context, toolName, call.args),
args: call.args,
result: "result" in call ? call.result : void 0,
isError: call.outcome !== "completed"
});
else if (call.outcome === "completed" && "result" in call) {
const artifact = extractToolResultMediaArtifact(call.result);
const mediaUrls = artifact ? filterToolResultMediaUrls(toolName, artifact.mediaUrls, call.result) : [];
appendUniqueCliMessagingEvidence(toolMediaUrls, toolMediaUrlKeys, mediaUrls);
if (mediaUrls.length > 0) {
toolAudioAsVoice ||= artifact?.audioAsVoice === true;
toolTrustedLocalMedia ||= artifact?.trustedLocalMedia === true;
}
}
}
});
};
const handleCliToolUseStart = (event) => {
if (event.kind !== "server_tool_use") {
const activeTool = {
toolName: event.name,
args: event.args,
loopbackAmbiguous: cliLoopbackCorrelationOverflowed && event.name.startsWith("mcp__")
};
activeCliTools.set(event.toolCallId, activeTool);
const admittedCall = {
toolName: normalizeCliMessagingToolName(event.name),
args: event.args
};
const pendingCandidates = cliLoopbackCalls.filter((candidate) => candidate.boundToolCallId === void 0 && matchesCliLoopbackCall(event.name, event.args, candidate.admitted));
const hasAssociatedPeer = matchingActiveCliTools(admittedCall).some(([toolCallId, peer]) => toolCallId !== event.toolCallId && (peer.loopbackCall !== void 0 || peer.loopbackAmbiguous));
const pending = pendingCandidates[0];
if (hasAssociatedPeer || pendingCandidates.length > 1 || pending?.ambiguous) {
markCliLoopbackSignatureAmbiguous(admittedCall);
if (pending) bindCliLoopbackCall(pending, event.toolCallId, activeTool);
} else if (pendingCandidates.length === 1 && pending) bindCliLoopbackCall(pending, event.toolCallId, activeTool);
}
const toolName = normalizeCliMessagingToolName(event.name);
if (event.kind === "server_tool_use" || gatewayCaptureKey || event.args.dryRun === true || !isMessagingToolDeliveryAction(toolName, event.args)) return;
if (pendingMessagingCalls.size >= 64) {
const oldestToolCallId = pendingMessagingCalls.keys().next().value;
if (oldestToolCallId !== void 0) {
pendingMessagingCalls.delete(oldestToolCallId);
didSendViaMessagingTool = true;
}
}
pendingMessagingCalls.set(event.toolCallId, {
toolName,
args: event.args,
target: extractCliMessagingTarget(context, toolName, event.args)
});
};
const handleCliToolResult = (event) => {
const activeTool = activeCliTools.get(event.toolCallId);
activeCliTools.delete(event.toolCallId);
retireCliLoopbackCorrelation(event.toolCallId, activeTool);
const pending = pendingMessagingCalls.get(event.toolCallId);
if (pending) {
pendingMessagingCalls.delete(event.toolCallId);
commitMessagingToolResult({
toolName: pending.toolName,
target: pending.target,
args: pending.args,
result: event.result,
isError: event.isError
});
}
};
const resolveCliLoopbackTerminalOutcome = (toolCallId) => {
const activeTool = activeCliTools.get(toolCallId);
if (activeTool?.loopbackAmbiguous) return { outcome: "unknown" };
return activeTool?.loopbackCall?.outcome;
};
const finishDeliveryTracking = async (params) => {
try {
if (!gatewayCaptureKey && pendingMessagingCalls.size > 0) {
const calls = Array.from(pendingMessagingCalls.values());
if ((await Promise.all(calls.map(isPreparedInternalSourceReply))).some((internal) => !internal)) {
didSendViaMessagingTool = true;
params.recordRunError(/* @__PURE__ */ new Error("CLI JSONL message tool call remained unresolved after exit"));
} else params.recordRunError(/* @__PURE__ */ new Error("CLI JSONL source reply call remained unresolved after exit"));
}
if (!gatewayCaptureKey) return;
if (await waitForMcpLoopbackToolCallCaptureIdle(gatewayCaptureKey, {
timeoutMs: CLI_MCP_DELIVERY_DRAIN_GRACE_MS,
admissionGraceMs: CLI_MCP_REQUEST_ADMISSION_GRACE_MS
})) return;
if (params.useManagedClaudeLiveSession) await closeCliLiveSession(context, "mcp-capture-rotation");
const internalCount = (await Promise.all(Array.from(inFlightPreparedMessagingCalls).map(isPreparedInternalSourceReply))).filter(Boolean).length;
if (inFlightUnclassifiedMcpRequests > 0 || inFlightMessagingToolCalls > internalCount) {
didSendViaMessagingTool = true;
params.recordRunError(/* @__PURE__ */ new Error("CLI message tool call remained in flight after exit"));
} else if (inFlightMessagingToolCalls > 0) params.recordRunError(/* @__PURE__ */ new Error("CLI source reply call remained in flight after exit"));
} catch (error) {
if (pendingMessagingCalls.size > 0 || inFlightUnclassifiedMcpRequests > 0 || inFlightMessagingToolCalls > 0) didSendViaMessagingTool = true;
params.recordRunError(error);
}
};
const finalizeCapture = (finalizeParsedTools) => {
try {
finalizeParsedTools();
} finally {
if (gatewayCaptureKey) try {
context.preparedBackend.mcpClientGrantCapture?.deactivate(gatewayCaptureKey);
} finally {
clearMcpLoopbackToolCallCapture(gatewayCaptureKey);
}
}
};
const evidence = () => ({
didSendViaMessagingTool,
didDeliverSourceReplyViaMessageTool,
sourceReplyDelivered,
messagingToolSentTexts,
messagingToolSentMediaUrls,
messagingToolSentTargets,
messagingToolSourceReplyPayloads,
toolMediaUrls,
toolAudioAsVoice,
toolTrustedLocalMedia,
acceptedSessionSpawns
});
return {
beginGatewayCapture,
handleCliToolUseStart,
handleCliToolResult,
resolveCliLoopbackTerminalOutcome,
finishDeliveryTracking,
finalizeCapture,
withExecutionEvidence(output) {
const current = evidence();
return {
...output,
...yielded ? { yielded: true } : {},
...yieldAcknowledgment ? { yieldAcknowledgment } : {},
...current.didSendViaMessagingTool ? { didSendViaMessagingTool: true } : {},
...current.didDeliverSourceReplyViaMessageTool ? { didDeliverSourceReplyViaMessageTool: true } : {},
...current.sourceReplyDelivered ? { sourceReplyDelivered: true } : {},
...current.messagingToolSentTexts.length > 0 ? { messagingToolSentTexts: current.messagingToolSentTexts.slice() } : {},
...current.messagingToolSentMediaUrls.length > 0 ? { messagingToolSentMediaUrls: current.messagingToolSentMediaUrls.slice() } : {},
...current.messagingToolSentTargets.length > 0 ? { messagingToolSentTargets: current.messagingToolSentTargets.slice() } : {},
...current.messagingToolSourceReplyPayloads.length > 0 ? { messagingToolSourceReplyPayloads: current.messagingToolSourceReplyPayloads.slice() } : {},
...current.toolMediaUrls.length > 0 ? { toolMediaUrls: current.toolMediaUrls.slice() } : {},
...current.toolAudioAsVoice ? { toolAudioAsVoice: true } : {},
...current.toolTrustedLocalMedia ? { toolTrustedLocalMedia: true } : {},
...current.acceptedSessionSpawns.length > 0 ? { acceptedSessionSpawns: current.acceptedSessionSpawns.slice() } : {}
};
},
attachDeliveryEvidence(error) {
return attachCliMessagingDeliveryEvidence(error, evidence());
}
};
}
//#endregion
//#region src/agents/cli-runner/model-call-diagnostics.ts
/** Trusted turn-level model-call diagnostics for the Claude Code CLI runtime. */
const MAX_CAPTURED_CONTENT_BYTES = 131072;
const FALLBACK_RESPONSE_RESERVE_BYTES = 16384;
const MAX_CAPTURED_OUTPUT_MESSAGES = 200;
const MAX_CAPTURED_OUTPUT_BLOCKS = 200;
const TRUNCATED_CONTENT_SUFFIX = "...(truncated)";
const MAX_CAPTURED_OUTPUT_STRUCTURE_BYTES = Buffer.byteLength(JSON.stringify(Array.from({ length: MAX_CAPTURED_OUTPUT_MESSAGES }, () => ({
role: "assistant",
content: [{
type: "tool_call",
name: "",
id: ""
}],
stopReason: ""
}))), "utf8");
function serializedStringContentBytes(value) {
return Buffer.byteLength(JSON.stringify(value), "utf8") - 2;
}
const TRUNCATED_CONTENT_SUFFIX_BYTES = serializedStringContentBytes(TRUNCATED_CONTENT_SUFFIX);
function truncateSerializedStringSafe(value, maxBytes) {
if (maxBytes <= 0) return "";
if (serializedStringContentBytes(value) <= maxBytes) return value;
let low = 0;
let high = Math.min(value.length, maxBytes);
let captured = "";
while (low <= high) {
const middle = Math.floor((low + high) / 2);
const candidate = truncateUtf16Safe(value, middle);
if (serializedStringContentBytes(candidate) <= maxBytes) {
captured = candidate;
low = middle + 1;
} else high = middle - 1;
}
return captured;
}
function releaseFallbackReserve(budget) {
budget.remainingBytes += budget.fallbackReserveBytes;
budget.remainingItems += budget.fallbackReserveItems;
budget.fallbackReserveBytes = 0;
budget.fallbackReserveItems = 0;
}
function captureTextWithinBudget(value, budget) {
if (budget.remainingBytes <= 0) {
budget.truncated = true;
return;
}
const valueBytes = serializedStringContentBytes(value);
if (valueBytes <= budget.remainingBytes) {
budget.remainingBytes -= valueBytes;
return value;
}
const suffix = truncateSerializedStringSafe(TRUNCATED_CONTENT_SUFFIX, budget.remainingBytes);
const captured = `${truncateSerializedStringSafe(value, Math.max(0, budget.remainingBytes - serializedStringContentBytes(suffix)))}${suffix}`;
budget.remainingBytes -= serializedStringContentBytes(captured);
budget.truncated = true;
return captured;
}
function captureBoundedText(value) {
return captureTextWithinBudget(value, {
remainingBytes: MAX_CAPTURED_CONTENT_BYTES,
remainingItems: 1,
fallbackReserveBytes: 0,
fallbackReserveItems: 0,
truncated: false
}) ?? "";
}
function assistantContentBlock(block, budget) {
if (!isRecord(block)) return;
if (block.type === "text" && typeof block.text === "string" && block.text.length > 0) {
const text = captureTextWithinBudget(block.text, budget);
return text === void 0 ? void 0 : {
type: "text",
text
};
}
if (block.type === "thinking" && typeof block.thinking === "string") {
const thinking = captureTextWithinBudget(block.thinking, budget);
return thinking === void 0 ? void 0 : {
type: "thinking",
thinking
};
}
if ((block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") && typeof block.name === "string") {
const name = captureTextWithinBudget(block.name, budget);
if (name === void 0) return;
const id = typeof block.id === "string" ? captureTextWithinBudget(block.id, budget) : void 0;
return {
type: "tool_call",
name,
...id !== void 0 ? { id } : {}
};
}
}
function isCapturableAssistantContentBlock(block) {
if (!isRecord(block)) return false;
return block.type === "text" && typeof block.text === "string" || block.type === "thinking" && typeof block.thinking === "string" || (block.type === "tool_use" || block.type === "server_tool_use" || block.type === "mcp_tool_use") && typeof block.name === "string";
}
function isTextAssistantContentBlock(block) {
return isRecord(block) && block.type === "text" && typeof block.text === "string" && block.text.length > 0;
}
function assistantMessageHasText(message) {
if (!isRecord(message)) return false;
if (typeof message.content === "string") return message.content.length > 0;
if (!Array.isArray(message.content)) return false;
const limit = Math.min(message.content.length, MAX_CAPTURED_OUTPUT_BLOCKS);
for (let index = 0; index < limit; index += 1) if (isTextAssistantContentBlock(message.content[index])) return true;
return false;
}
function normalizeClaudeAssistantMessage(message, budget) {
if (!isRecord(message)) return;
const content = [];
if (typeof message.content === "string") {
if (message.content.length === 0) return;
releaseFallbackReserve(budget);
const text = captureTextWithinBudget(message.content, budget);
if (text !== void 0 && budget.remainingItems > 0) {
content.push({
type: "text",
text
});
budget.remainingItems -= 1;
} else if (text !== void 0) budget.truncated = true;
} else if (Array.isArray(message.content)) {
const sourceBlocks = message.content.slice(0, MAX_CAPTURED_OUTPUT_BLOCKS);
if (sourceBlocks.length < message.content.length) budget.truncated = true;
for (const [index, sourceBlock] of sourceBlocks.entries()) {
if (isTextAssistantContentBlock(sourceBlock)) releaseFallbackReserve(budget);
const block = assistantContentBlock(sourceBlock, budget);
if (block) {
if (budget.remainingItems > 0) {
content.push(block);
budget.remainingItems -= 1;
} else budget.truncated = true;
}
if (budget.remainingBytes <= 0 || budget.remainingItems <= 0) {
if (sourceBlocks.slice(index + 1).some(isCapturableAssistantContentBlock)) budget.truncated = true;
break;
}
}
}
if (content.length === 0) return;
const stopReason = typeof message.stop_reason === "string" ? captureTextWithinBudget(message.stop_reason, budget) : void 0;
return {
role: "assistant",
content,
...stopReason !== void 0 ? { stopReason } : {}
};
}
function hasTextContent(messages) {
return messages.some((message) => Array.isArray(message.content) && message.content.some((block) => isRecord(block) && block.type === "text" && typeof block.text === "string" && block.text.length > 0));
}
function appendOutputTruncationMarker(messages) {
const marker = {
type: "text",
text: TRUNCATED_CONTENT_SUFFIX
};
if (messages.length < MAX_CAPTURED_OUTPUT_MESSAGES) {
messages.push({
role: "assistant",
content: [marker]
});
return;
}
const lastIndex = messages.length - 1;
const lastMessage = messages[lastIndex];
messages[lastIndex] = {
...lastMessage,
content: [...Array.isArray(lastMessage?.content) ? lastMessage.content : [], marker]
};
}
function privateData(params) {
if (!params.modelContent && !params.errorMessage) return;
return {
...params.errorMessage ? { errorMessage: params.errorMessage } : {},
...params.modelContent ? { modelContent: params.modelContent } : {}
};
}
function failureKindForClaudeCli(error, abortSignal) {
if (isFailoverError(error) && error.reason === "timeout") return "timeout";
const inferred = diagnosticErrorFailureKind(error);
if (inferred) return inferred;
return abortSignal?.aborted ? "aborted" : void 0;
}
function usageField(usage) {
return usage ? { usage } : {};
}
/** Creates one exactly-once Claude CLI model-call lifecycle for a prepared turn. */
function createClaudeCliModelCallDiagnostics(params) {
if (params.context.backendResolved.id !== "claude-cli" || !areDiagnosticsEnabledForProcess() || !hasInternalDiagnosticEventListeners()) return;
const now = params.now ?? (() => Date.now());
const capture = resolveDiagnosticModelContentCapturePolicy(params.context.params.config ?? params.context.contextEngineConfig);
const contextWindow = params.context.contextWindowInfo;
const trace = freezeDiagnosticTraceContext(createDiagnosticTraceContextFromActiveScope());
const baseFields = {
runId: params.context.params.runId,
callId: `${params.context.params.runId}:claude-cli:${crypto.randomUUID()}`,
...params.context.params.sessionKey ? { sessionKey: params.context.params.sessionKey } : {},
sessionId: params.context.params.sessionId,
provider: params.context.backendResolved.modelProvider ?? params.context.params.modelProvider ?? "anthropic",
model: params.context.normalizedModel,
api: "claude-code",
transport: params.transport,
observationUnit: "turn",
...contextWindow ? {
contextTokenBudget: contextWindow.tokens,
contextWindowSource: contextWindow.source,
...contextWindow.referenceTokens ? { contextWindowReferenceTokens: contextWindow.referenceTokens } : {}
} : {},
promptStats: {
inputMessagesCount: 1,
inputMessagesChars: params.prompt.length,
...params.systemPrompt ? { systemPromptChars: params.systemPrompt.length } : {},
totalChars: params.prompt.length + (params.systemPrompt?.length ?? 0)
},
trace
};
const capturedAssistantMessages = [];
const outputContentBudget = {
remainingBytes: MAX_CAPTURED_CONTENT_BYTES - MAX_CAPTURED_OUTPUT_STRUCTURE_BYTES - TRUNCATED_CONTENT_SUFFIX_BYTES - FALLBACK_RESPONSE_RESERVE_BYTES,
remainingItems: 198,
fallbackReserveBytes: FALLBACK_RESPONSE_RESERVE_BYTES,
fallbackReserveItems: 1,
truncated: false
};
let started = false;
let terminalEmitted = false;
let startedAt = 0;
let requestPayloadBytes;
let responseStreamBytes = 0;
let firstCliOutputAt;
let observedUsage;
let observedTerminalUsage;
const baseModelContent = () => {
if (!capture.anyModelContent) return;
const content = {
...capture.inputMessages ? { inputMessages: cloneDiagnosticContentValue([{
role: "user",
content: [{
type: "text",
text: captureBoundedText(params.prompt)
}]
}]) } : {},
...capture.systemPrompt && params.systemPrompt ? { systemPrompt: captureBoundedText(params.systemPrompt) } : {}
};
return Object.keys(content).length > 0 ? content : void 0;
};
const outputMessages = (output) => {
const messages = capturedAssistantMessages.slice();
const responseText = output?.rawText ?? output?.text;
if (!hasTextContent(messages) && responseText && messages.length < MAX_CAPTURED_OUTPUT_MESSAGES) {
const fallback = normalizeClaudeAssistantMessage({ content: responseText }, outputContentBudget);
if (fallback) messages.push(fallback);
}
if (outputContentBudget.truncated) appendOutputTruncationMarker(messages);
return cloneDiagnosticContentValue(messages);
};
const completedModelContent = (output) => {
const base = baseModelContent();
if (!capture.outputMessages) return base;
return {
...base,
outputMessages: outputMessages(output)
};
};
const sizeTimingFields = () => ({
...requestPayloadBytes !== void 0 ? { requestPayloadBytes } : {},
...responseStreamBytes > 0 ? { responseStreamBytes } : {},
...firstCliOutputAt !== void 0 ? { timeToFirstByteMs: Math.max(0, firstCliOutputAt - startedAt) } : {}
});
return {
emitStarted: () => {
if (started) return;
started = true;
startedAt = now();
emitTrustedDiagnosticEventWithPrivateData({
type: "model.call.started",
...baseFields
}, privateData({ modelContent: baseModelContent() }));
},
observeRequestPayload: (payload) => {
requestPayloadBytes = Buffer.byteLength(payload, "utf8");
},
observeCliOutput: (chunk, stream, knownByteLength) => {
if (!chunk) return;
firstCliOutputAt ??= now();
if (stream === "stdout") responseStreamBytes += knownByteLength ?? Buffer.byteLength(chunk, "utf8");
},
observeAssistantMessage: (message) => {
if (!capture.outputMessages || (outputContentBudget.remainingBytes <= 0 || outputContentBudget.remainingItems <= 0) && !(outputContentBudget.fallbackReserveItems > 0 && assistantMessageHasText(message)) || capturedAssistantMessages.length >= 199) {
if (capture.outputMessages) outputContentBudget.truncated = true;
return;
}
const normalized = normalizeClaudeAssistantMessage(message, outputContentBudget);
if (normalized) capturedAssistantMessages.push(normalized);
},
observeUsage: (usage, terminal) => {
observedUsage = usage;
if (terminal) observedTerminalUsage = usage;
},
emitCompleted: (output) => {
if (!started || terminalEmitted) return;
terminalEmitted = true;
emitTrustedDiagnosticEventWithPrivateData({
type: "model.call.completed",
...baseFields,
durationMs: Math.max(0, now() - startedAt),
...sizeTimingFields(),
...usageField(output.diagnosticUsage ?? observedTerminalUsage ?? output.usage ?? observedUsage)
}, privateData({ modelContent: completedModelContent(output) }));
},
emitError: (error) => {
if (!started || terminalEmitted) return;
terminalEmitted = true;
const failureKind = failureKindForClaudeCli(error, params.context.params.abortSignal);
emitTrustedDiagnosticEventWithPrivateData({
type: "model.call.error",
...baseFields,
durationMs: Math.max(0, now() - startedAt),
errorCategory: (isFailoverError(error) ? error.reason : void 0) ?? failureKind ?? diagnosticErrorCategory(error),
...failureKind ? { failureKind } : {},
...sizeTimingFields(),
...usageField(observedTerminalUsage ?? observedUsage)
}, privateData({
modelContent: completedModelContent(),
errorMessage: diagnosticErrorMessage(error)
}));
}
};
}
//#endregion
//#region src/agents/cli-runner/execute.ts
/** Executes prepared CLI backend runs and owns their queue and resource lifecycle. */
function normalizeCliBackendThinkingLevel(level) {
return level === "ultra" ? "max" : level;
}
function exactToolAvailabilityError(params) {
if (!params.isolatedCompletion) return new Error(params.message);
const error = new Error(params.message);
error.name = "IsolatedCompletionRuntimeError";
error.code = params.code;
return error;
}
function assertExactToolAvailabilityRuntimeVersion(params) {
const artifact = params.executableIdentity?.runtimeArtifact;
const packageVersion = artifact?.kind === "package-tree" ? artifact.packageVersion : void 0;
const parsedVersion = packageVersion ? parse(packageVersion) : null;
const prereleaseChannel = parsedVersion?.prerelease[0];
const minimumVersion = parsedVersion?.prerelease.length === 0 ? params.policy?.stableMinimum : typeof prereleaseChannel === "string" ? params.policy?.prereleaseMinimums?.[prereleaseChannel] : void 0;
const comparison = packageVersion && minimumVersion ? compareValidSemver(packageVersion, minimumVersion) : null;
if (comparison !== null && comparison >= 0) return;
throw exactToolAvailabilityError({
code: "unsupported",
isolatedCompletion: params.isolatedCompletion,
message: `CLI backend ${params.backendId} requires a supported package version for exact per-run tool availability${minimumVersion ? ` (requires >=${minimumVersion}` : " (unsupported release line"}${packageVersion ? `; found ${packageVersion})` : ")"}`
});
}
/** Executes a prepared CLI run context and returns normalized CLI output. */
async function executePreparedCliRun(context, cliSessionIdToUse, options) {
const params = context.params;
const assertCurrent = createCliRunCurrentAssertion(params);
assertCurrent();
const backend = context.preparedBackend.backend;
const executionTarget = context.executionTarget;
const localProcessEnv = installationTargetEnv(getInstallationTarget());
if (localProcessEnv && executionTarget.kind === "node") throw new Error(LOCAL_INSTALLATION_TARGET_UNSUPPORTED);
const nodePlacement = executionTarget.kind === "node" ? executionTarget.placement : null;
const usePluginOwnedExecution = executionTarget.kind === "plugin";
const { sessionId: resolvedSessionId, isNew } = resolveSessionIdToSend({
backend,
cliSessionId: cliSessionIdToUse
});
const useResume = Boolean(cliSessionIdToUse && resolvedSessionId && backend.resumeArgs && backend.resumeArgs.length > 0);
const resendSystemPromptForSoftResume = context.reusableCliSession.mode === "reuse-with-drift";
const systemPromptArg = resolveSystemPromptUsage({
backend,
isNewSession: isNew || resendSystemPromptForSoftResume,
systemPrompt: context.systemPrompt
});
const shouldSendSystemPrompt = systemPromptArg && (!useResume || backend.systemPromptWhen === "always" || resendSystemPromptForSoftResume);
const systemPromptFile = !nodePlacement && !usePluginOwnedExecution && shouldSendSystemPrompt ? await executeDeps.writeCliSystemPromptFile({
backend,
systemPrompt: systemPromptArg
}) : void 0;
const nodeSystemPrompt = nodePlacement && shouldSendSystemPrompt ? systemPromptArg : void 0;
const basePrompt = cliSessionIdToUse ? params.prompt : context.openClawHistoryPrompt ?? params.prompt;
let prompt = params.controlOperation !== void 0 ? basePrompt : applyPluginTextReplacements(basePrompt, context.backendResolved.textTransforms?.input);
const promptContext = context.promptContext ? {
...context.promptContext.prependContext ? { prependContext: applyPluginTextReplacements(context.promptContext.prependContext, context.backendResolved.textTransforms?.input) } : {},
...context.promptContext.appendContext ? { appendContext: applyPluginTextReplacements(context.promptContext.appendContext, context.backendResolved.textTransforms?.input) } : {}
} : void 0;
if (nodePlacement && ((params.images?.length ?? 0) > 0 || (params.mediaImageLayout ? params.mediaImageLayout.slots.length > 0 : hasHydratableMediaImages(params.media)) || (params.imagePrompt ? detectImageReferences(params.imagePrompt).length > 0 : false))) throw new Error("paired-node Claude CLI sessions do not support attachments or images");
const imageTurnEntryId = isClaudeCliBackendId(context.backendResolved.id) ? params.userTurnTranscriptRecorder?.getAdmissionReceipt()?.entryId : void 0;
const imagePayload = nodePlacement ? {
prompt,
imagePaths: [],
cleanupImages: async () => {}
} : await prepareCliPromptImagePayload({
backend,
prompt,
imagePrompt: params.imagePrompt,
workspaceDir: context.workspaceDir,
localRoots: getAgentScopedMediaLocalRoots(params.config ?? {}, params.agentId),
images: params.images,
imageOrder: params.imageOrder,
mediaImageLayout: params.mediaImageLayout,
media: params.media,
...imageTurnEntryId ? { imageTurnKey: hashCliImageTurnEntryId(imageTurnEntryId) } : {}
});
prompt = imagePayload.prompt;
const promptInputBackend = params.controlOperation === "compact" && context.backendResolved.manualCompaction ? {
...backend,
input: context.backendResolved.manualCompaction.input
} : backend;
const { argsPrompt, stdin } = resolvePromptInput({
backend: promptInputBackend,
prompt
});
const baseArgs = useResume ? backend.resumeArgs ?? backend.args ?? [] : backend.args ?? [];
const resolvedArgs = useResume ? baseArgs.map((entry) => entry.replaceAll("{sessionId}", resolvedSessionId ?? "")) : baseArgs;
const baseArgsWithSkills = !nodePlacement && context.claudeSkillsPluginArgs.length > 0 ? [...resolvedArgs, ...context.claudeSkillsPluginArgs] : resolvedArgs;
const resolvedExecutionArgs = context.backendResolved.resolveExecutionArgs?.({
config: params.config,
workspaceDir: context.workspaceDir,
provider: params.provider,
modelId: context.modelId,
authProfileId: context.effectiveAuthProfileId,
thinkingLevel: normalizeCliBackendThinkingLevel(params.thinkLevel),
executionMode: params.executionMode ?? "agent",
toolAvailability: params.cliToolAvailability && nodePlacement ? {
native: params.cliToolAvailability.native,
openClaw: []
} : params.cliToolAvailability,
useResume,
baseArgs: baseArgsWithSkills
});
if (params.cliToolAvailability && context.backendResolved.toolAvailabilityEnforcement === "execution-args" && !resolvedExecutionArgs) throw new Error(`CLI backend ${context.backendResolved.id} did not enforce exact per-run tool availability`);
const executionBaseArgs = nodePlacement ? stripGatewayLocalClaudeArgs(resolvedExecutionArgs ?? baseArgsWithSkills) : resolvedExecutionArgs ?? baseArgsWithSkills;
const args = buildCliArgs({
backend: nodePlacement ? {
...backend,
systemPromptArg: void 0,
systemPromptFileArg: void 0
} : backend,
baseArgs: Array.from(executionBaseArgs),
modelId: context.normalizedModel,
sessionId: resolvedSessionId,
systemPrompt: nodePlacement || usePluginOwnedExecution ? void 0 : systemPromptArg,
systemPromptFilePath: systemPromptFile?.filePath,
imagePaths: imagePayload.imagePaths,
promptArg: argsPrompt,
useResume,
forkResume: params.forkCliSessionOnResume,
resumeAt: params.cliSessionResumeAt,
sendSystemPromptOnResume: resendSystemPromptForSoftResume
});
const cliLiveOwnerKey = buildCliLiveOwnerKey({
agentAccountId: params.agentAccountId,
agentId: params.agentId,
authProfileId: context.effectiveAuthProfileId,
sessionId: params.sessionId,
sessionKey: params.sessionKey
});
const queueKey = resolveCliRunQueueKey({
backendId: context.backendResolved.id,
liveSession: backend.liveSession,
serialize: backend.serialize,
runId: params.runId,
workspaceDir: context.workspaceDir,
cliSessionId: useResume ? resolvedSessionId : void 0,
ownerKey: cliLiveOwnerKey
});
const useManagedClaudeLiveSession = usePluginOwnedExecution && acceptsCliLiveSession(context) && !params.onSuccessfulAuthBinding;
const diagnostics = createClaudeCliModelCallDiagnostics({
context,
prompt: composeCliPromptContext(prompt, promptContext),
systemPrompt: systemPromptArg ?? void 0,
transport: nodePlacement ? "paired-node-cli" : useManagedClaudeLiveSession ? "stdio-live" : "stdio"
});
let completedOutput;
let executionError;
let outerCleanupError;
let forkResumeClaimed = false;
let forkSuccessorObserved = false;
let forkSuccessorPersistence;
const observeForkSuccessor = (sessionId) => {
if (forkSuccessorObserved || !forkResumeClaimed || !resolvedSessionId || sessionId === resolvedSessionId) return;
forkSuccessorObserved = true;
forkSuccessorPersistence = params.persistCliSessionForkSuccessor?.(sessionId);
forkSuccessorPersistence?.catch(() => void 0);
};
const finishForkSuccessorPersistence = async () => {
try {
await forkSuccessorPersistence;
} catch (error) {
forkSuccessorObserved = false;
throw error;
}
};
const cleanupOuterResource = async (cleanup) => {
try {
await cleanup?.();
} catch (error) {
if (completedOutput?.didSendViaMessagingTool) {
cliBackendLog.warn(`CLI outer resource cleanup failed after confirmed message delivery: ${formatErrorMessage(error)}`);
return;
}
if (executionError !== void 0) {
cliBackendLog.warn(`CLI outer resource cleanup also failed after run error: ${formatErrorMessage(error)}`);
return;
}
throw error;
}
};
const executeAttempt = async () => {
assertCurrent();
await context.preparedBackend.beforeExecution?.();
assertCurrent();
const cliTurnStartedAt = Date.now();
const restoreSkillEnv = params.skillsSnapshot && !params.controlOperation ? applySkillEnvOverridesFromSnapshot({
snapshot: params.skillsSnapshot,
config: params.config
}) : void 0;
let cleanupMcpCaptureAttempt;
let runOutput;
let runError;
let runFailed = false;
const recordRunError = (error) => {
if (!runFailed) {
runFailed = true;
runError = error;
}
};
const toolTracking = createCliToolTracking(context);
const events = createCliEventHandlers({
context,
toolTracking,
getRunState: () => ({
failed: runFailed,
error: runError
})
});
try {
cliBackendLog.info(buildCliExecLogLine({
provider: params.provider,
model: context.normalizedModel,
promptChars: basePrompt.length,
trigger: params.trigger,
useResume,
cliSessionId: cliSessionIdToUse,
resolvedSessionId,
reusableSession: context.reusableCliSession,
hasHistoryPrompt: Boolean(context.openClawHistoryPrompt)
}));
const logOutputText = isTruthyEnvValue(process.env["OPENCLAW_CLI_BACKEND_LOG_OUTPUT"]) || isTruthyEnvValue(process.env["OPENCLAW_CLAUDE_CLI_LOG_OUTPUT"]);
const outputMode = useResume ? backend.resumeOutput ?? backend.output : backend.output;
const initialGatewayCaptureKey = nodePlacement || !context.mcpDeliveryCapture ? void 0 : crypto.randomUUID();
const mcpCaptureAttempt = nodePlacement ? {
env: {},
cleanup: void 0
} : await prepareCliBundleMcpCaptureAttempt({
mode: context.backendResolved.bundleMcpMode,
backend,
env: context.preparedBackend.env,
captureKey: initialGatewayCaptureKey
});
cleanupMcpCaptureAttempt = mcpCaptureAttempt.cleanup;
const preparedBackendEnv = context.preparedBackend.env ?? {};
const selectedClaudeClearEnv = Boolean(context.preparedBackend.secretInput) || [...CLAUDE_SELECTED_AUTH_ENV_KEYS].some((key) => Object.hasOwn(preparedBackendEnv, key)) ? new Set(backend.clearEnv ?? []) : void 0;
const backendEnv = {
...Object.fromEntries(Object.entries(backend.env ?? {}).filter(([key]) => !selectedClaudeClearEnv?.has(key))),
...preparedBackendEnv
};
const nodeEnvEntries = Object.entries(preparedBackendEnv).filter(([key]) => NODE_CLAUDE_FORWARD_ENV_KEYS.has(key));
const nodeEnv = nodePlacement ? {
...Object.fromEntries(nodeEnvEntries),
...resolveNodeClaudeAuthEnv(context)
} : void 0;
const nodeRuntimeClearEnv = nodePlacement ? [...NODE_CLAUDE_FORWARD_ENV_KEYS].filter((key) => backend.clearEnv?.includes(key)) : [];
const nodeClearEnv = [...selectedClaudeClearEnv ?? [], ...nodeRuntimeClearEnv].filter((key, index, values) => values.indexOf(key) === index);
const env = sanitizeHostExecEnv({
baseEnv: process.env,
blockPathOverrides: true
});
const preservedEnv = parseCliBackendPreserveEnv(process.env[CLI_BACKEND_PRESERVE_ENV]);
for (const key of backend.clearEnv ?? []) if (!preservedEnv.has(key) || selectedClaudeClearEnv?.has(key)) delete env[key];
if (Object.keys(backendEnv).length > 0) Object.assign(env, sanitizeHostExecEnv({
baseEnv: {},
overrides: backendEnv,
blockPathOverrides: true
}));
Object.assign(env, mcpCaptureAttempt.env, localProcessEnv);
delete env.CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST;
let executionCommand = backend.command;
let executionArgv0;
let executionLeadingArgv = [];
context.runtimeOwnerFingerprint = void 0;
context.runtimeArtifactFingerprint = void 0;
const exactToolAvailabilityVersionPolicy = params.cliToolAvailability ? context.backendResolved.runtimeArtifact?.exactToolAvailabilityVersionPolicy : void 0;
if (exactToolAvailabilityVersionPolicy && nodePlacement) throw exactToolAvailabilityError({
code: "unsupported",
isolatedCompletion: params.isolatedCompletion === true,
message: `CLI backend ${context.backendResolved.id} cannot verify its exact tool-availability runtime on a paired node`
});
if ((params.onSuccessfulAuthBinding || exactToolAvailabilityVersionPolicy) && !nodePlacement) {
const executableIdentity = await resolveCliExecutableIdentity({
command: backend.command,
cwd: context.cwd ?? context.workspaceDir,
env,
...context.backendResolved.runtimeArtifact ? { runtimeArtifact: context.backendResolved.runtimeArtifact } : {}
});
if (!executableIdentity) throw exactToolAvailabilityError({
code: "runtime-unavailable",
isolatedCompletion: params.isolatedCompletion === true && exactToolAvailabilityVersionPolicy !== void 0,
message: `CLI backend ${context.backendResolved.id} executable cannot be bound to one durable absolute owner`
});
if (exactToolAvailabilityVersionPolicy) assertExactToolAvailabilityRuntimeVersion({
backendId: context.backendResolved.id,
policy: exactToolAvailabilityVersionPolicy,
executableIdentity,
isolatedCompletion: params.isolatedCompletion === true
});
executionCommand = executableIdentity.invocation.command;
executionArgv0 = executableIdentity.invocation.argv0;
executionLeadingArgv = executableIdentity.invocation.leadingArgv;
context.runtimeArtifactFingerprint = fingerprintCliRuntimeArtifact({
provider: params.provider,
backendId: context.backendResolved.id,
executableIdentity
});
if (params.onSuccessfulAuthBinding && !context.authBindingFingerprint) context.runtimeOwnerFingerprint = await resolveCliRuntimeOwnerFingerprint({
provider: params.provider,
config: params.config ?? context.contextEngineConfig,
...context.agentDir ? { agentDir: context.agentDir } : {},
agentId: params.agentId,
runtimeOwnerId: context.backendResolved.id,
...context.effectiveAuthProfileId ? { authProfileId: context.effectiveAuthProfileId } : {},
...context.authBindingSkipsLocalCredential ? { skipLocalCredential: true } : {},
runtimeArtifactFingerprint: context.runtimeArtifactFingerprint
});
}
if (logOutputText) logCliInvocation({
args,
command: executionCommand,
env,
systemPromptArg: backend.systemPromptArg,
modelArg: backend.modelArg,
imageArg: backend.imageArg,
argsPrompt,
log: (message) => cliBackendLog.info(message)
});
const runTimeoutOverrideMs = resolveCliRunTimeoutOverrideMs({
config: params.config,
lane: params.lane,
timeoutMs: params.timeoutMs,
runTimeoutOverrideMs: params.runTimeoutOverrideMs
});
const noOutputTimeoutMs = resolveCliNoOutputTimeoutMs({
backend,
timeoutMs: params.timeoutMs,
expectedQuiet: params.controlOperation === "compact",
runTimeoutOverrideMs,
useResume,
trigger: params.trigger
});
if (!useManagedClaudeLiveSession) toolTracking.beginGatewayCapture(initialGatewayCaptureKey);
runOutput = await executeCliProcess({
context,
assertCurrent,
backend,
deps: executeDeps,
events,
toolTracking,
diagnostics,
nodePlacement,
nodeSystemPrompt,
nodeEnv: nodeEnv && Object.keys(nodeEnv).length > 0 ? nodeEnv : void 0,
nodeClearEnv: nodeClearEnv.length > 0 ? nodeClearEnv : void 0,
useManagedClaudeLiveSession,
initialGatewayCaptureKey,
useResume,
cliSessionIdToUse,
resolvedSessionId,
executionCommand,
executionArgv0,
executionLeadingArgv,
executionArgs: args,
env,
prompt,
...promptContext ? { promptContext } : {},
argsPrompt,
stdin,
noOutputTimeoutMs,
outputMode,
logOutputText,
cliTurnStartedAt,
observeForkSuccessor,
options
});
} catch (error) {
recordRunError(error);
} finally {
await toolTracking.finishDeliveryTracking({
useManagedClaudeLiveSession,
recordRunError
});
toolTracking.finalizeCapture(events.finalizeParsedTools);
try {
await cleanupMcpCaptureAttempt?.();
} catch (error) {
recordRunError(error);
}
try {
restoreSkillEnv?.();
} catch (error) {
recordRunError(error);
}
}
if (runFailed) throw toolTracking.attachDeliveryEvidence(runError);
if (!runOutput) throw new Error("CLI run completed without output");
return toolTracking.withExecutionEvidence({
...runOutput,
toolSummary: events.getToolSummary()
});
};
try {
completedOutput = await enqueueCliRun(queueKey, async () => {
assertCurrent();
if (params.lifecycleGeneration) assertAgentRunLifecycleGenerationCurrent(params.lifecycleGeneration);
diagnostics?.emitStarted();
if (params.forkCliSessionOnResume && useResume) {
if (!params.persistCliSessionForkSuccessor) throw new Error("CLI session fork successor persistence is unavailable");
forkResumeClaimed = await params.claimCliSessionFork?.() === true;
if (!forkResumeClaimed) throw new Error("CLI session fork marker is no longer available");
await closeCliLiveSession(context, "restart");
}
return await executeAttempt();
});
if (completedOutput.sessionId) observeForkSuccessor(completedOutput.sessionId);
await finishForkSuccessorPersistence();
if (forkResumeClaimed && !forkSuccessorObserved) {
await params.restoreCliSessionFork?.();
forkResumeClaimed = false;
throw new Error("forked CLI session did not report a successor session id");
}
} catch (error) {
executionError = error;
diagnostics?.emitError(error);
let failure = error;
try {
await finishForkSuccessorPersistence();
} catch (persistenceError) {
failure = new AggregateError([error, persistenceError], "CLI turn failed and its fork successor could not be persisted", { cause: error });
}
if (forkResumeClaimed && !forkSuccessorObserved) await params.restoreCliSessionFork?.();
throw failure;
} finally {
try {
await cleanupOuterResource(systemPromptFile?.cleanup);
await cleanupOuterResource(imagePayload.cleanupImages);
} catch (error) {
outerCleanupError = toErrorObject(error, "CLI outer resource cleanup failed");
}
}
if (outerCleanupError) {
options?.onPhase?.("cleanup");
diagnostics?.emitError(outerCleanupError);
throw outerCleanupError;
}
if (!completedOutput) {
const error = /* @__PURE__ */ new Error("CLI run completed without output");
diagnostics?.emitError(error);
throw error;
}
diagnostics?.emitCompleted(completedOutput);
return completedOutput;
}
//#endregion
export { executePreparedCliRun as t };