@botbotgo/agent-harness 0.0.326 → 0.0.328

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/cli/chat-stream.js +33 -27
  2. package/dist/cli/main.js +30 -3
  3. package/dist/contracts/runtime-requests.d.ts +1 -2
  4. package/dist/contracts/runtime-scheduling.d.ts +1 -1
  5. package/dist/flow/flow-graph-upstream.js +3 -7
  6. package/dist/package-version.d.ts +1 -1
  7. package/dist/package-version.js +1 -1
  8. package/dist/projections/request-events.js +0 -1
  9. package/dist/resource/isolation.js +51 -10
  10. package/dist/resources/toolkit.mjs +183 -0
  11. package/dist/resources/tools/cancel_request.mjs +1 -1
  12. package/dist/resources/tools/fetch_url.mjs +1 -1
  13. package/dist/resources/tools/http_request.mjs +1 -1
  14. package/dist/resources/tools/inspect_approvals.mjs +1 -1
  15. package/dist/resources/tools/inspect_artifacts.mjs +1 -1
  16. package/dist/resources/tools/inspect_events.mjs +1 -1
  17. package/dist/resources/tools/inspect_requests.mjs +1 -1
  18. package/dist/resources/tools/inspect_sessions.mjs +1 -1
  19. package/dist/resources/tools/list_files.mjs +1 -1
  20. package/dist/resources/tools/read_artifact.mjs +1 -1
  21. package/dist/resources/tools/request_approval.mjs +1 -1
  22. package/dist/resources/tools/run_command.mjs +1 -1
  23. package/dist/resources/tools/schedule_task.mjs +1 -1
  24. package/dist/resources/tools/search_files.mjs +1 -1
  25. package/dist/resources/tools/send_message.mjs +1 -1
  26. package/dist/runtime/adapter/compat/deepagent-compat.d.ts +0 -9
  27. package/dist/runtime/adapter/compat/deepagent-compat.js +0 -22
  28. package/dist/runtime/adapter/flow/stream-runtime.d.ts +4 -0
  29. package/dist/runtime/adapter/flow/stream-runtime.js +239 -8
  30. package/dist/runtime/adapter/local-tool-invocation.js +53 -0
  31. package/dist/runtime/adapter/middleware-assembly.js +174 -29
  32. package/dist/runtime/adapter/runtime-adapter-support.js +1 -2
  33. package/dist/runtime/adapter/stream-event-projection.d.ts +17 -0
  34. package/dist/runtime/adapter/stream-event-projection.js +217 -4
  35. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +0 -3
  36. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +37 -17
  37. package/dist/runtime/adapter/tool/resolved-tool.js +29 -3
  38. package/dist/runtime/agent-runtime-adapter.d.ts +3 -3
  39. package/dist/runtime/agent-runtime-adapter.js +12 -33
  40. package/dist/runtime/agent-runtime-assembly.d.ts +3 -21
  41. package/dist/runtime/agent-runtime-assembly.js +4 -56
  42. package/dist/runtime/harness/run/inspection.js +21 -5
  43. package/dist/runtime/harness/run/run-operations.js +2 -1
  44. package/dist/runtime/harness/run/stream-run.d.ts +3 -1
  45. package/dist/runtime/harness/run/stream-run.js +206 -30
  46. package/dist/runtime/harness.js +3 -0
  47. package/dist/runtime/parsing/output-content.js +11 -4
  48. package/dist/runtime/parsing/output-recovery.d.ts +3 -0
  49. package/dist/runtime/parsing/output-recovery.js +57 -11
  50. package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
  51. package/dist/runtime/parsing/output-tool-args.js +122 -0
  52. package/dist/runtime/parsing/stream-event-parsing.js +37 -3
  53. package/dist/runtime/support/harness-support.d.ts +1 -0
  54. package/dist/runtime/support/harness-support.js +44 -2
  55. package/dist/tools.js +34 -4
  56. package/package.json +8 -8
@@ -1,9 +1,3 @@
1
- import type { CompiledModel, CompiledSubAgent } from "../../../contracts/types.js";
2
- type DelegationPromptCompatibilityParams = {
3
- subagents?: CompiledSubAgent[];
4
- generalPurposeAgent?: boolean;
5
- taskDescription?: string;
6
- };
7
1
  export declare function relativizeDeepAgentSkillSourcePaths(workspaceRoot: string | undefined, skillPaths: string[] | undefined): string[] | undefined;
8
2
  export declare function materializeDeepAgentSkillSourcePaths(options: {
9
3
  workspaceRoot?: string;
@@ -17,6 +11,3 @@ export declare function resolveDeepAgentSkillSourcePaths(options: {
17
11
  ownerId: string;
18
12
  skillPaths?: string[];
19
13
  }): string[] | undefined;
20
- export declare function shouldRelaxDeepAgentDelegationPrompt(model: CompiledModel | undefined, params: DelegationPromptCompatibilityParams): boolean;
21
- export declare function applyDeepAgentDelegationPromptCompatibility<T extends DelegationPromptCompatibilityParams>(model: CompiledModel | undefined, params: T): T;
22
- export {};
@@ -1,7 +1,4 @@
1
1
  import path from "node:path";
2
- function isOpenAICompatibleGptOssModel(model) {
3
- return model?.provider === "openai-compatible" && model.model.trim().toLowerCase().startsWith("gpt-oss");
4
- }
5
2
  export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
6
3
  if (!workspaceRoot || !skillPaths) {
7
4
  return skillPaths;
@@ -27,22 +24,3 @@ export function resolveDeepAgentSkillSourcePaths(options) {
27
24
  }
28
25
  return relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
29
26
  }
30
- export function shouldRelaxDeepAgentDelegationPrompt(model, params) {
31
- if (!isOpenAICompatibleGptOssModel(model)) {
32
- return false;
33
- }
34
- if ((params.subagents?.length ?? 0) === 0) {
35
- return false;
36
- }
37
- return params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
38
- }
39
- export function applyDeepAgentDelegationPromptCompatibility(model, params) {
40
- if (!shouldRelaxDeepAgentDelegationPrompt(model, params)) {
41
- return params;
42
- }
43
- return {
44
- ...params,
45
- generalPurposeAgent: undefined,
46
- taskDescription: undefined,
47
- };
48
- }
@@ -5,6 +5,9 @@ type RunnableLike = {
5
5
  stream?: (input: unknown, config?: Record<string, unknown>) => Promise<AsyncIterable<unknown>>;
6
6
  streamEvents?: (input: unknown, config?: Record<string, unknown>) => Promise<AsyncIterable<unknown>>;
7
7
  };
8
+ export declare class ExecutionReconciliationError extends Error {
9
+ constructor(message: string);
10
+ }
8
11
  export declare function streamRuntimeExecution(options: {
9
12
  binding: CompiledAgentBinding;
10
13
  input: MessageContent;
@@ -48,5 +51,6 @@ export declare function streamRuntimeExecution(options: {
48
51
  isLangChainBinding: (binding: CompiledAgentBinding) => boolean;
49
52
  isDeepAgentBinding: (binding: CompiledAgentBinding) => boolean;
50
53
  countConfiguredTools: (binding: CompiledAgentBinding) => number;
54
+ countConfiguredToolsForAgentId?: (agentId: string) => number;
51
55
  }): AsyncGenerator<RuntimeStreamChunk>;
52
56
  export {};
@@ -1,14 +1,109 @@
1
- import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
1
+ import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
2
2
  import { buildInvocationRequest } from "../model/invocation-request.js";
3
3
  import { buildRawModelMessages } from "../model/message-assembly.js";
4
4
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
5
5
  import { projectTextStreamChunks } from "../stream-text-consumption.js";
6
6
  import { computeRemainingTimeoutMs } from "../resilience.js";
7
7
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
8
+ export class ExecutionReconciliationError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = "ExecutionReconciliationError";
12
+ }
13
+ }
8
14
  function toVisibleContent(value) {
9
15
  const extracted = extractVisibleOutput(value);
10
16
  return extracted ? sanitizeVisibleText(extracted) : "";
11
17
  }
18
+ function readTerminalEventVisibleOutput(event) {
19
+ if (typeof event !== "object" || event === null) {
20
+ return "";
21
+ }
22
+ const typed = event;
23
+ const eventName = typeof typed.event === "string" ? typed.event : "";
24
+ if (eventName !== "on_chat_model_end" && eventName !== "on_chain_end") {
25
+ return "";
26
+ }
27
+ return toVisibleContent(typed.data?.output);
28
+ }
29
+ function hasIncompletePlanStateInExecutedToolResults(executedToolResults) {
30
+ for (const latest of [...executedToolResults].reverse()) {
31
+ if (typeof latest.output !== "object" || latest.output === null) {
32
+ continue;
33
+ }
34
+ const summaryContainer = latest.output.summary;
35
+ if (typeof summaryContainer !== "object" || summaryContainer === null) {
36
+ continue;
37
+ }
38
+ const summary = summaryContainer.summary;
39
+ if (typeof summary !== "object" || summary === null) {
40
+ continue;
41
+ }
42
+ const typedSummary = summary;
43
+ const pending = typeof typedSummary.pending === "number" ? typedSummary.pending : 0;
44
+ const inProgress = typeof typedSummary.inProgress === "number" ? typedSummary.inProgress : 0;
45
+ return pending > 0 || inProgress > 0;
46
+ }
47
+ return false;
48
+ }
49
+ function hasNonTodoToolEvidence(executedToolResults) {
50
+ return executedToolResults.some((item) => item.toolName !== "write_todos" && item.toolName !== "read_todos");
51
+ }
52
+ function hasSuccessfulNonTodoToolEvidence(executedToolResults) {
53
+ return executedToolResults.some((item) => item.isError !== true && item.toolName !== "write_todos" && item.toolName !== "read_todos");
54
+ }
55
+ function hasSuccessfulTaskToolEvidence(executedToolResults) {
56
+ return executedToolResults.some((item) => item.isError !== true && item.toolName === "task");
57
+ }
58
+ function buildExecutionRecoveryEvidence(params) {
59
+ const { projectionState, executedToolResults = [] } = params;
60
+ return {
61
+ hasToolResultEvidence: executedToolResults.length > 0 || projectionState.emittedToolResult || projectionState.emittedToolError,
62
+ hasSuccessfulToolResultEvidence: executedToolResults.some((item) => item.isError !== true) || projectionState.emittedSuccessfulToolResult,
63
+ hasNonTodoToolResultEvidence: hasNonTodoToolEvidence(executedToolResults) || projectionState.emittedNonTodoToolResult || projectionState.emittedToolError,
64
+ hasSuccessfulNonTodoToolResultEvidence: hasSuccessfulNonTodoToolEvidence(executedToolResults) || projectionState.emittedSuccessfulNonTodoToolResult,
65
+ hasIncompletePlanState: projectionState.hasIncompletePlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
66
+ hasPlanStateEvidence: projectionState.sawPlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
67
+ hasOpenTaskDelegation: projectionState.openTaskDelegations > 0,
68
+ hasFailedTaskDelegation: projectionState.hasFailedTaskDelegation,
69
+ hasDelegatedAgentWithConfiguredTools: projectionState.sawDelegatedAgentWithConfiguredTools,
70
+ hasDelegatedExecutionToolEvidence: projectionState.emittedDelegatedExecutionToolResult,
71
+ hasOnlyPlaceholderTaskCompletion: projectionState.emittedSuccessfulTaskResult
72
+ && projectionState.emittedPlaceholderTaskResult
73
+ && !projectionState.emittedDelegatedTerminalOutput,
74
+ };
75
+ }
76
+ function hasUnresolvedExecution(evidence) {
77
+ return (evidence.hasIncompletePlanState
78
+ || evidence.hasFailedTaskDelegation
79
+ || evidence.hasOpenTaskDelegation);
80
+ }
81
+ function hasMissingDelegatedExecutionEvidence(evidence) {
82
+ return evidence.hasDelegatedAgentWithConfiguredTools && !evidence.hasDelegatedExecutionToolEvidence;
83
+ }
84
+ function hasMissingDelegatedFindings(evidence) {
85
+ return evidence.hasDelegatedAgentWithConfiguredTools && evidence.hasOnlyPlaceholderTaskCompletion;
86
+ }
87
+ function createUnresolvedExecutionError(evidence) {
88
+ const reasons = [];
89
+ if (evidence.hasIncompletePlanState) {
90
+ reasons.push("plan state still has unfinished work");
91
+ }
92
+ if (evidence.hasFailedTaskDelegation) {
93
+ reasons.push("delegated task failed before surfacing final findings");
94
+ }
95
+ if (evidence.hasOpenTaskDelegation) {
96
+ reasons.push("delegated task has not finished");
97
+ }
98
+ if (hasMissingDelegatedExecutionEvidence(evidence)) {
99
+ reasons.push("delegated agent ended without surfacing any real tool execution evidence");
100
+ }
101
+ if (hasMissingDelegatedFindings(evidence)) {
102
+ reasons.push("delegated task returned only the upstream placeholder result without surfaced final findings");
103
+ }
104
+ const detail = reasons.length > 0 ? `: ${reasons.join("; ")}` : "";
105
+ return new ExecutionReconciliationError(`Agent ended before execution was fully reconciled${detail}.`);
106
+ }
12
107
  function createProfileStep(id, kind, name, action, status, detail) {
13
108
  return {
14
109
  kind: "profile",
@@ -49,6 +144,7 @@ export async function* streamRuntimeExecution(options) {
49
144
  const shouldValidateStreamOutput = shouldValidateExecutionWithoutToolEvidence(request);
50
145
  const deferredStreamContent = [];
51
146
  let sawRetrySafeInvalidToolSelectionError = false;
147
+ const projectionState = createStreamEventProjectionState();
52
148
  const shouldDeferStreamContent = () => shouldValidateStreamOutput && !emittedUnsafeStreamSideEffects;
53
149
  const flushDeferredStreamContent = async function* () {
54
150
  while (deferredStreamContent.length > 0) {
@@ -212,7 +308,6 @@ export async function* streamRuntimeExecution(options) {
212
308
  });
213
309
  throw error;
214
310
  }
215
- const projectionState = createStreamEventProjectionState();
216
311
  const streamEventsConsume = startProfileStep({
217
312
  id: "profile:agent:stream-events-consume",
218
313
  kind: "agent",
@@ -225,8 +320,12 @@ export async function* streamRuntimeExecution(options) {
225
320
  for await (const event of options.iterateWithTimeout(events, options.streamIdleTimeoutMs, "agent streamEvents", options.streamDeadlineAt, options.invokeTimeoutMs)) {
226
321
  const projectedChunks = projectRuntimeStreamEvent({
227
322
  event,
228
- allowVisibleStreamDeltas: options.isLangChainBinding(options.binding),
323
+ allowVisibleStreamDeltas: true,
229
324
  includeStateStreamOutput: options.isDeepAgentBinding(options.binding),
325
+ rootAgentId: typeof options.binding.agent?.id === "string"
326
+ ? options.binding.agent.id
327
+ : undefined,
328
+ countConfiguredToolsForAgentId: options.countConfiguredToolsForAgentId,
230
329
  toolNameMapping: options.toolNameMapping,
231
330
  primaryTools: options.primaryTools,
232
331
  state: projectionState,
@@ -248,12 +347,27 @@ export async function* streamRuntimeExecution(options) {
248
347
  if (eventContainsNonTodoToolResult || eventContainsNonRetrySafeChunk) {
249
348
  emittedUnsafeStreamSideEffects = true;
250
349
  }
251
- if (chunk.kind === "content" && shouldDeferStreamContent()) {
350
+ if (chunk.kind === "content" && (shouldDeferStreamContent() || projectionState.hasFailedTaskDelegation)) {
252
351
  deferredStreamContent.push(chunk);
253
352
  continue;
254
353
  }
255
354
  yield chunk;
256
355
  }
356
+ const terminalVisibleOutput = readTerminalEventVisibleOutput(event);
357
+ if (terminalVisibleOutput) {
358
+ const terminalExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
359
+ if (!shouldDeferStreamContent()
360
+ && !terminalExecutionEvidence.hasIncompletePlanState
361
+ && !terminalExecutionEvidence.hasFailedTaskDelegation
362
+ && !terminalExecutionEvidence.hasOpenTaskDelegation
363
+ && !hasMissingDelegatedExecutionEvidence(terminalExecutionEvidence)
364
+ && !hasMissingDelegatedFindings(terminalExecutionEvidence)) {
365
+ if (deferredStreamContent.length > 0) {
366
+ yield* flushDeferredStreamContent();
367
+ }
368
+ return;
369
+ }
370
+ }
257
371
  }
258
372
  if (shouldProfile)
259
373
  yield finishProfileStep({
@@ -278,13 +392,55 @@ export async function* streamRuntimeExecution(options) {
278
392
  });
279
393
  throw error;
280
394
  }
281
- const terminalRecoveryInstruction = !emittedUnsafeStreamSideEffects && sawRetrySafeInvalidToolSelectionError ? INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION : null;
282
- if (terminalRecoveryInstruction) {
395
+ const streamedExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
396
+ if (hasUnresolvedExecution(streamedExecutionEvidence)) {
397
+ throw createUnresolvedExecutionError(streamedExecutionEvidence);
398
+ }
399
+ const executionWithoutToolEvidenceInstruction = projectionState.emittedOutput
400
+ ? resolveExecutionWithoutToolEvidenceTextInstruction(request, projectionState.emittedOutput, false, {
401
+ ...streamedExecutionEvidence,
402
+ hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(streamedExecutionEvidence),
403
+ })
404
+ : null;
405
+ const retryInstruction = !emittedUnsafeStreamSideEffects && sawRetrySafeInvalidToolSelectionError
406
+ ? INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION
407
+ : executionWithoutToolEvidenceInstruction;
408
+ if (retryInstruction) {
283
409
  let retried;
284
- retried = await options.invoke(options.applyToolRecoveryInstruction(options.binding, terminalRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
410
+ retried = await options.invoke(options.applyToolRecoveryInstruction(options.binding, retryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
285
411
  const executedToolResults = Array.isArray(retried.metadata?.executedToolResults)
286
412
  ? retried.metadata.executedToolResults
287
413
  : [];
414
+ const originalExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
415
+ const retriedExecutionEvidence = buildExecutionRecoveryEvidence({
416
+ projectionState: createStreamEventProjectionState(),
417
+ executedToolResults,
418
+ });
419
+ const retriedVisibleOutput = retried.output ? toVisibleContent(retried.output) : "";
420
+ const retriedCarriesExecutionEvidence = retriedExecutionEvidence.hasToolResultEvidence
421
+ || retriedExecutionEvidence.hasOpenTaskDelegation
422
+ || retriedExecutionEvidence.hasDelegatedExecutionToolEvidence;
423
+ const retriedHasUnresolvedExecution = hasUnresolvedExecution(retriedExecutionEvidence)
424
+ || hasMissingDelegatedExecutionEvidence(retriedExecutionEvidence)
425
+ || hasMissingDelegatedFindings(retriedExecutionEvidence)
426
+ || (!retriedCarriesExecutionEvidence
427
+ && (hasUnresolvedExecution(originalExecutionEvidence)
428
+ || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
429
+ || hasMissingDelegatedFindings(originalExecutionEvidence)));
430
+ const effectiveRecoveryEvidence = retriedCarriesExecutionEvidence
431
+ ? retriedExecutionEvidence
432
+ : {
433
+ ...retriedExecutionEvidence,
434
+ hasIncompletePlanState: originalExecutionEvidence.hasIncompletePlanState,
435
+ hasFailedTaskDelegation: originalExecutionEvidence.hasFailedTaskDelegation,
436
+ hasOpenTaskDelegation: originalExecutionEvidence.hasOpenTaskDelegation,
437
+ hasDelegatedAgentWithConfiguredTools: originalExecutionEvidence.hasDelegatedAgentWithConfiguredTools,
438
+ hasDelegatedExecutionToolEvidence: originalExecutionEvidence.hasDelegatedExecutionToolEvidence,
439
+ };
440
+ if (retriedHasUnresolvedExecution
441
+ || (retriedHasUnresolvedExecution && retriedExecutionEvidence.hasToolResultEvidence && !retriedVisibleOutput)) {
442
+ throw createUnresolvedExecutionError(effectiveRecoveryEvidence);
443
+ }
288
444
  for (const toolResult of executedToolResults) {
289
445
  yield {
290
446
  kind: "tool-result",
@@ -303,7 +459,15 @@ export async function* streamRuntimeExecution(options) {
303
459
  if (deferredStreamContent.length > 0) {
304
460
  yield* flushDeferredStreamContent();
305
461
  }
306
- if (projectionState.emittedOutput || projectionState.emittedToolResult || projectionState.emittedToolError) {
462
+ if (hasMissingDelegatedExecutionEvidence(streamedExecutionEvidence)) {
463
+ throw createUnresolvedExecutionError(streamedExecutionEvidence);
464
+ }
465
+ if (hasMissingDelegatedFindings(streamedExecutionEvidence)) {
466
+ throw createUnresolvedExecutionError(streamedExecutionEvidence);
467
+ }
468
+ const hasUnresolvedStreamExecution = hasUnresolvedExecution(streamedExecutionEvidence);
469
+ if (projectionState.emittedOutput
470
+ || ((projectionState.emittedToolResult || projectionState.emittedToolError) && !hasUnresolvedStreamExecution)) {
307
471
  return;
308
472
  }
309
473
  }
@@ -429,6 +593,73 @@ export async function* streamRuntimeExecution(options) {
429
593
  const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
430
594
  ? result.metadata.executedToolResults
431
595
  : [];
596
+ const invokeExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState, executedToolResults });
597
+ if (hasUnresolvedExecution(invokeExecutionEvidence)) {
598
+ throw createUnresolvedExecutionError(invokeExecutionEvidence);
599
+ }
600
+ const invokeFallbackRecoveryInstruction = result.output
601
+ ? resolveExecutionWithoutToolEvidenceTextInstruction(request, result.output, false, {
602
+ ...invokeExecutionEvidence,
603
+ hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(invokeExecutionEvidence),
604
+ })
605
+ : null;
606
+ if (invokeFallbackRecoveryInstruction) {
607
+ const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, invokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
608
+ const recoveredToolResults = Array.isArray(recovered.metadata?.executedToolResults)
609
+ ? recovered.metadata.executedToolResults
610
+ : [];
611
+ const originalExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState, executedToolResults });
612
+ const recoveredExecutionEvidence = buildExecutionRecoveryEvidence({
613
+ projectionState: createStreamEventProjectionState(),
614
+ executedToolResults: recoveredToolResults,
615
+ });
616
+ const recoveredVisibleOutput = recovered.output ? toVisibleContent(recovered.output) : "";
617
+ const recoveredCarriesExecutionEvidence = recoveredExecutionEvidence.hasToolResultEvidence
618
+ || recoveredExecutionEvidence.hasOpenTaskDelegation
619
+ || recoveredExecutionEvidence.hasDelegatedExecutionToolEvidence;
620
+ const recoveredHasUnresolvedExecution = hasUnresolvedExecution(recoveredExecutionEvidence)
621
+ || hasMissingDelegatedExecutionEvidence(recoveredExecutionEvidence)
622
+ || hasMissingDelegatedFindings(recoveredExecutionEvidence)
623
+ || (!recoveredCarriesExecutionEvidence
624
+ && (hasUnresolvedExecution(originalExecutionEvidence)
625
+ || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
626
+ || hasMissingDelegatedFindings(originalExecutionEvidence)));
627
+ const effectiveRecoveredEvidence = recoveredCarriesExecutionEvidence
628
+ ? recoveredExecutionEvidence
629
+ : {
630
+ ...recoveredExecutionEvidence,
631
+ hasIncompletePlanState: originalExecutionEvidence.hasIncompletePlanState,
632
+ hasFailedTaskDelegation: originalExecutionEvidence.hasFailedTaskDelegation,
633
+ hasOpenTaskDelegation: originalExecutionEvidence.hasOpenTaskDelegation,
634
+ hasDelegatedAgentWithConfiguredTools: originalExecutionEvidence.hasDelegatedAgentWithConfiguredTools,
635
+ hasDelegatedExecutionToolEvidence: originalExecutionEvidence.hasDelegatedExecutionToolEvidence,
636
+ };
637
+ if (recoveredHasUnresolvedExecution
638
+ || (recoveredHasUnresolvedExecution && recoveredExecutionEvidence.hasToolResultEvidence && !recoveredVisibleOutput)) {
639
+ throw createUnresolvedExecutionError(effectiveRecoveredEvidence);
640
+ }
641
+ for (const toolResult of recoveredToolResults) {
642
+ yield {
643
+ kind: "tool-result",
644
+ toolName: toolResult.toolName,
645
+ output: toolResult.output,
646
+ isError: toolResult.isError,
647
+ };
648
+ }
649
+ if (recovered.output) {
650
+ const visible = toVisibleContent(recovered.output);
651
+ if (visible) {
652
+ yield { kind: "content", content: visible };
653
+ }
654
+ }
655
+ return;
656
+ }
657
+ if (hasMissingDelegatedExecutionEvidence(invokeExecutionEvidence)) {
658
+ throw createUnresolvedExecutionError(invokeExecutionEvidence);
659
+ }
660
+ if (hasMissingDelegatedFindings(invokeExecutionEvidence)) {
661
+ throw createUnresolvedExecutionError(invokeExecutionEvidence);
662
+ }
432
663
  for (const toolResult of executedToolResults) {
433
664
  yield {
434
665
  kind: "tool-result",
@@ -4,7 +4,42 @@ import { canReplayToolCallsLocally } from "./tool/tool-replay.js";
4
4
  import { extractToolCallsFromResult, normalizeToolArgsForSchema, stringifyToolOutput } from "./tool/tool-arguments.js";
5
5
  import { extractMemoryCandidatesFromToolOutput } from "../harness/system/runtime-memory-candidates.js";
6
6
  import { maybePersistLargeToolOutput } from "./tool/tool-output-artifacts.js";
7
+ import { appendToolRecoveryInstruction, extractVisibleOutput, resolveExecutionWithoutToolEvidenceTextInstruction, sanitizeVisibleText, } from "../parsing/output-parsing.js";
8
+ import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
7
9
  const TOOL_FOLLOW_UP_INSTRUCTION = "One or more tool results are already available in this conversation. Answer the user's current request directly from the existing context and tool results. Do not ask the user to repeat inputs that are already present above.";
10
+ function readPlanStateSummary(output) {
11
+ if (typeof output !== "object" || output === null) {
12
+ return null;
13
+ }
14
+ const typed = output;
15
+ const summaryContainer = typed.summary;
16
+ if (typeof summaryContainer !== "object" || summaryContainer === null) {
17
+ return null;
18
+ }
19
+ const nested = summaryContainer;
20
+ const counts = nested.summary;
21
+ if (typeof counts !== "object" || counts === null) {
22
+ return null;
23
+ }
24
+ const typedCounts = counts;
25
+ return {
26
+ pending: typeof typedCounts.pending === "number" ? typedCounts.pending : 0,
27
+ inProgress: typeof typedCounts.inProgress === "number" ? typedCounts.inProgress : 0,
28
+ };
29
+ }
30
+ function hasIncompleteExecutedPlan(executedToolResults) {
31
+ for (const latest of [...executedToolResults].reverse()) {
32
+ const summary = readPlanStateSummary(latest.output);
33
+ if (!summary) {
34
+ continue;
35
+ }
36
+ return summary.pending > 0 || summary.inProgress > 0;
37
+ }
38
+ return false;
39
+ }
40
+ function hasNonTodoToolEvidence(executedToolResults) {
41
+ return executedToolResults.some((item) => item.toolName !== "write_todos" && item.toolName !== "read_todos");
42
+ }
8
43
  function extractLatestUserInput(request) {
9
44
  const typedRequest = request;
10
45
  const messages = Array.isArray(typedRequest.messages) ? typedRequest.messages : [];
@@ -40,6 +75,24 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
40
75
  pendingResult = undefined;
41
76
  const toolCalls = extractToolCallsFromResult(result);
42
77
  if (toolCalls.length === 0) {
78
+ const terminalText = sanitizeVisibleText(extractVisibleOutput(result) || "");
79
+ const hasIncompletePlanState = hasIncompleteExecutedPlan(executedToolResults);
80
+ const hasExecutionBeyondTodoPlanning = hasNonTodoToolEvidence(executedToolResults);
81
+ const recoveryInstruction = terminalText
82
+ ? resolveExecutionWithoutToolEvidenceTextInstruction(activeRequest, terminalText, false, {
83
+ hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
84
+ hasIncompletePlanState: hasExecutionBeyondTodoPlanning && hasIncompletePlanState,
85
+ })
86
+ : hasIncompletePlanState && hasExecutionBeyondTodoPlanning
87
+ ? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
88
+ : null;
89
+ if (recoveryInstruction) {
90
+ if (iteration + 1 === maxToolIterations) {
91
+ throw new Error(`Tool-calling loop exceeded the maximum of ${maxToolIterations} iterations`);
92
+ }
93
+ activeRequest = appendToolRecoveryInstruction(activeRequest, recoveryInstruction);
94
+ continue;
95
+ }
43
96
  break;
44
97
  }
45
98
  if (!canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools)) {