@botbotgo/agent-harness 0.0.338 → 0.0.341

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 (32) hide show
  1. package/dist/contracts/workspace.d.ts +10 -0
  2. package/dist/package-version.d.ts +2 -2
  3. package/dist/package-version.js +2 -2
  4. package/dist/runtime/adapter/flow/execution-context.js +3 -2
  5. package/dist/runtime/adapter/flow/stream-runtime.d.ts +6 -0
  6. package/dist/runtime/adapter/flow/stream-runtime.js +54 -15
  7. package/dist/runtime/adapter/invocation-result.js +111 -9
  8. package/dist/runtime/adapter/local-tool-invocation.js +21 -1
  9. package/dist/runtime/adapter/middleware/context-hygiene.d.ts +5 -0
  10. package/dist/runtime/adapter/middleware/context-hygiene.js +83 -0
  11. package/dist/runtime/adapter/middleware-assembly.d.ts +11 -0
  12. package/dist/runtime/adapter/middleware-assembly.js +154 -178
  13. package/dist/runtime/adapter/model/invocation-request.js +39 -1
  14. package/dist/runtime/adapter/runtime-adapter-support.js +33 -3
  15. package/dist/runtime/adapter/stream-event-projection.js +6 -5
  16. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +7 -0
  17. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +31 -24
  18. package/dist/runtime/agent-runtime-adapter.d.ts +3 -2
  19. package/dist/runtime/agent-runtime-adapter.js +128 -9
  20. package/dist/runtime/agent-runtime-assembly.d.ts +1 -0
  21. package/dist/runtime/agent-runtime-assembly.js +10 -2
  22. package/dist/runtime/harness/run/inspection.js +4 -5
  23. package/dist/runtime/harness/run/stream-run.js +180 -50
  24. package/dist/runtime/parsing/output-parsing.d.ts +1 -1
  25. package/dist/runtime/parsing/output-parsing.js +1 -1
  26. package/dist/runtime/parsing/output-recovery.d.ts +9 -0
  27. package/dist/runtime/parsing/output-recovery.js +46 -1
  28. package/dist/runtime/support/compiled-binding.d.ts +5 -0
  29. package/dist/runtime/support/compiled-binding.js +12 -0
  30. package/dist/workspace/agent-binding-compiler.js +8 -0
  31. package/dist/workspace/object-loader.js +6 -0
  32. package/package.json +1 -1
@@ -174,6 +174,7 @@ export type CompiledTool = {
174
174
  };
175
175
  };
176
176
  export type CompiledSubAgent = {
177
+ agentId?: string;
177
178
  name: string;
178
179
  description: string;
179
180
  systemPrompt: string;
@@ -183,6 +184,10 @@ export type CompiledSubAgent = {
183
184
  skills?: string[];
184
185
  responseFormat?: unknown;
185
186
  middleware?: Array<Record<string, unknown>>;
187
+ builtinTools?: {
188
+ filesystem?: boolean;
189
+ todos?: boolean;
190
+ };
186
191
  };
187
192
  export type CompiledAsyncSubAgent = {
188
193
  name: string;
@@ -219,6 +224,11 @@ export type DeepAgentParams = {
219
224
  name: string;
220
225
  memory: string[];
221
226
  skills: string[];
227
+ interactionMode?: "stream" | "invoke";
228
+ builtinTools?: {
229
+ filesystem?: boolean;
230
+ todos?: boolean;
231
+ };
222
232
  };
223
233
  export type LegacyLangChainAgentParams = LangChainAgentParams & {
224
234
  passthrough?: Record<string, unknown>;
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.337";
2
- export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-23";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.341";
2
+ export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.337";
2
- export const AGENT_HARNESS_RELEASE_DATE = "2026-04-23";
1
+ export const AGENT_HARNESS_VERSION = "0.0.341";
2
+ export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,5 +1,5 @@
1
1
  import { buildExecutableToolMap } from "../tool-resolution.js";
2
- import { getBindingPrimaryModel, getBindingPrimaryTools, isLangChainBinding } from "../../support/compiled-binding.js";
2
+ import { getBindingInteractionMode, getBindingPrimaryModel, getBindingPrimaryTools, isLangChainBinding } from "../../support/compiled-binding.js";
3
3
  export function buildBindingToolCatalog(input) {
4
4
  const primaryTools = getBindingPrimaryTools(input.binding);
5
5
  const toolNameMapping = input.getToolNameMapping(input.binding);
@@ -54,6 +54,7 @@ export async function resolveRuntimeStreamExecutionContext(input) {
54
54
  binding: input.binding,
55
55
  getToolNameMapping: input.getToolNameMapping,
56
56
  });
57
+ const interactionMode = getBindingInteractionMode(input.binding) ?? "stream";
57
58
  const { forceInvokeFallback, canUseDirectModelStream, langChainStreamModel, } = await resolveLangChainStreamContext({
58
59
  binding: input.binding,
59
60
  resolveModel: input.resolveModel,
@@ -62,7 +63,7 @@ export async function resolveRuntimeStreamExecutionContext(input) {
62
63
  return {
63
64
  primaryTools,
64
65
  toolNameMapping,
65
- forceInvokeFallback,
66
+ forceInvokeFallback: interactionMode === "invoke" ? true : forceInvokeFallback,
66
67
  canUseDirectModelStream,
67
68
  langChainStreamModel,
68
69
  };
@@ -30,6 +30,12 @@ export declare function streamRuntimeExecution(options: {
30
30
  stream?: (input: unknown) => Promise<AsyncIterable<unknown>>;
31
31
  };
32
32
  createRunnable: () => Promise<RunnableLike>;
33
+ resolveInvocationConfig?: (binding: CompiledAgentBinding, options: {
34
+ sessionId: string;
35
+ requestId: string;
36
+ context?: Record<string, unknown>;
37
+ toolRuntimeContext?: Record<string, unknown>;
38
+ }) => Record<string, unknown>;
33
39
  withTimeout: <T>(producer: () => T | Promise<T>, timeoutMs: number | undefined, operation: string, stage?: "stream" | "invoke") => Promise<T>;
34
40
  iterateWithTimeout: <T>(iterable: AsyncIterable<T>, timeoutMs: number | undefined, operation: string, deadlineAt?: number, deadlineTimeoutMs?: number) => AsyncGenerator<T>;
35
41
  invokeTimeoutMs?: number;
@@ -1,4 +1,4 @@
1
- import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
1
+ import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, 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";
@@ -152,6 +152,22 @@ export async function* streamRuntimeExecution(options) {
152
152
  const deferredStreamContent = [];
153
153
  let sawRetrySafeInvalidToolSelectionError = false;
154
154
  const projectionState = createStreamEventProjectionState();
155
+ const requestId = options.runtimeOptions.requestId ?? options.sessionId;
156
+ const buildRunnableConfig = (extra) => ({
157
+ ...(options.resolveInvocationConfig
158
+ ? options.resolveInvocationConfig(options.binding, {
159
+ sessionId: options.sessionId,
160
+ requestId,
161
+ ...(options.runtimeOptions.context ? { context: options.runtimeOptions.context } : {}),
162
+ ...(options.runtimeOptions.toolRuntimeContext ? { toolRuntimeContext: options.runtimeOptions.toolRuntimeContext } : {}),
163
+ })
164
+ : {
165
+ configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: options.sessionId, [UPSTREAM_REQUEST_CONFIG_KEY]: requestId },
166
+ ...(options.runtimeOptions.context ? { context: options.runtimeOptions.context } : {}),
167
+ ...(options.runtimeOptions.toolRuntimeContext ? { toolRuntimeContext: options.runtimeOptions.toolRuntimeContext } : {}),
168
+ }),
169
+ ...(extra ?? {}),
170
+ });
155
171
  const shouldDeferStreamContent = () => shouldValidateStreamOutput && !emittedUnsafeStreamSideEffects;
156
172
  const flushDeferredStreamContent = async function* () {
157
173
  while (deferredStreamContent.length > 0) {
@@ -293,12 +309,7 @@ export async function* streamRuntimeExecution(options) {
293
309
  yield streamEventsStart.chunk;
294
310
  let events;
295
311
  try {
296
- events = await options.withTimeout(() => runnable.streamEvents(request, {
297
- configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: options.sessionId, [UPSTREAM_REQUEST_CONFIG_KEY]: options.runtimeOptions.requestId },
298
- version: "v2",
299
- ...(options.runtimeOptions.context ? { context: options.runtimeOptions.context } : {}),
300
- ...(options.runtimeOptions.toolRuntimeContext ? { toolRuntimeContext: options.runtimeOptions.toolRuntimeContext } : {}),
301
- }), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "agent streamEvents start", "stream");
312
+ events = await options.withTimeout(() => runnable.streamEvents(request, buildRunnableConfig({ version: "v2" })), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "agent streamEvents start", "stream");
302
313
  if (shouldProfile)
303
314
  yield finishProfileStep({
304
315
  id: "profile:agent:stream-events-start",
@@ -370,12 +381,24 @@ export async function* streamRuntimeExecution(options) {
370
381
  const terminalVisibleOutput = readTerminalEventVisibleOutput(event);
371
382
  if (terminalVisibleOutput) {
372
383
  const terminalExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
384
+ const terminalMissingPlanRecoveryInstruction = !terminalExecutionEvidence.hasDelegatedAgentWithConfiguredTools
385
+ && !terminalExecutionEvidence.hasOpenTaskDelegation
386
+ && !projectionState.emittedSuccessfulTaskResult
387
+ ? resolveMissingPlanRecoveryInstruction({
388
+ request,
389
+ assistantText: terminalVisibleOutput,
390
+ hasPlanStateEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
391
+ hasWriteTodosEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
392
+ hasToolResultEvidence: terminalExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
393
+ })
394
+ : null;
373
395
  if (!shouldDeferStreamContent()
374
396
  && !terminalExecutionEvidence.hasIncompletePlanState
375
397
  && !terminalExecutionEvidence.hasFailedTaskDelegation
376
398
  && !terminalExecutionEvidence.hasOpenTaskDelegation
377
399
  && !hasMissingDelegatedExecutionEvidence(terminalExecutionEvidence)
378
- && !hasMissingDelegatedFindings(terminalExecutionEvidence)) {
400
+ && !hasMissingDelegatedFindings(terminalExecutionEvidence)
401
+ && !terminalMissingPlanRecoveryInstruction) {
379
402
  if (deferredStreamContent.length > 0) {
380
403
  yield* flushDeferredStreamContent();
381
404
  }
@@ -419,9 +442,18 @@ export async function* streamRuntimeExecution(options) {
419
442
  hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(streamedExecutionEvidence),
420
443
  })
421
444
  : null;
445
+ const missingPlanRecoveryInstruction = !hasUnresolvedExecution(streamedExecutionEvidence) && !delegatedExecutionRecoveryInstruction
446
+ ? resolveMissingPlanRecoveryInstruction({
447
+ request,
448
+ assistantText: projectionState.emittedOutput,
449
+ hasPlanStateEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
450
+ hasWriteTodosEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
451
+ hasToolResultEvidence: streamedExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
452
+ })
453
+ : null;
422
454
  const retryInstruction = !emittedUnsafeStreamSideEffects && sawRetrySafeInvalidToolSelectionError
423
455
  ? INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION
424
- : delegatedExecutionRecoveryInstruction ?? executionWithoutToolEvidenceInstruction;
456
+ : delegatedExecutionRecoveryInstruction ?? missingPlanRecoveryInstruction ?? executionWithoutToolEvidenceInstruction;
425
457
  if (retryInstruction) {
426
458
  let retried;
427
459
  retried = await options.invoke(options.applyToolRecoveryInstruction(options.binding, retryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
@@ -499,10 +531,7 @@ export async function* streamRuntimeExecution(options) {
499
531
  yield streamStart.chunk;
500
532
  let stream;
501
533
  try {
502
- stream = await options.withTimeout(() => runnable.stream(request, {
503
- configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: options.sessionId, [UPSTREAM_REQUEST_CONFIG_KEY]: options.runtimeOptions.requestId },
504
- ...(options.runtimeOptions.toolRuntimeContext ? { toolRuntimeContext: options.runtimeOptions.toolRuntimeContext } : {}),
505
- }), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "agent stream start", "stream");
534
+ stream = await options.withTimeout(() => runnable.stream(request, buildRunnableConfig()), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "agent stream start", "stream");
506
535
  if (shouldProfile)
507
536
  yield finishProfileStep({
508
537
  id: "profile:agent:stream-start",
@@ -620,8 +649,18 @@ export async function* streamRuntimeExecution(options) {
620
649
  hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(invokeExecutionEvidence),
621
650
  })
622
651
  : resolveDelegatedExecutionRecoveryInstruction(invokeExecutionEvidence);
623
- if (invokeFallbackRecoveryInstruction) {
624
- const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, invokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
652
+ const invokeFallbackMissingPlanRecoveryInstruction = !hasUnresolvedExecution(invokeExecutionEvidence) && !invokeFallbackRecoveryInstruction
653
+ ? resolveMissingPlanRecoveryInstruction({
654
+ request,
655
+ assistantText: typeof result.output === "string" ? result.output : "",
656
+ hasPlanStateEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
657
+ hasWriteTodosEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
658
+ hasToolResultEvidence: invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
659
+ })
660
+ : null;
661
+ const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction ?? invokeFallbackRecoveryInstruction;
662
+ if (effectiveInvokeFallbackRecoveryInstruction) {
663
+ const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, effectiveInvokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
625
664
  const recoveredToolResults = Array.isArray(recovered.metadata?.executedToolResults)
626
665
  ? recovered.metadata.executedToolResults
627
666
  : [];
@@ -2,6 +2,7 @@ import { containsLikelySkillDocument, extractContentBlocks, extractEmptyAssistan
2
2
  import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
3
3
  import { buildStateSnapshot } from "./model/message-assembly.js";
4
4
  import { asRecord } from "./tool/resolved-tool.js";
5
+ import { renderToolFailure } from "../support/harness-support.js";
5
6
  function looksLikeLeakedToolCallText(value) {
6
7
  const normalized = sanitizeVisibleText(value).trim();
7
8
  if (!normalized) {
@@ -24,6 +25,16 @@ function isLowSignalStructuredCompletion(value) {
24
25
  }
25
26
  return /Status:\s*completed[\s\S]*Summary:\s*-\s*none[\s\S]*Likely Causes:\s*-\s*none[\s\S]*Blockers:\s*-\s*none[\s\S]*Next Commands:\s*-\s*none/i.test(normalized);
26
27
  }
28
+ function looksLikeToolBlocker(value) {
29
+ const normalized = sanitizeVisibleText(value).trim();
30
+ if (!normalized) {
31
+ return false;
32
+ }
33
+ return /^Blocked:\s*/iu.test(normalized)
34
+ || /error parsing tool call/iu.test(normalized)
35
+ || /invalid tool call/iu.test(normalized)
36
+ || /tool call.*schema/iu.test(normalized);
37
+ }
27
38
  function normalizeToolOutputText(output) {
28
39
  const directText = typeof output === "string"
29
40
  ? sanitizeVisibleText(output).trim()
@@ -56,14 +67,35 @@ function extractLatestSuccessfulTaskResultText(executedToolResults) {
56
67
  continue;
57
68
  }
58
69
  const normalized = normalizeToolOutputText(toolResult.output);
59
- if (normalized) {
70
+ if (normalized && !looksLikeToolBlocker(normalized)) {
60
71
  return normalized;
61
72
  }
62
73
  }
63
74
  return "";
64
75
  }
76
+ function looksLikeReportOutput(value) {
77
+ const normalized = sanitizeVisibleText(value).trim();
78
+ if (!normalized) {
79
+ return false;
80
+ }
81
+ return /(?:^|\n)\s*Status\s*:/iu.test(normalized)
82
+ || /(?:^|\n)\s*Summary\s*:/iu.test(normalized)
83
+ || /(?:^|\n)\s*Likely Causes\s*:/iu.test(normalized)
84
+ || /(?:^|\n)\s*Blockers\s*:/iu.test(normalized)
85
+ || /(?:^|\n)\s*Next Commands\s*:/iu.test(normalized);
86
+ }
87
+ function looksLikeRawCommandTranscript(value) {
88
+ const normalized = sanitizeVisibleText(value).trim();
89
+ if (!normalized) {
90
+ return false;
91
+ }
92
+ return /^(?:stdout|stderr)\s*:/iu.test(normalized)
93
+ || /(?:^|\n)\s*(?:stdout|stderr)\s*:/iu.test(normalized)
94
+ || /(?:^|\n)\s*exitCode\s*:\s*-?\d+\s*$/iu.test(normalized);
95
+ }
65
96
  function extractLatestSuccessfulNonTodoToolResultText(executedToolResults) {
66
- for (const toolResult of [...executedToolResults].reverse()) {
97
+ const candidates = [];
98
+ for (const toolResult of executedToolResults) {
67
99
  if (toolResult.isError === true) {
68
100
  continue;
69
101
  }
@@ -72,32 +104,102 @@ function extractLatestSuccessfulNonTodoToolResultText(executedToolResults) {
72
104
  }
73
105
  const normalized = normalizeToolOutputText(toolResult.output);
74
106
  if (normalized) {
75
- return normalized;
107
+ candidates.push(normalized);
76
108
  }
77
109
  }
78
- return "";
110
+ return [...candidates].reverse().find(looksLikeReportOutput)
111
+ ?? [...candidates].reverse().find((candidate) => !looksLikeRawCommandTranscript(candidate))
112
+ ?? candidates.at(-1)
113
+ ?? "";
114
+ }
115
+ function hasDelegationBlocker(executedToolResults) {
116
+ return executedToolResults.some((toolResult) => {
117
+ if (toolResult.toolName !== "task") {
118
+ return false;
119
+ }
120
+ if (toolResult.isError === true) {
121
+ return true;
122
+ }
123
+ const normalized = normalizeToolOutputText(toolResult.output);
124
+ return looksLikeToolBlocker(normalized);
125
+ });
126
+ }
127
+ function looksLikeClarificationQuestion(value) {
128
+ const normalized = sanitizeVisibleText(value).trim();
129
+ if (!normalized) {
130
+ return false;
131
+ }
132
+ return /[??]\s*$/u.test(normalized)
133
+ || /^(?:could you|can you|please provide|please share|i need|what is|which )/iu.test(normalized)
134
+ || /^(?:请问|请提供|能否提供|能不能提供)/u.test(normalized);
135
+ }
136
+ function looksLikeNonEvidenceApology(value) {
137
+ const normalized = sanitizeVisibleText(value).trim();
138
+ if (!normalized) {
139
+ return false;
140
+ }
141
+ return /(?:i(?:'|’)m sorry|sorry)[\s\S]*(?:couldn(?:'|’)t|cannot|can(?:'|’)t|unable to)\s+(?:complete|proceed|finish|continue)/iu.test(normalized)
142
+ || /(?:system limitation|technical limitation|internal limitation|recursion limit)/iu.test(normalized)
143
+ || /(?:抱歉|对不起)[\s\S]*(?:无法|不能|未能)(?:完成|继续|处理)/u.test(normalized);
144
+ }
145
+ function extractDeterministicToolFailureReport(executedToolResults) {
146
+ const hasSuccessfulSubstantiveTool = executedToolResults.some((toolResult) => (toolResult.isError !== true
147
+ && toolResult.toolName !== "write_todos"
148
+ && toolResult.toolName !== "read_todos"));
149
+ if (hasSuccessfulSubstantiveTool) {
150
+ return "";
151
+ }
152
+ const failures = executedToolResults.filter((toolResult) => toolResult.isError === true);
153
+ if (failures.length === 0) {
154
+ return "";
155
+ }
156
+ const blockerLines = failures
157
+ .slice(-3)
158
+ .map((toolResult) => `- ${renderToolFailure(toolResult.toolName, toolResult.output)}`);
159
+ return [
160
+ "Status: failed",
161
+ "Summary:",
162
+ "- The investigation could not proceed because required tools failed before any concrete evidence was gathered.",
163
+ "Likely Causes:",
164
+ "- none",
165
+ "Blockers:",
166
+ ...blockerLines,
167
+ "Next Commands:",
168
+ "- none",
169
+ ].join("\n");
79
170
  }
80
171
  export function resolveDeterministicFinalOutput(params) {
81
172
  const visibleOutput = params.visibleOutput ?? "";
82
173
  const toolFallback = params.toolFallback ?? "";
83
174
  const executedToolResults = params.executedToolResults ?? [];
84
- const delegatedTaskOutput = extractLatestSuccessfulTaskResultText(executedToolResults);
85
- if (delegatedTaskOutput) {
86
- return delegatedTaskOutput;
87
- }
88
175
  const sanitizedVisibleOutput = visibleOutput && !looksLikeLeakedToolCallText(visibleOutput)
89
176
  ? sanitizeVisibleText(visibleOutput).trim()
90
177
  : "";
178
+ const deterministicFailureReport = extractDeterministicToolFailureReport(executedToolResults);
179
+ const delegatedTaskOutput = extractLatestSuccessfulTaskResultText(executedToolResults);
180
+ const successfulToolOutput = extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
181
+ if (sanitizedVisibleOutput && successfulToolOutput && hasDelegationBlocker(executedToolResults)) {
182
+ return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput;
183
+ }
184
+ if (sanitizedVisibleOutput
185
+ && (looksLikeClarificationQuestion(sanitizedVisibleOutput) || looksLikeNonEvidenceApology(sanitizedVisibleOutput))) {
186
+ return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput || sanitizedVisibleOutput;
187
+ }
91
188
  if (sanitizedVisibleOutput && !isLowSignalStructuredCompletion(sanitizedVisibleOutput)) {
92
189
  return sanitizedVisibleOutput;
93
190
  }
94
- const successfulToolOutput = extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
191
+ if (delegatedTaskOutput) {
192
+ return delegatedTaskOutput;
193
+ }
95
194
  if (successfulToolOutput) {
96
195
  return successfulToolOutput;
97
196
  }
98
197
  const sanitizedToolFallback = toolFallback && !looksLikeLeakedToolCallText(toolFallback)
99
198
  ? sanitizeVisibleText(toolFallback).trim()
100
199
  : "";
200
+ if (deterministicFailureReport) {
201
+ return deterministicFailureReport;
202
+ }
101
203
  return isLowSignalStructuredCompletion(sanitizedToolFallback) ? "" : sanitizedToolFallback;
102
204
  }
103
205
  export function extractDelegatedFindingsText(executedToolResults) {
@@ -4,7 +4,7 @@ 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";
7
+ import { appendToolRecoveryInstruction, extractVisibleOutput, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, sanitizeVisibleText, } from "../parsing/output-parsing.js";
8
8
  import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
9
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
10
  function readPlanStateSummary(output) {
@@ -40,6 +40,9 @@ function hasIncompleteExecutedPlan(executedToolResults) {
40
40
  function hasNonTodoToolEvidence(executedToolResults) {
41
41
  return executedToolResults.some((item) => item.toolName !== "write_todos" && item.toolName !== "read_todos");
42
42
  }
43
+ function hasPlanStateEvidence(executedToolResults) {
44
+ return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos" || readPlanStateSummary(item.output) !== null);
45
+ }
43
46
  function extractLatestUserInput(request) {
44
47
  const typedRequest = request;
45
48
  const messages = Array.isArray(typedRequest.messages) ? typedRequest.messages : [];
@@ -80,7 +83,9 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
80
83
  const hasExecutionBeyondTodoPlanning = hasNonTodoToolEvidence(executedToolResults);
81
84
  const recoveryInstruction = terminalText
82
85
  ? resolveExecutionWithoutToolEvidenceTextInstruction(activeRequest, terminalText, false, {
86
+ hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
83
87
  hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
88
+ hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
84
89
  hasIncompletePlanState: hasExecutionBeyondTodoPlanning && hasIncompletePlanState,
85
90
  })
86
91
  : hasIncompletePlanState && hasExecutionBeyondTodoPlanning
@@ -95,6 +100,21 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
95
100
  }
96
101
  break;
97
102
  }
103
+ const missingPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
104
+ request: activeRequest,
105
+ hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
106
+ hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
107
+ hasToolResultEvidence: executedToolResults.length > 0 || toolCalls.length > 0,
108
+ });
109
+ if (missingPlanRecoveryInstruction
110
+ && toolCalls.some((toolCall) => {
111
+ const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
112
+ return resolvedToolName !== "write_todos" && resolvedToolName !== "read_todos" && toolCall.name !== "write_todos" && toolCall.name !== "read_todos";
113
+ })) {
114
+ activeRequest = appendToolRecoveryInstruction(activeRequest, missingPlanRecoveryInstruction);
115
+ pendingResult = undefined;
116
+ continue;
117
+ }
98
118
  if (!canReplayToolCallsLocally(binding, toolCalls, primaryTools, toolNameMapping, executableTools, builtinExecutableTools)) {
99
119
  break;
100
120
  }
@@ -0,0 +1,5 @@
1
+ export declare function pruneReasoningMetadataFromMessages(messages: unknown): {
2
+ messages: unknown;
3
+ changed: boolean;
4
+ };
5
+ export declare function createContextHygieneMiddleware(): unknown;
@@ -0,0 +1,83 @@
1
+ import { createMiddleware } from "langchain";
2
+ import { isRecord } from "../../../utils/object.js";
3
+ const REASONING_METADATA_KEYS = new Set([
4
+ "reasoning",
5
+ "reasoning_content",
6
+ "thinking",
7
+ "redacted_thinking",
8
+ ]);
9
+ const REASONING_CONTENT_BLOCK_TYPES = new Set([
10
+ "reasoning",
11
+ "thinking",
12
+ "redacted_thinking",
13
+ ]);
14
+ function pruneReasoningValue(value) {
15
+ if (Array.isArray(value)) {
16
+ let changed = false;
17
+ const next = [];
18
+ for (const item of value) {
19
+ if (isRecord(item) && typeof item.type === "string" && REASONING_CONTENT_BLOCK_TYPES.has(item.type)) {
20
+ changed = true;
21
+ continue;
22
+ }
23
+ const pruned = pruneReasoningValue(item);
24
+ changed = changed || pruned.changed;
25
+ next.push(pruned.value);
26
+ }
27
+ return changed ? { value: next, changed } : { value, changed: false };
28
+ }
29
+ if (!isRecord(value)) {
30
+ return { value, changed: false };
31
+ }
32
+ let changed = false;
33
+ const next = {};
34
+ for (const [key, entry] of Object.entries(value)) {
35
+ if (REASONING_METADATA_KEYS.has(key)) {
36
+ changed = true;
37
+ continue;
38
+ }
39
+ const pruned = pruneReasoningValue(entry);
40
+ changed = changed || pruned.changed;
41
+ next[key] = pruned.value;
42
+ }
43
+ return changed ? { value: next, changed } : { value, changed: false };
44
+ }
45
+ function pruneMessageReasoningMetadata(message) {
46
+ if (!isRecord(message)) {
47
+ return { message, changed: false };
48
+ }
49
+ let changed = false;
50
+ const next = Object.assign(Object.create(Object.getPrototypeOf(message)), message);
51
+ for (const key of ["additional_kwargs", "response_metadata", "content", "lc_kwargs", "kwargs"]) {
52
+ const pruned = pruneReasoningValue(message[key]);
53
+ if (pruned.changed) {
54
+ next[key] = pruned.value;
55
+ changed = true;
56
+ }
57
+ }
58
+ return changed ? { message: next, changed } : { message, changed: false };
59
+ }
60
+ export function pruneReasoningMetadataFromMessages(messages) {
61
+ if (!Array.isArray(messages)) {
62
+ return { messages, changed: false };
63
+ }
64
+ let changed = false;
65
+ const next = messages.map((message) => {
66
+ const pruned = pruneMessageReasoningMetadata(message);
67
+ changed = changed || pruned.changed;
68
+ return pruned.message;
69
+ });
70
+ return changed ? { messages: next, changed } : { messages, changed: false };
71
+ }
72
+ export function createContextHygieneMiddleware() {
73
+ return createMiddleware({
74
+ name: "HarnessContextHygieneMiddleware",
75
+ beforeModel: (state) => {
76
+ if (!isRecord(state)) {
77
+ return undefined;
78
+ }
79
+ const pruned = pruneReasoningMetadataFromMessages(state.messages);
80
+ return pruned.changed ? { messages: pruned.messages } : undefined;
81
+ },
82
+ });
83
+ }
@@ -1,6 +1,9 @@
1
1
  import { FilesystemBackend } from "deepagents";
2
2
  import type { CompiledAgentBinding, CompiledModel, CompiledSubAgent, DeepAgentParams, RuntimeAdapterOptions } from "../../contracts/types.js";
3
3
  import type { ExecutableTool } from "./flow/invoke-runtime.js";
4
+ type RunnableLike = {
5
+ invoke: (input: unknown, config?: Record<string, unknown>) => Promise<unknown>;
6
+ };
4
7
  export type LangChainRuntimeExtensionPlan = {
5
8
  includePatchToolCalls: boolean;
6
9
  includeAutomaticSummarization: boolean;
@@ -27,7 +30,13 @@ export type UpstreamSubagentConfig = {
27
30
  }>;
28
31
  responseFormat?: unknown;
29
32
  middleware?: unknown[];
33
+ runnable?: RunnableLike;
30
34
  };
35
+ export declare function extractSubagentRequestText(state: unknown): string;
36
+ export declare function wrapRequestResultAsSubagentResponse(result: {
37
+ output: string;
38
+ structuredResponse?: unknown;
39
+ }): Record<string, unknown>;
31
40
  export declare function buildBuiltinTaskSubagentMiddleware(input: {
32
41
  selectedSubagent: UpstreamSubagentConfig;
33
42
  builtinBackend: unknown;
@@ -49,6 +58,7 @@ export declare function resolveSubagents(input: {
49
58
  resolveModel: (model: CompiledModel) => Promise<unknown>;
50
59
  resolveTools: (tools: Parameters<DeepAgentParams["tools"]["slice"]>[0] extends never ? never : any, binding?: CompiledAgentBinding) => unknown[];
51
60
  createDeclaredMiddlewareResolverOptions: (binding?: CompiledAgentBinding) => unknown;
61
+ resolveBackend?: (binding?: CompiledAgentBinding) => unknown;
52
62
  }): Promise<UpstreamSubagentConfig[]>;
53
63
  export declare function invokeBuiltinTaskTool(input: {
54
64
  binding: CompiledAgentBinding;
@@ -118,3 +128,4 @@ export declare function resolveMiddleware(input: {
118
128
  createDeclaredMiddlewareResolverOptions: (binding?: CompiledAgentBinding) => unknown;
119
129
  resolveLangChainRuntimeExtensionMiddleware: (binding: CompiledAgentBinding) => Promise<unknown[]>;
120
130
  }): Promise<unknown[]>;
131
+ export {};