Download src/agents/cli-runner/execute-events.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 14 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/cli-runner/execute-events.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/agents/cli-runner/execute-events.ts
-
curl -L -o execute-events.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/agents/cli-runner/execute-events.ts
14 kB
| import { emitAgentEvent } from "../../infra/agent-events.js"; | |
| import { emitTrustedDiagnosticEvent } from "../../infra/diagnostic-events.js"; | |
| import type { | |
| CliCompactionDelta, | |
| CliStreamingDelta, | |
| CliThinkingDelta, | |
| CliThinkingProgress, | |
| CliToolUseStartDelta, | |
| } from "../cli-output-contracts.js"; | |
| import type { ToolSummaryTrace } from "../embedded-agent-runner/types.js"; | |
| import { sanitizeToolArgs, sanitizeToolResult } from "../embedded-agent-tool-results.js"; | |
| import { applyPluginTextReplacements } from "../plugin-text-transforms.js"; | |
| import { resolveCliToolTerminalReason } from "../run-termination.js"; | |
| import type { CliToolTracking } from "./execute-tool-tracking.js"; | |
| import { stripOpenClawMcpToolPrefix } from "./tool-policy.js"; | |
| import type { PreparedCliRunContext } from "./types.js"; | |
| type CliToolResult = { | |
| toolCallId: string; | |
| name: string; | |
| isError: boolean; | |
| result?: unknown; | |
| }; | |
| function resolveCliToolSource(name: string, kind?: CliToolUseStartDelta["kind"]): "core" | "mcp" { | |
| return kind === "mcp_tool_use" || name.startsWith("mcp__") ? "mcp" : "core"; | |
| } | |
| export function createCliEventHandlers(params: { | |
| context: PreparedCliRunContext; | |
| toolTracking: CliToolTracking; | |
| getRunState: () => { failed: boolean; error: unknown }; | |
| }) { | |
| 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 = new Map<string, { name: string; failed: boolean }>(); | |
| // CLI results report an outcome without repeating the request, so the terminal | |
| // progress event would otherwise describe the output instead of the command. | |
| const toolArgsByCallId = new Map<string, Record<string, unknown>>(); | |
| const toolSummaryNames: string[] = []; | |
| const toolSummaryNameSet = new Set<string>(); | |
| const activeParsedTools = new Map< | |
| string, | |
| { startedAt: number; toolName: string; kind: CliToolUseStartDelta["kind"] } | |
| >(); | |
| const rememberToolName = (name: string) => { | |
| if (!name || toolSummaryNameSet.has(name)) { | |
| return; | |
| } | |
| toolSummaryNameSet.add(name); | |
| toolSummaryNames.push(name); | |
| }; | |
| const recordToolStart = (event: CliToolUseStartDelta) => { | |
| 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: CliToolResult) => { | |
| 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 = (): ToolSummaryTrace => ({ | |
| calls: toolSummaryById.size, | |
| tools: toolSummaryNames.slice(), | |
| failures: Array.from(toolSummaryById.values()).filter((entry) => entry.failed).length, | |
| }); | |
| const emitCliToolUseStart = (event: CliToolUseStartDelta) => { | |
| 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: CliToolResult) => { | |
| 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 } : {}), | |
| }, | |
| }); | |
| } | |
| }; | |
| // Plugin-parsed events describe native work already performed by the backend. | |
| // Render and summarize them without host-tool correlation or delivery evidence. | |
| const emitCliDisplayToolUseStart = (event: CliToolUseStartDelta) => { | |
| 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: CliToolResult) => { | |
| 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: CliToolUseStartDelta) => { | |
| 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: { | |
| toolCallId: string; | |
| name: string; | |
| isError: boolean; | |
| incomplete?: boolean; | |
| }) => { | |
| 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 | |
| : undefined; | |
| const runState = params.getRunState(); | |
| const terminalReason = | |
| trustedTerminalReason ?? | |
| resolveCliToolTerminalReason({ | |
| error: event.incomplete ? runState.error : undefined, | |
| abortSignal: runParams.abortSignal, | |
| }); | |
| // Incomplete client/MCP tools inherit the enclosing failed run even when | |
| // the loopback disconnect is ambiguous. Server-native tools do not. | |
| const useEnclosingTerminalReason = | |
| event.incomplete && | |
| runState.failed && | |
| activeTool !== undefined && | |
| 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 !== undefined && 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: CliToolResult) => { | |
| emitParsedToolTerminal(event); | |
| emitCliToolResult(event); | |
| }; | |
| const emitCliCompaction = (event: CliCompactionDelta) => { | |
| 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: string) => { | |
| if (!emitLiveEvents) { | |
| return; | |
| } | |
| commentaryCounter += 1; | |
| emitAgentEvent({ | |
| runId: runParams.runId, | |
| stream: "item", | |
| data: { | |
| kind: "preamble", | |
| itemId: `commentary-${runParams.runId}-${commentaryCounter}`, | |
| // The JSONL parser flushes a complete pre-tool text segment here. | |
| // Mark its boundary so channels can safely create their first notification. | |
| phase: "end", | |
| title: "commentary", | |
| status: "running", | |
| progressText: applyPluginTextReplacements( | |
| text, | |
| context.backendResolved.textTransforms?.output, | |
| ), | |
| }, | |
| }); | |
| }; | |
| const emitCliAssistantDelta = ({ text, delta }: CliStreamingDelta) => { | |
| 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), | |
| }, | |
| }); | |
| } | |
| }; | |
| // Emit-always: thinking reaches the event bus and session archive like the | |
| // embedded reasoning stream; /reasoning and /verbose gate presentation only. | |
| const emitCliThinkingDelta = ({ text, delta, isReasoningSnapshot }: CliThinkingDelta) => { | |
| if (text || delta) { | |
| observedCliActivity = true; | |
| } | |
| if (emitLiveEvents) { | |
| emitAgentEvent({ | |
| runId: runParams.runId, | |
| stream: "thinking", | |
| data: { text, delta, ...(isReasoningSnapshot ? { isReasoningSnapshot } : {}) }, | |
| }); | |
| } | |
| }; | |
| const emitCliThinkingProgress = ({ progressTokens }: CliThinkingProgress) => { | |
| 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, | |
| }; | |
| } | |
| export type CliEventHandlers = ReturnType<typeof createCliEventHandlers>; | |