{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../src/harness/runtime/drive/tools.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAkC,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAW7F,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,KAAK,EAA2B,KAAK,EAAa,eAAe,EAAE,MAAM,aAAa,CAAC;AAknB9F,iFAAiF;AACjF,wBAAsB,QAAQ,CAAC,QAAQ,SAAS,MAAM,GAAG,SAAS,EACjE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,EACpB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,cAAc,GACjB,OAAO,CAAC,eAAe,CAAC,CAgC1B","sourcesContent":["import type { ToolResultMessage } from \"@earendil-works/pi-ai\";\nimport type { AgentToolCall, AgentToolResult } from \"../../../types.ts\";\nimport { AbortRequested } from \"../../execution/effect-gate.ts\";\nimport {\n\tapplyBeforeToolDecision,\n\ttype ClearedToolCall,\n\tcreateToolResultMessage,\n\ttype ExecutedToolCall,\n\texecuteToolCall,\n\ttype FinalizedToolCall,\n\tfinalizeToolCall,\n\tprepareToolCall,\n\ttoolResultFromMessage,\n} from \"../../execution/tools.ts\";\nimport { SessionInvariantError } from \"../../session/session.ts\";\nimport type { JsonValue, ToolBatch, ToolCall, ToolsOperation } from \"../../session/types.ts\";\nimport {\n\tdeleteValue,\n\toperationToolArgs,\n\toperationToolMemo,\n\toperationToolMemoPrefix,\n\tpendingEntry,\n\tpendingToolOutput,\n\tsetValue,\n} from \"../../session/values.ts\";\nimport type { AgentHarnessTool, AgentHarnessToolInvocation } from \"../../types.ts\";\nimport type { Lane } from \"../lane.ts\";\nimport { openToolProgress } from \"../progress.ts\";\nimport type { ContinueOperationResult, Drive, LaneState, ProcedureResult } from \"../types.ts\";\nimport {\n\tmaterializeReady,\n\treadToolBatchSource,\n\ttype ToolBatchSource,\n\ttoolCallFor,\n\twithToolBatch,\n} from \"./tool-placement.ts\";\n\ntype ToolCallTask = { completion: Promise<void> };\ntype ToolOutcome = { toolCall: AgentToolCall; message: ToolResultMessage<unknown>; terminate: boolean };\ntype PreparedToolInvocation<TContext extends object | undefined> =\n\t| { kind: \"ready\"; cleared: ClearedToolCall<TContext> }\n\t| { kind: \"outcome\"; outcome: ToolOutcome };\n\nconst INTERRUPTION_MARKER =\n\t\"[Tool execution was interrupted. The preceding output is the latest durable progress snapshot; newer live output may be missing, and the external outcome is unknown.]\";\n\nclass ToolInvocationEnded extends Error {\n\tconstructor() {\n\t\tsuper(\"Tool invocation no longer owns its durable effect\");\n\t\tthis.name = \"ToolInvocationEnded\";\n\t}\n}\n\nfunction currentBatch<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n): { run: ToolsOperation; batch: ToolBatch } | undefined {\n\tconst operation = lane.state.operation;\n\tif (operation?.state.at !== \"tools\") return undefined;\n\treturn { run: operation.state, batch: operation.state.batch };\n}\n\nfunction findCall(batch: ToolBatch, sourceIndex: number, resultEntryId: string): ToolCall | undefined {\n\treturn batch.calls.find((call) => call.sourceIndex === sourceIndex && call.resultEntryId === resultEntryId);\n}\n\nfunction replaceCall(batch: ToolBatch, replacement: ToolCall): ToolBatch {\n\treturn {\n\t\t...batch,\n\t\tcalls: batch.calls.map((call) =>\n\t\t\tcall.sourceIndex === replacement.sourceIndex && call.resultEntryId === replacement.resultEntryId\n\t\t\t\t? replacement\n\t\t\t\t: call,\n\t\t),\n\t};\n}\n\nfunction validateMemoName(name: string): void {\n\tif (name.length === 0) throw new TypeError(\"Tool invocation memo name must not be empty\");\n\tif (name.includes(\":\")) throw new TypeError(\"Tool invocation memo name must not contain ':'\");\n}\n\nfunction invocationCapability<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tbatch: ToolBatch,\n\tcall: Extract<ToolCall, { status: \"effect_pending\" }>,\n): { invocation: AgentHarnessToolInvocation; expire(): void } {\n\tlet active = true;\n\tconst ownsEffect = (state: LaneState): boolean => {\n\t\tconst operation = state.operation;\n\t\tif (operation?.state.at !== \"tools\") return false;\n\t\treturn findCall(operation.state.batch, call.sourceIndex, call.resultEntryId)?.status === \"effect_pending\";\n\t};\n\tconst ended = (): ToolInvocationEnded => new ToolInvocationEnded();\n\treturn {\n\t\tinvocation: {\n\t\t\tinvocationId: call.resultEntryId,\n\t\t\toperationId: drive.operationId,\n\t\t\tturnId: batch.turnId,\n\t\t\tgetMemo(name) {\n\t\t\t\tvalidateMemoName(name);\n\t\t\t\tif (!active) return Promise.reject(ended());\n\t\t\t\treturn lane.command<JsonValue | undefined>(async (state, reader) => {\n\t\t\t\t\tif (!ownsEffect(state)) return { kind: \"reject\", error: ended() };\n\t\t\t\t\tconst stored = await reader.getValue(\n\t\t\t\t\t\toperationToolMemo(drive.operationId, call.resultEntryId, name),\n\t\t\t\t\t\tdrive.context,\n\t\t\t\t\t);\n\t\t\t\t\treturn { kind: \"return\", result: stored?.value };\n\t\t\t\t}, drive.context);\n\t\t\t},\n\t\t\tsetMemo(name, value) {\n\t\t\t\tvalidateMemoName(name);\n\t\t\t\tif (!active) return Promise.reject(ended());\n\t\t\t\treturn lane.command<void>((state) => {\n\t\t\t\t\tif (!ownsEffect(state)) return { kind: \"reject\", error: ended() };\n\t\t\t\t\tconst address = operationToolMemo(drive.operationId, call.resultEntryId, name);\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkind: \"commit\",\n\t\t\t\t\t\twrites: [value === undefined ? deleteValue(address) : setValue(address, value)],\n\t\t\t\t\t\tnext: state,\n\t\t\t\t\t\tmaterialize: () => undefined,\n\t\t\t\t\t};\n\t\t\t\t}, drive.context);\n\t\t\t},\n\t\t},\n\t\texpire() {\n\t\t\tactive = false;\n\t\t},\n\t};\n}\n\nfunction syntheticMessage(\n\ttoolCall: AgentToolCall,\n\tcontent: AgentToolResult<unknown>[\"content\"],\n\toptions: { details?: unknown; usage?: AgentToolResult<unknown>[\"usage\"] } = {},\n): ToolResultMessage<unknown> {\n\treturn {\n\t\trole: \"toolResult\",\n\t\ttoolCallId: toolCall.id,\n\t\ttoolName: toolCall.name,\n\t\tcontent,\n\t\t...(options.details === undefined ? {} : { details: options.details }),\n\t\t...(options.usage === undefined ? {} : { usage: options.usage }),\n\t\tisError: true,\n\t\ttimestamp: Date.now(),\n\t};\n}\n\nfunction abortedOutcome(toolCall: AgentToolCall): ToolOutcome {\n\treturn {\n\t\ttoolCall,\n\t\tmessage: syntheticMessage(toolCall, [{ type: \"text\", text: \"Tool execution was cancelled before completion.\" }]),\n\t\tterminate: false,\n\t};\n}\n\nfunction interruptedOutcome(toolCall: AgentToolCall, checkpoint: AgentToolResult<unknown> | undefined): ToolOutcome {\n\treturn {\n\t\ttoolCall,\n\t\tmessage: syntheticMessage(\n\t\t\ttoolCall,\n\t\t\t[...(checkpoint?.content ?? []), { type: \"text\", text: INTERRUPTION_MARKER }],\n\t\t\tcheckpoint === undefined ? {} : { details: checkpoint.details, usage: checkpoint.usage },\n\t\t),\n\t\tterminate: false,\n\t};\n}\n\nfunction truncatedOutcome(toolCall: AgentToolCall): ToolOutcome {\n\treturn {\n\t\ttoolCall,\n\t\tmessage: syntheticMessage(toolCall, [\n\t\t\t{\n\t\t\t\ttype: \"text\",\n\t\t\t\ttext: `Tool call ${JSON.stringify(toolCall.name)} was not executed because the assistant response hit the output token limit, so its arguments may be truncated. Re-issue the tool call with complete arguments.`,\n\t\t\t},\n\t\t]),\n\t\tterminate: false,\n\t};\n}\n\nfunction outcomeFromFinalizedCall(finalized: FinalizedToolCall): ToolOutcome {\n\treturn { toolCall: finalized.toolCall, message: createToolResultMessage(finalized), terminate: finalized.terminate };\n}\n\nasync function publishToolIntent<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n\tplanned: Extract<ToolCall, { status: \"planned\" }>,\n\ttoolCall: AgentToolCall,\n\targs: Record<string, JsonValue>,\n\treplay: \"never\" | \"safe\",\n\trecovery: boolean,\n): Promise<ContinueOperationResult<Extract<ToolCall, { status: \"effect_pending\" }>>> {\n\treturn lane.continueOperation(\n\t\trun,\n\t\t(_state, run) => {\n\t\t\tconst effectPending: Extract<ToolCall, { status: \"effect_pending\" }> = {\n\t\t\t\tstatus: \"effect_pending\",\n\t\t\t\tsourceIndex: planned.sourceIndex,\n\t\t\t\tresultEntryId: planned.resultEntryId,\n\t\t\t\treplay,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"commit\",\n\t\t\t\twrites: [setValue(operationToolArgs(drive.operationId, run.batch.turnId, planned.sourceIndex), args)],\n\t\t\t\toperationState: withToolBatch(run, replaceCall(run.batch, effectPending)),\n\t\t\t\tmaterialize: () => effectPending,\n\t\t\t\tevents: () => [\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"tool_start\",\n\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\t\tturnId: run.batch.turnId,\n\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\targs,\n\t\t\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t\tdrive.context,\n\t);\n}\n\nasync function publishToolOutcome<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tcapability: ToolsOperation,\n\tcall: Extract<ToolCall, { status: \"planned\" | \"effect_pending\" }>,\n\tfinalized: ToolOutcome,\n\trecovery: boolean,\n): Promise<void> {\n\tawait lane.settleOperation(\n\t\tcapability,\n\t\tasync (_state, run, _meta, reader) => {\n\t\t\tconst { toolCall } = finalized;\n\t\t\tconst memos = await reader.scanValues(\n\t\t\t\toperationToolMemoPrefix(drive.operationId, call.resultEntryId),\n\t\t\t\tdrive.context,\n\t\t\t);\n\t\t\tconst durableTerminate = run.control.status === \"running\" && finalized.terminate;\n\t\t\tconst outcome: Extract<ToolCall, { status: \"outcome_ready\" }> = {\n\t\t\t\tstatus: \"outcome_ready\",\n\t\t\t\tsourceIndex: call.sourceIndex,\n\t\t\t\tresultEntryId: call.resultEntryId,\n\t\t\t\tterminate: durableTerminate,\n\t\t\t};\n\t\t\treturn {\n\t\t\t\tkind: \"commit\",\n\t\t\t\twrites: [\n\t\t\t\t\tsetValue(pendingEntry(call.resultEntryId), { type: \"message\", payload: finalized.message }),\n\t\t\t\t\tdeleteValue(pendingToolOutput(drive.operationId, call.resultEntryId)),\n\t\t\t\t\t...memos.map(({ address }) => deleteValue(address)),\n\t\t\t\t],\n\t\t\t\toperationState: withToolBatch(run, replaceCall(run.batch, outcome)),\n\t\t\t\tmaterialize: () => undefined,\n\t\t\t\tevents: () => [\n\t\t\t\t\t...(call.status === \"planned\"\n\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\ttype: \"tool_start\" as const,\n\t\t\t\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\t\t\t\t\tturnId: run.batch.turnId,\n\t\t\t\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\t\t\t\targs: toolCall.arguments,\n\t\t\t\t\t\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t: []),\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"tool_end\",\n\t\t\t\t\t\tlane: lane.name,\n\t\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\t\tturnId: run.batch.turnId,\n\t\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\t\tresult: toolResultFromMessage(finalized.message, durableTerminate),\n\t\t\t\t\t\tisError: finalized.message.isError,\n\t\t\t\t\t\tterminate: durableTerminate,\n\t\t\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t};\n\t\t},\n\t\tdrive.context,\n\t);\n}\n\nasync function clearReplayCheckpoint<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tbatch: ToolBatch,\n\tcall: Extract<ToolCall, { status: \"effect_pending\" }>,\n\ttoolCall: AgentToolCall,\n): Promise<Record<string, JsonValue>> {\n\treturn lane.command(async (state, reader) => {\n\t\tconst stored = await reader.getValue(\n\t\t\toperationToolArgs(drive.operationId, batch.turnId, call.sourceIndex),\n\t\t\tdrive.context,\n\t\t);\n\t\tif (stored === undefined) {\n\t\t\tthrow new SessionInvariantError(`Tool call ${call.resultEntryId} is missing persisted arguments`);\n\t\t}\n\t\treturn {\n\t\t\tkind: \"commit\",\n\t\t\twrites: [deleteValue(pendingToolOutput(drive.operationId, call.resultEntryId))],\n\t\t\tnext: state,\n\t\t\tmaterialize: () => stored.value,\n\t\t\tevents: () => [\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_start\",\n\t\t\t\t\tlane: lane.name,\n\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\tturnId: batch.turnId,\n\t\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\t\ttoolName: toolCall.name,\n\t\t\t\t\targs: stored.value,\n\t\t\t\t\trecovery: true,\n\t\t\t\t},\n\t\t\t],\n\t\t};\n\t}, drive.context);\n}\n\nfunction readCheckpoint<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tcall: Extract<ToolCall, { status: \"effect_pending\" }>,\n): Promise<AgentToolResult<unknown> | undefined> {\n\treturn lane.command(async (_state, reader) => {\n\t\tconst stored = await reader.getValue(pendingToolOutput(drive.operationId, call.resultEntryId), drive.context);\n\t\treturn { kind: \"return\", result: stored?.value };\n\t}, drive.context);\n}\n\nasync function resolveToolContext<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n): Promise<TContext> {\n\tconst source = lane.readConfig().toolContext;\n\treturn (typeof source === \"function\" ? await source(drive.context) : source) as TContext;\n}\n\nasync function performToolInvocation<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tbatch: ToolBatch,\n\tcall: Extract<ToolCall, { status: \"effect_pending\" }>,\n\tcleared: ClearedToolCall<TContext>,\n\ttoolContext: TContext,\n\trecovery: boolean,\n): Promise<ToolOutcome> {\n\tconst capability = invocationCapability(lane, drive, batch, call);\n\tconst progress = openToolProgress(lane, drive, batch.turnId, call.sourceIndex, call.resultEntryId);\n\tlet latestUpdateDelivery: Promise<void> = Promise.resolve();\n\tconst publishUpdate = (partial: AgentToolResult<unknown>): void => {\n\t\tlatestUpdateDelivery = lane.emitBatch(\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\ttype: \"tool_update\",\n\t\t\t\t\tlane: lane.name,\n\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\tturnId: batch.turnId,\n\t\t\t\t\ttoolCallId: cleared.toolCall.id,\n\t\t\t\t\ttoolName: cleared.toolCall.name,\n\t\t\t\t\tpartialResult: partial,\n\t\t\t\t\t...(recovery ? { recovery: true as const } : {}),\n\t\t\t\t},\n\t\t\t],\n\t\t\tdrive.context,\n\t\t);\n\t\tvoid latestUpdateDelivery.catch(() => {});\n\t};\n\n\tlet execution: Promise<ExecutedToolCall>;\n\ttry {\n\t\texecution = executeToolCall(\n\t\t\tcleared,\n\t\t\tdrive.gate,\n\t\t\t(partial, options) => {\n\t\t\t\tpublishUpdate(partial);\n\t\t\t\tif (options?.checkpoint === true) progress.write(partial);\n\t\t\t},\n\t\t\ttoolContext,\n\t\t\tcapability.invocation,\n\t\t\tdrive.context,\n\t\t);\n\t} catch (error) {\n\t\tcapability.expire();\n\t\tprogress.seal();\n\t\tawait progress.drain();\n\t\tif (!(error instanceof AbortRequested)) throw error;\n\t\tawait error.cancellation;\n\t\treturn recovery ? interruptedOutcome(cleared.toolCall, undefined) : abortedOutcome(cleared.toolCall);\n\t}\n\n\tconst executed = await execution.finally(() => {\n\t\tcapability.expire();\n\t\tprogress.seal();\n\t});\n\tawait latestUpdateDelivery;\n\tawait progress.drain();\n\n\tlet patch: Awaited<ReturnType<typeof lane.hooks.runToolWithGate<\"after_tool\">>>;\n\ttry {\n\t\tpatch = await lane.hooks.runToolWithGate(\n\t\t\t\"after_tool\",\n\t\t\t{\n\t\t\t\tlane: lane.name,\n\t\t\t\trunId: drive.operationId,\n\t\t\t\ttoolCallId: cleared.toolCall.id,\n\t\t\t\ttoolName: cleared.toolCall.name,\n\t\t\t\targs: cleared.args,\n\t\t\t\tcontent: executed.result.content,\n\t\t\t\t...(executed.result.details === undefined ? {} : { details: executed.result.details as JsonValue }),\n\t\t\t\tisError: executed.isError,\n\t\t\t\t...(executed.result.usage === undefined ? {} : { usage: executed.result.usage }),\n\t\t\t},\n\t\t\tdrive.gate,\n\t\t\tdrive.context,\n\t\t);\n\t} catch (error) {\n\t\tif (!(error instanceof AbortRequested)) throw error;\n\t\tpatch = undefined;\n\t}\n\tconst finalized = finalizeToolCall(cleared, executed, patch);\n\treturn { toolCall: finalized.toolCall, message: createToolResultMessage(finalized), terminate: finalized.terminate };\n}\n\nasync function prepareToolInvocation<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\tsources: ToolBatchSource,\n\tcall: Extract<ToolCall, { status: \"planned\" }>,\n\ttools: AgentHarnessTool<TContext>[],\n): Promise<PreparedToolInvocation<TContext>> {\n\tconst toolCall = toolCallFor(sources, call);\n\tif (sources.assistant.stopReason === \"length\") {\n\t\treturn { kind: \"outcome\", outcome: truncatedOutcome(toolCall) };\n\t}\n\tconst prepared = prepareToolCall(toolCall, tools);\n\tif (\"kind\" in prepared) return { kind: \"outcome\", outcome: outcomeFromFinalizedCall(prepared) };\n\n\tlet decision: Awaited<ReturnType<typeof lane.hooks.runToolWithGate<\"before_tool\">>>;\n\ttry {\n\t\tdecision = await lane.hooks.runToolWithGate(\n\t\t\t\"before_tool\",\n\t\t\t{\n\t\t\t\tlane: lane.name,\n\t\t\t\trunId: drive.operationId,\n\t\t\t\ttoolCallId: toolCall.id,\n\t\t\t\ttoolName: toolCall.name,\n\t\t\t\targs: prepared.args,\n\t\t\t},\n\t\t\tdrive.gate,\n\t\t\tdrive.context,\n\t\t);\n\t} catch (error) {\n\t\tif (!(error instanceof AbortRequested)) throw error;\n\t\tawait error.cancellation;\n\t\treturn { kind: \"outcome\", outcome: abortedOutcome(toolCall) };\n\t}\n\tconst cleared = applyBeforeToolDecision(prepared, decision);\n\treturn \"kind\" in cleared\n\t\t? { kind: \"outcome\", outcome: outcomeFromFinalizedCall(cleared) }\n\t\t: { kind: \"ready\", cleared };\n}\n\nasync function startToolInvocation<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n\tsources: ToolBatchSource,\n\tcall: Extract<ToolCall, { status: \"planned\" }>,\n\ttools: AgentHarnessTool<TContext>[],\n\ttoolContext: TContext,\n\trecovery: boolean,\n): Promise<ToolCallTask> {\n\tconst prepared = await prepareToolInvocation(lane, drive, sources, call, tools);\n\tif (prepared.kind === \"outcome\") {\n\t\treturn { completion: publishToolOutcome(lane, drive, run, call, prepared.outcome, recovery) };\n\t}\n\tconst effectPending = await publishToolIntent(\n\t\tlane,\n\t\tdrive,\n\t\trun,\n\t\tcall,\n\t\tprepared.cleared.toolCall,\n\t\tprepared.cleared.args,\n\t\tprepared.cleared.tool.replay ?? \"never\",\n\t\trecovery,\n\t);\n\treturn {\n\t\tcompletion:\n\t\t\teffectPending.kind === \"cancel_requested\"\n\t\t\t\t? publishToolOutcome(lane, drive, run, call, abortedOutcome(prepared.cleared.toolCall), recovery)\n\t\t\t\t: performToolInvocation(\n\t\t\t\t\t\tlane,\n\t\t\t\t\t\tdrive,\n\t\t\t\t\t\trun.batch,\n\t\t\t\t\t\teffectPending.value,\n\t\t\t\t\t\tprepared.cleared,\n\t\t\t\t\t\ttoolContext,\n\t\t\t\t\t\trecovery,\n\t\t\t\t\t).then((outcome) => publishToolOutcome(lane, drive, run, effectPending.value, outcome, recovery)),\n\t};\n}\n\nasync function recoverToolInvocation<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n\tsources: ToolBatchSource,\n\tcall: Extract<ToolCall, { status: \"effect_pending\" }>,\n\ttoolsByName: Map<string, AgentHarnessTool<TContext>>,\n\ttoolContext: TContext,\n\tcancelled: boolean,\n): Promise<ToolCallTask> {\n\tconst toolCall = toolCallFor(sources, call);\n\tconst tool = toolsByName.get(toolCall.name);\n\tif (!cancelled && call.replay === \"safe\" && tool?.replay === \"safe\") {\n\t\tconst args = await clearReplayCheckpoint(lane, drive, run.batch, call, toolCall);\n\t\tconst cleared: ClearedToolCall<TContext> = { toolCall, tool, args };\n\t\treturn {\n\t\t\tcompletion: performToolInvocation(lane, drive, run.batch, call, cleared, toolContext, true).then((outcome) =>\n\t\t\t\tpublishToolOutcome(lane, drive, run, call, outcome, true),\n\t\t\t),\n\t\t};\n\t}\n\tconst checkpoint = await readCheckpoint(lane, drive, call);\n\treturn {\n\t\tcompletion: publishToolOutcome(lane, drive, run, call, interruptedOutcome(toolCall, checkpoint), true),\n\t};\n}\n\nasync function runSequential<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n\tsources: ToolBatchSource,\n\texecution:\n\t\t| {\n\t\t\t\ttools: AgentHarnessTool<TContext>[];\n\t\t\t\ttoolsByName: Map<string, AgentHarnessTool<TContext>>;\n\t\t\t\ttoolContext: TContext;\n\t\t  }\n\t\t| undefined,\n\trecovery: boolean,\n): Promise<ProcedureResult> {\n\tconst { batch } = run;\n\tfor (let transition = 0; transition <= batch.calls.length * 2 + 1; transition += 1) {\n\t\tawait materializeReady(lane, drive, run, sources, recovery);\n\t\tconst current = currentBatch(lane);\n\t\tif (current === undefined) return { kind: \"continue\" };\n\t\tconst call = current.batch.calls.find((candidate) => candidate.status !== \"completed\");\n\t\tif (call === undefined) throw new SessionInvariantError(\"Tool batch remained open after every call completed\");\n\t\tif (call.status === \"outcome_ready\") throw new SessionInvariantError(\"Ready tool outcome was not materialized\");\n\n\t\tif (current.run.control.status === \"cancel_requested\") {\n\t\t\tconst toolCall = toolCallFor(sources, call);\n\t\t\tif (call.status === \"planned\") {\n\t\t\t\tawait publishToolOutcome(lane, drive, current.run, call, abortedOutcome(toolCall), recovery);\n\t\t\t} else {\n\t\t\t\tconst checkpoint = await readCheckpoint(lane, drive, call);\n\t\t\t\tawait publishToolOutcome(\n\t\t\t\t\tlane,\n\t\t\t\t\tdrive,\n\t\t\t\t\tcurrent.run,\n\t\t\t\t\tcall,\n\t\t\t\t\tinterruptedOutcome(toolCall, checkpoint),\n\t\t\t\t\trecovery,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (execution === undefined) throw new SessionInvariantError(\"Running tool batch is missing execution context\");\n\t\tconst started =\n\t\t\tcall.status === \"planned\"\n\t\t\t\t? await startToolInvocation(\n\t\t\t\t\t\tlane,\n\t\t\t\t\t\tdrive,\n\t\t\t\t\t\tcurrent.run,\n\t\t\t\t\t\tsources,\n\t\t\t\t\t\tcall,\n\t\t\t\t\t\texecution.tools,\n\t\t\t\t\t\texecution.toolContext,\n\t\t\t\t\t\trecovery,\n\t\t\t\t\t)\n\t\t\t\t: await recoverToolInvocation(\n\t\t\t\t\t\tlane,\n\t\t\t\t\t\tdrive,\n\t\t\t\t\t\tcurrent.run,\n\t\t\t\t\t\tsources,\n\t\t\t\t\t\tcall,\n\t\t\t\t\t\texecution.toolsByName,\n\t\t\t\t\t\texecution.toolContext,\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t);\n\t\tawait started.completion;\n\t}\n\tthrow new SessionInvariantError(\"Sequential tool batch exceeded its bounded transition count\");\n}\n\nasync function runParallel<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n\tsources: ToolBatchSource,\n\ttools: AgentHarnessTool<TContext>[],\n\ttoolsByName: Map<string, AgentHarnessTool<TContext>>,\n\ttoolContext: TContext,\n\trecovery: boolean,\n): Promise<ProcedureResult> {\n\tconst { batch } = run;\n\tlet materialization = Promise.resolve();\n\tconst scheduleMaterialization = (): Promise<void> => {\n\t\tconst scheduled = materialization.then(() => materializeReady(lane, drive, run, sources, recovery));\n\t\tmaterialization = scheduled.catch(() => {});\n\t\treturn scheduled;\n\t};\n\tconst jobs: Promise<void>[] = [];\n\tfor (const call of batch.calls) {\n\t\tif (call.status === \"completed\" || call.status === \"outcome_ready\") continue;\n\t\tconst started =\n\t\t\tcall.status === \"planned\"\n\t\t\t\t? await startToolInvocation(lane, drive, run, sources, call, tools, toolContext, recovery)\n\t\t\t\t: await recoverToolInvocation(\n\t\t\t\t\t\tlane,\n\t\t\t\t\t\tdrive,\n\t\t\t\t\t\trun,\n\t\t\t\t\t\tsources,\n\t\t\t\t\t\tcall,\n\t\t\t\t\t\ttoolsByName,\n\t\t\t\t\t\ttoolContext,\n\t\t\t\t\t\tlane.state.operation!.state.control.status === \"cancel_requested\",\n\t\t\t\t\t);\n\t\tconst job = started.completion.then(async () => {\n\t\t\tawait scheduleMaterialization();\n\t\t});\n\t\tvoid job.catch(() => {});\n\t\tjobs.push(job);\n\t}\n\tawait Promise.all(jobs);\n\tawait scheduleMaterialization();\n\treturn { kind: \"continue\" };\n}\n\n/** Execute, recover, stage, and source-order one complete durable tool batch. */\nexport async function runTools<TContext extends object | undefined>(\n\tlane: Lane<TContext>,\n\tdrive: Drive,\n\trun: ToolsOperation,\n): Promise<ProcedureResult> {\n\tconst batch = run.batch;\n\tconst recovery = batch.calls.some((call) => call.status === \"effect_pending\" || call.status === \"outcome_ready\");\n\tif (recovery) {\n\t\tawait lane.emitBatch(\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\ttype: \"turn_start\",\n\t\t\t\t\tlane: lane.name,\n\t\t\t\t\trunId: drive.operationId,\n\t\t\t\t\tturnId: batch.turnId,\n\t\t\t\t\trecovery: true,\n\t\t\t\t},\n\t\t\t],\n\t\t\tdrive.context,\n\t\t);\n\t}\n\tconst sources = await readToolBatchSource(lane, drive, batch);\n\tawait materializeReady(lane, drive, run, sources, recovery);\n\tconst current = currentBatch(lane);\n\tif (current === undefined) return { kind: \"continue\" };\n\tif (current.run.control.status === \"cancel_requested\") {\n\t\treturn runSequential(lane, drive, current.run, sources, undefined, recovery);\n\t}\n\tconst config = lane.readConfig();\n\tconst active = new Set(batch.configuration.activeToolNames);\n\tconst tools = config.tools.filter((tool) => active.has(tool.name));\n\tconst toolsByName = new Map(tools.map((tool) => [tool.name, tool]));\n\tconst toolContext = await resolveToolContext(lane, drive);\n\treturn run.settings.toolExecution === \"sequential\"\n\t\t? runSequential(lane, drive, current.run, sources, { tools, toolsByName, toolContext }, recovery)\n\t\t: runParallel(lane, drive, current.run, sources, tools, toolsByName, toolContext, recovery);\n}\n"]}