import { isDeepStrictEqual } from "node:util"; import { beginMcpLoopbackToolCallCapture, clearMcpLoopbackToolCallCapture, type McpLoopbackToolCallStart, waitForMcpLoopbackToolCallCaptureIdle, } from "../../gateway/mcp-http.loopback-runtime.js"; import { shouldUseInternalSourceReplySink } from "../../infra/outbound/internal-source-reply.js"; import { normalizeAcceptedSessionSpawnResult, type AcceptedSessionSpawn, } from "../accepted-session-spawn.js"; import type { CliOutput, CliToolUseStartDelta } from "../cli-output-contracts.js"; import { readEmbeddedMessageDeliveryFact } from "../embedded-agent-message-delivery.js"; import { isDeliveredMessageToolOnlySourceReplyResult, isDeliveredMessagingToolResult, resolveMessageToolSourceReplyFinal, } from "../embedded-agent-message-tool-source-reply.js"; import { extractMessagingToolSendResult, extractMessagingToolSourceReplyPayload, isDeliveredMessagingToolSendToCurrentSource, } from "../embedded-agent-messaging-extraction.js"; import { isMessagingTool, isMessagingToolDeliveryAction, isMessagingToolSendAction, isPluginNativeMessagingTool, } from "../embedded-agent-messaging.js"; import type { MessagingToolSend, MessagingToolSourceReplyPayload, } from "../embedded-agent-messaging.types.js"; import { extractToolResultMediaArtifact, filterToolResultMediaUrls, } from "../embedded-agent-tool-media.js"; import { readToolResultDetails } from "../tool-result-error.js"; import { closeCliLiveSession } from "./cli-live-session-registry.js"; import { attachCliMessagingDeliveryEvidence } from "./delivery-evidence.js"; import * as Deadline from "./execute-ask-user-deadline.js"; import { appendUniqueCliMessagingEvidence, buildMessagingToolSendEvidenceKey, CLI_MESSAGING_EVIDENCE_MAX_CALLS, extractCliMessagingContent, extractCliMessagingTarget, normalizeCliMessagingToolName, } from "./execute-messaging.js"; import type { PreparedCliRunContext } from "./types.js"; const CLI_LOOPBACK_CORRELATION_MAX_CALLS = 64; const CLI_MCP_DELIVERY_DRAIN_GRACE_MS = 5_000; const CLI_MCP_REQUEST_ADMISSION_GRACE_MS = 250; type ActiveCliTool = Deadline.ActiveCliTool; type CliLoopbackCall = Deadline.CliLoopbackCall; export function createCliToolTracking(context: PreparedCliRunContext) { let gatewayCaptureKey: string | undefined; let yielded = false; let yieldAcknowledgment: string | undefined; let didSendViaMessagingTool = false; let didDeliverSourceReplyViaMessageTool = false; let sourceReplyDelivered: true | undefined; let inFlightUnclassifiedMcpRequests = 0; let inFlightMessagingToolCalls = 0; const inFlightPreparedMessagingCalls = new Set(); const pendingMessagingCalls = new Map< string, { toolName: string; args: Record; target?: MessagingToolSend } >(); const cliLoopbackCalls: CliLoopbackCall[] = []; const activeCliTools = new Map(); let cliLoopbackCorrelationOverflowed = false; const askUserDeadlines = Deadline.createAskUserDeadlineTracking( activeCliTools, () => cliLoopbackCorrelationOverflowed, ); const messagingToolSentTexts: string[] = []; const messagingToolSentTextKeys = new Set(); const messagingToolSentMediaUrls: string[] = []; const messagingToolSentMediaUrlKeys = new Set(); const messagingToolSentTargets: MessagingToolSend[] = []; const messagingToolSentTargetKeys = new Set(); const messagingToolSourceReplyPayloads: MessagingToolSourceReplyPayload[] = []; const toolMediaUrls: string[] = []; const toolMediaUrlKeys = new Set(); let toolAudioAsVoice = false; let toolTrustedLocalMedia = false; const acceptedSessionSpawns: AcceptedSessionSpawn[] = []; const matchesCliLoopbackCall = ( toolName: string, toolArgs: Record, call: McpLoopbackToolCallStart, ) => normalizeCliMessagingToolName(toolName) === call.toolName && isDeepStrictEqual(toolArgs, call.args); const markCliLoopbackCallsAmbiguous = ( calls: CliLoopbackCall[], activeEntries = Array.from(activeCliTools.entries()).filter( ([, activeTool]) => activeTool.loopbackCall !== undefined && calls.includes(activeTool.loopbackCall), ), ) => { const groups = 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: new Set(), activeToolCallIds: 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); } askUserDeadlines.refresh(); }; const matchingActiveCliTools = (call: McpLoopbackToolCallStart): Array<[string, ActiveCliTool]> => Array.from(activeCliTools.entries()).filter(([, activeTool]) => matchesCliLoopbackCall(activeTool.toolName, activeTool.args, call), ); const markCliLoopbackSignatureAmbiguous = (call: McpLoopbackToolCallStart) => { const calls = cliLoopbackCalls.filter((candidate) => matchesCliLoopbackCall(call.toolName, call.args, candidate.admitted), ); markCliLoopbackCallsAmbiguous(calls, matchingActiveCliTools(call)); }; const retainCliLoopbackCall = (call: McpLoopbackToolCallStart) => { 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; askUserDeadlines.refresh(); return undefined; } const retained: CliLoopbackCall = { admitted: call, current: call, ambiguous: false }; cliLoopbackCalls.push(retained); return retained; }; const bindCliLoopbackCall = ( call: CliLoopbackCall, toolCallId: string, activeTool: ActiveCliTool, ) => { call.boundToolCallId = toolCallId; activeTool.loopbackCall = call; activeTool.loopbackAmbiguous ||= call.ambiguous; if (call.ambiguityGroup) { activeTool.ambiguityGroup = call.ambiguityGroup; call.ambiguityGroup.activeToolCallIds.add(toolCallId); } askUserDeadlines.refresh(); }; const removeCliLoopbackCall = (call: CliLoopbackCall | undefined) => { if (!call) { return; } const index = cliLoopbackCalls.indexOf(call); if (index >= 0) { cliLoopbackCalls.splice(index, 1); } }; const retireCliLoopbackCorrelation = ( toolCallId: string, activeTool: ActiveCliTool | undefined, ) => { removeCliLoopbackCall(activeTool?.loopbackCall); const group = activeTool?.ambiguityGroup; if (!group) { return; } group.activeToolCallIds.delete(toolCallId); const hasUnboundCall = Array.from(group.calls).some( (call) => call.boundToolCallId === undefined && cliLoopbackCalls.includes(call), ); if (group.activeToolCallIds.size > 0 || hasUnboundCall) { return; } // An ambiguous group owns unbound captures too. Retire the whole group // once its parsed tools finish so stale calls cannot poison later tools. for (const call of group.calls) { removeCliLoopbackCall(call); } group.calls.clear(); }; const commitMessagingToolResult = (params: { toolName: string; target?: MessagingToolSend; args?: Record; result?: unknown; isError?: boolean; }) => { const deliveryFact = readEmbeddedMessageDeliveryFact( readToolResultDetails(params.result)?.messageDelivery, ); const delivered = deliveryFact ? deliveryFact.status === "settled" && (params.isError !== true || deliveryFact.partialDelivery) : isPluginNativeMessagingTool(params.toolName) && isDeliveredMessagingToolResult(params); if (!delivered) { return; } didSendViaMessagingTool = true; // Implicit source replies can settle without an argument-derived target. 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) : undefined; 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 >= CLI_MESSAGING_EVIDENCE_MAX_CALLS) { messagingToolSourceReplyPayloads.shift(); } // Each internal source-reply send is a distinct delivery, even when // two intentional sends have identical text or media. messagingToolSourceReplyPayloads.push({ ...payload, ...(sourceReplyFinal !== undefined ? { sourceReplyFinal } : {}), }); } } if (!confirmedTarget) { return; } const targetWithContent = { ...confirmedTarget, ...content, ...(sourceReplyFinal !== undefined ? { sourceReplyFinal } : {}), }; const evidenceKey = buildMessagingToolSendEvidenceKey(targetWithContent); if (messagingToolSentTargetKeys.has(evidenceKey)) { return; } if (messagingToolSentTargets.length >= CLI_MESSAGING_EVIDENCE_MAX_CALLS) { const removed = messagingToolSentTargets.shift(); if (removed) { messagingToolSentTargetKeys.delete(buildMessagingToolSendEvidenceKey(removed)); } } messagingToolSentTargets.push(targetWithContent); messagingToolSentTargetKeys.add(evidenceKey); }; const isPreparedInternalSourceReply = async (call: McpLoopbackToolCallStart) => { 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: string | undefined, assertCurrent: () => void) => { if (!captureKey || gatewayCaptureKey === captureKey) { return; } if (gatewayCaptureKey) { throw new Error("CLI MCP capture key changed during an active attempt"); } context.preparedBackend.mcpClientGrantCapture?.activate(captureKey, assertCurrent); gatewayCaptureKey = captureKey; const isPotentialDelivery = (toolName: string) => isMessagingTool(normalizeCliMessagingToolName(toolName)); const isPreparedDelivery = (toolName: string, toolArgs: Record) => 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); // Parallel same-name calls can reach the loopback out of stream order. // Bind only a unique name+arguments match; ambiguity is safer than a wrong outcome. let matched = retained && candidates.length === 1 && !candidates[0]?.[1].loopbackCall && !candidates[0]?.[1].loopbackAmbiguous ? candidates[0] : undefined; 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; const toolName = normalizeCliMessagingToolName(current.toolName); askUserDeadlines.update(candidate, toolName, current.args); } 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 }) => { const isDelivery = prepared ? isPreparedDelivery(call.toolName, call.args) : isPotentialDelivery(call.toolName); if (isDelivery) { inFlightMessagingToolCalls = Math.max(0, inFlightMessagingToolCalls - 1); } inFlightPreparedMessagingCalls.delete(call); }, onToolCallResult: (call) => { const terminalOutcome: Deadline.CliToolTerminalOutcome = call.outcome === "blocked" ? { outcome: call.outcome, deniedReason: call.deniedReason } : { outcome: call.outcome }; const correlated = call.correlationId ? cliLoopbackCalls.find((candidate) => candidate.boundToolCallId === call.correlationId) : undefined; 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 : undefined, 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: CliToolUseStartDelta) => { if (event.kind !== "server_tool_use") { const activeTool: ActiveCliTool = { 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 === undefined && matchesCliLoopbackCall(event.name, event.args, candidate.admitted), ); const hasAssociatedPeer = matchingActiveCliTools(admittedCall).some( ([toolCallId, peer]) => toolCallId !== event.toolCallId && (peer.loopbackCall !== undefined || 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 >= CLI_MESSAGING_EVIDENCE_MAX_CALLS) { const oldestToolCallId = pendingMessagingCalls.keys().next().value; if (oldestToolCallId !== undefined) { pendingMessagingCalls.delete(oldestToolCallId); // Once an unresolved send is evicted, its later result cannot be correlated. // Fail closed so a failed turn cannot duplicate it. didSendViaMessagingTool = true; } } pendingMessagingCalls.set(event.toolCallId, { toolName, args: event.args, target: extractCliMessagingTarget(context, toolName, event.args), }); }; const handleCliToolResult = (event: { toolCallId: string; name: string; isError: boolean; result?: unknown; }) => { const activeTool = activeCliTools.get(event.toolCallId); if (activeTool?.loopbackCall) { askUserDeadlines.clear(activeTool.loopbackCall); } 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: string) => { const activeTool = activeCliTools.get(toolCallId); if (activeTool?.loopbackAmbiguous) { return { outcome: "unknown" } as const; } return activeTool?.loopbackCall?.outcome; }; const finishDeliveryTracking = async (params: { useManagedClaudeLiveSession: boolean; recordRunError: (error: unknown) => void; }) => { try { if (!gatewayCaptureKey && pendingMessagingCalls.size > 0) { const calls = Array.from(pendingMessagingCalls.values()); const internalStates = await Promise.all(calls.map(isPreparedInternalSourceReply)); if (internalStates.some((internal) => !internal)) { didSendViaMessagingTool = true; params.recordRunError( new Error("CLI JSONL message tool call remained unresolved after exit"), ); } else { params.recordRunError( new Error("CLI JSONL source reply call remained unresolved after exit"), ); } } if (!gatewayCaptureKey) { return; } const captureBecameIdle = await waitForMcpLoopbackToolCallCaptureIdle(gatewayCaptureKey, { timeoutMs: CLI_MCP_DELIVERY_DRAIN_GRACE_MS, admissionGraceMs: CLI_MCP_REQUEST_ADMISSION_GRACE_MS, }); if (captureBecameIdle) { return; } if (params.useManagedClaudeLiveSession) { // The child still holds the process-env capture key. If drain cannot // prove idle, kill it so a stale key cannot admit later sends. await closeCliLiveSession(context, "mcp-capture-rotation"); } const internalStates = await Promise.all( Array.from(inFlightPreparedMessagingCalls).map(isPreparedInternalSourceReply), ); const internalCount = internalStates.filter(Boolean).length; const hasPotentialVisibleSend = inFlightMessagingToolCalls > internalCount; if (inFlightUnclassifiedMcpRequests > 0 || hasPotentialVisibleSend) { didSendViaMessagingTool = true; params.recordRunError(new Error("CLI message tool call remained in flight after exit")); } else if (inFlightMessagingToolCalls > 0) { params.recordRunError(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: () => void) => { // Captured MCP calls may settle after the attempt returns. Drain first so // finalization can use their trusted terminal outcomes. try { finalizeParsedTools(); } finally { if (gatewayCaptureKey) { // Fence this exact grant generation before clearing observers; otherwise // a late request escapes accounting. try { context.preparedBackend.mcpClientGrantCapture?.deactivate(gatewayCaptureKey); } finally { clearMcpLoopbackToolCallCapture(gatewayCaptureKey); } } } }; const evidence = () => ({ didSendViaMessagingTool, didDeliverSourceReplyViaMessageTool, sourceReplyDelivered, messagingToolSentTexts, messagingToolSentMediaUrls, messagingToolSentTargets, messagingToolSourceReplyPayloads, toolMediaUrls, toolAudioAsVoice, toolTrustedLocalMedia, acceptedSessionSpawns, }); return { beginGatewayCapture, getActiveLoopbackAskUserDeadline: askUserDeadlines.get, onActiveLoopbackAskUserDeadlineChange: askUserDeadlines.onChange, handleCliToolUseStart, handleCliToolResult, resolveCliLoopbackTerminalOutcome, finishDeliveryTracking, finalizeCapture, withExecutionEvidence(output: CliOutput): CliOutput { const current = evidence(); return { ...output, ...(yielded ? { yielded: true as const } : {}), ...(yieldAcknowledgment ? { yieldAcknowledgment } : {}), ...(current.didSendViaMessagingTool ? { didSendViaMessagingTool: true } : {}), ...(current.didDeliverSourceReplyViaMessageTool ? { didDeliverSourceReplyViaMessageTool: true } : {}), ...(current.sourceReplyDelivered ? { sourceReplyDelivered: true as const } : {}), ...(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: unknown) { return attachCliMessagingDeliveryEvidence(error, evidence()); }, }; } export type CliToolTracking = ReturnType;