@botbotgo/agent-harness 0.0.403 → 0.0.405

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.
@@ -13,7 +13,7 @@ spec:
13
13
  # LangChain aligned feature: concrete embedding model identifier passed to the provider integration.
14
14
  model: nomic-embed-text
15
15
  # LangChain aligned feature: provider-specific initialization options for embeddings.
16
- baseUrl: ${env:AGENT_HARNESS_OLLAMA_BASE_URL:-http://127.0.0.1:11434}
16
+ baseUrl: http://127.0.0.1:11434
17
17
 
18
18
  # ===================
19
19
  # DeepAgents Features
@@ -18,12 +18,12 @@ spec:
18
18
  provider: ollama
19
19
  # LangChain aligned feature: concrete model identifier passed to the selected provider integration.
20
20
  # Example values depend on `provider`, such as `gpt-oss:latest` for `ollama`.
21
- model: gemma4:e2b
21
+ model: granite4.1:3b
22
22
  # LangChain aligned feature: provider-specific initialization options.
23
23
  # Write these fields directly on the model object.
24
24
  # Common examples include `baseUrl`, `temperature`, and auth/client settings.
25
25
  # `baseUrl` configures the Ollama-compatible endpoint used by the model client.
26
26
  # For `openai-compatible`, `baseUrl` is normalized into the ChatOpenAI `configuration.baseURL` field.
27
- baseUrl: ${env:AGENT_HARNESS_OLLAMA_BASE_URL:-http://127.0.0.1:11434}
27
+ baseUrl: http://127.0.0.1:11434
28
28
  # LangChain aligned feature: provider/model initialization option controlling sampling temperature.
29
29
  temperature: 0.2
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.403";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.405";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.403";
1
+ export const AGENT_HARNESS_VERSION = "0.0.405";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
@@ -708,7 +708,9 @@ export async function* streamRuntimeExecution(options) {
708
708
  }
709
709
  const streamedDelegatedRecoveryInstruction = resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence);
710
710
  const streamedDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, streamedExecutionEvidence);
711
- const streamedIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding) && streamedExecutionEvidence.hasIncompletePlanState
711
+ const streamedIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
712
+ && streamedExecutionEvidence.hasIncompletePlanState
713
+ && streamedExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
712
714
  ? CLOSE_REQUIRED_PLAN_RECOVERY_INSTRUCTION
713
715
  : null;
714
716
  const streamedPrematurePlanCloseRecoveryInstruction = requiresPlanEvidence(options.binding)
@@ -721,7 +723,8 @@ export async function* streamRuntimeExecution(options) {
721
723
  : null;
722
724
  if (hasUnresolvedExecution(streamedExecutionEvidence)
723
725
  && !delegatedExecutionRecoveryInstruction
724
- && !streamedIncompletePlanRecoveryInstruction) {
726
+ && !streamedIncompletePlanRecoveryInstruction
727
+ && !streamedPrematurePlanCloseRecoveryInstruction) {
725
728
  throw createUnresolvedExecutionError(streamedExecutionEvidence);
726
729
  }
727
730
  const executionWithoutToolEvidenceInstruction = projectionState.emittedOutput
@@ -968,10 +971,18 @@ export async function* streamRuntimeExecution(options) {
968
971
  })
969
972
  : null;
970
973
  const invokeFallbackDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, invokeExecutionEvidence);
971
- const invokeFallbackIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding) && invokeExecutionEvidence.hasIncompletePlanState
974
+ const invokeFallbackIncompletePlanRecoveryInstruction = requiresPlanEvidence(options.binding)
975
+ && invokeExecutionEvidence.hasIncompletePlanState
976
+ && invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
972
977
  ? CLOSE_REQUIRED_PLAN_RECOVERY_INSTRUCTION
973
978
  : null;
979
+ const invokeFallbackPlanWithoutEvidenceRecoveryInstruction = requiresPlanEvidence(options.binding)
980
+ && invokeExecutionEvidence.hasPlanStateEvidence
981
+ && !invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence
982
+ ? RUN_EVIDENCE_AFTER_PREMATURE_PLAN_CLOSE_INSTRUCTION
983
+ : null;
974
984
  const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackIncompletePlanRecoveryInstruction
985
+ ?? invokeFallbackPlanWithoutEvidenceRecoveryInstruction
975
986
  ?? invokeFallbackMissingPlanRecoveryInstruction
976
987
  ?? invokeFallbackDelegationOnlyRecoveryInstruction
977
988
  ?? invokeFallbackRecoveryInstruction;
@@ -10,9 +10,6 @@ import { appendToolRecoveryInstruction, extractVisibleOutput, resolveMissingPlan
10
10
  import { salvageJsonToolCalls } from "../parsing/output-tool-args.js";
11
11
  import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../prompts/runtime-prompts.js";
12
12
  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.";
13
- function isObject(value) {
14
- return typeof value === "object" && value !== null && !Array.isArray(value);
15
- }
16
13
  function readPlanStateSummary(output) {
17
14
  if (typeof output !== "object" || output === null) {
18
15
  return null;
@@ -61,159 +58,6 @@ function isFallbackTodoCompletionToolCall(toolCall) {
61
58
  && toolCall.id.startsWith("fallback-complete-")
62
59
  && (toolCall.name === "write_todos" || toolCall.name === "tool_call_write_todos");
63
60
  }
64
- function isTerminalTodoUpdateToolCall(toolCall) {
65
- if (!isPlanToolName(toolCall.name) || normalizeToolName(toolCall.name).includes("read_todos")) {
66
- return false;
67
- }
68
- if (typeof toolCall.args !== "object" || toolCall.args === null || !Array.isArray(toolCall.args.todos)) {
69
- return false;
70
- }
71
- const todos = toolCall.args.todos;
72
- return todos.length > 0 && todos.every((todo) => {
73
- if (typeof todo !== "object" || todo === null || typeof todo.status !== "string") {
74
- return false;
75
- }
76
- const status = todo.status.trim().toLowerCase();
77
- return status !== "pending" && status !== "in_progress";
78
- });
79
- }
80
- function readSchemaShape(schema) {
81
- if (!isObject(schema)) {
82
- return null;
83
- }
84
- if (isObject(schema.properties)) {
85
- return schema.properties;
86
- }
87
- if (isObject(schema.shape)) {
88
- return schema.shape;
89
- }
90
- const def = schema._def;
91
- if (!def) {
92
- return null;
93
- }
94
- const shape = typeof def.shape === "function" ? def.shape() : def.shape;
95
- return isObject(shape) ? shape : null;
96
- }
97
- function readSchemaDescription(schemaPart) {
98
- if (!isObject(schemaPart)) {
99
- return "";
100
- }
101
- const direct = schemaPart.description;
102
- if (typeof direct === "string") {
103
- return direct;
104
- }
105
- const nested = schemaPart._def;
106
- if (typeof nested?.description === "string") {
107
- return nested.description;
108
- }
109
- return readSchemaDescription(nested?.innerType);
110
- }
111
- function readSchemaDefault(schemaPart) {
112
- if (!isObject(schemaPart)) {
113
- return undefined;
114
- }
115
- const typed = schemaPart;
116
- const hasJsonDefault = Object.prototype.hasOwnProperty.call(schemaPart, "default") && typeof typed.default !== "function";
117
- if (hasJsonDefault) {
118
- return typed.default;
119
- }
120
- if (Object.prototype.hasOwnProperty.call(schemaPart, "const")) {
121
- return typed.const;
122
- }
123
- const def = schemaPart._def;
124
- if (!def) {
125
- return undefined;
126
- }
127
- if (def.defaultValue !== undefined) {
128
- return typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
129
- }
130
- return readSchemaDefault(def.innerType);
131
- }
132
- function parseFirstStringArrayExample(description) {
133
- const arrayMatch = description.match(/\[[^\]]+\]/u);
134
- if (!arrayMatch) {
135
- return null;
136
- }
137
- const values = [...arrayMatch[0].matchAll(/["']([^"']+)["']/gu)].map((match) => match[1]).filter(Boolean);
138
- return values.length > 0 ? values : null;
139
- }
140
- function buildGenericFallbackArgsFromSchema(schema, latestUserInput) {
141
- const shape = readSchemaShape(schema);
142
- if (!shape) {
143
- return {};
144
- }
145
- const args = {};
146
- for (const [key, schemaPart] of Object.entries(shape)) {
147
- const defaultValue = readSchemaDefault(schemaPart);
148
- if (defaultValue !== undefined) {
149
- args[key] = defaultValue;
150
- continue;
151
- }
152
- const description = readSchemaDescription(schemaPart);
153
- const arrayExample = parseFirstStringArrayExample(description);
154
- if (arrayExample) {
155
- args[key] = arrayExample;
156
- continue;
157
- }
158
- if (latestUserInput
159
- && !args[key]
160
- && /(?:query|question|prompt|input|text)/iu.test(`${key} ${description}`)) {
161
- args[key] = latestUserInput;
162
- }
163
- }
164
- return args;
165
- }
166
- function readTodoPlanTextFromToolCalls(toolCalls) {
167
- const fragments = [];
168
- for (const toolCall of toolCalls) {
169
- if (typeof toolCall.args !== "object" || toolCall.args === null) {
170
- continue;
171
- }
172
- const todos = toolCall.args.todos;
173
- if (!Array.isArray(todos)) {
174
- continue;
175
- }
176
- for (const todo of todos) {
177
- if (typeof todo === "object" && todo !== null && typeof todo.content === "string") {
178
- fragments.push(todo.content);
179
- }
180
- }
181
- }
182
- return fragments.join("\n");
183
- }
184
- function selectGenericFallbackEvidenceTool(params) {
185
- const candidates = [];
186
- const appendCandidate = (name) => {
187
- if (isPlanToolName(name)) {
188
- return;
189
- }
190
- const resolved = resolveModelFacingToolName(name, params.toolNameMapping, params.primaryTools);
191
- const executable = params.executableTools.get(name)
192
- ?? params.executableTools.get(resolved)
193
- ?? params.builtinExecutableTools.get(name)
194
- ?? params.builtinExecutableTools.get(resolved);
195
- if (!executable || candidates.some((candidate) => candidate.executable.name === executable.name)) {
196
- return;
197
- }
198
- candidates.push({ requestedName: name, executable });
199
- };
200
- for (const tool of params.primaryTools) {
201
- appendCandidate(tool.name);
202
- const modelFacing = params.toolNameMapping.originalToModelFacing.get(tool.name);
203
- if (modelFacing) {
204
- appendCandidate(modelFacing);
205
- }
206
- }
207
- for (const name of [...params.executableTools.keys(), ...params.builtinExecutableTools.keys()]) {
208
- appendCandidate(name);
209
- }
210
- if (candidates.length === 0) {
211
- return null;
212
- }
213
- const normalizedPlanText = params.planText.toLowerCase();
214
- return candidates.find((candidate) => normalizedPlanText.includes(candidate.requestedName.toLowerCase())
215
- || normalizedPlanText.includes(candidate.executable.name.toLowerCase())) ?? candidates[0];
216
- }
217
61
  function buildDeterministicFinalFromToolEvidence(executedToolResults) {
218
62
  const evidence = executedToolResults
219
63
  .filter((item) => item.isError !== true && item.toolName !== "write_todos" && item.toolName !== "read_todos")
@@ -294,19 +138,12 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
294
138
  const hasIncompletePlanState = hasIncompleteExecutedPlan(executedToolResults);
295
139
  const shouldEnforceIncompletePlan = requiresPlanEvidence(binding) && hasIncompletePlanState;
296
140
  const hasExecutionBeyondTodoPlanning = hasNonTodoToolEvidence(executedToolResults);
297
- const missingInitialPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
298
- request: activeRequest,
299
- requiresPlan: requiresPlanEvidence(binding),
300
- hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
301
- hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
302
- hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
303
- });
304
141
  const toolErrorRecoveryInstruction = latestToolErrorRecoveryInstruction(executedToolResults)
305
142
  ?? terminalToolErrorRecoveryInstruction(terminalText);
306
143
  const leakedJsonToolCallRecoveryInstruction = terminalText && salvageJsonToolCalls(terminalText).length > 0
307
144
  ? STRICT_TOOL_JSON_INSTRUCTION
308
145
  : null;
309
- const recoveryInstruction = toolErrorRecoveryInstruction ?? leakedJsonToolCallRecoveryInstruction ?? missingInitialPlanRecoveryInstruction ?? (terminalText
146
+ const recoveryInstruction = toolErrorRecoveryInstruction ?? leakedJsonToolCallRecoveryInstruction ?? (terminalText
310
147
  ? resolveExecutionWithoutToolEvidenceTextInstruction(activeRequest, terminalText, false, {
311
148
  hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
312
149
  hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
@@ -415,53 +252,6 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
415
252
  content: stringifyToolOutput(safeToolResult),
416
253
  }));
417
254
  }
418
- if (requiresPlanEvidence(binding)
419
- && !hadNonTodoEvidenceBeforeToolReplay
420
- && !hasNonTodoToolEvidence(executedToolResults)
421
- && toolCalls.length > 0
422
- && toolCalls.every((toolCall) => isPlanToolName(toolCall.name))
423
- && toolCalls.some(isTerminalTodoUpdateToolCall)) {
424
- const fallbackEvidenceTool = selectGenericFallbackEvidenceTool({
425
- planText: readTodoPlanTextFromToolCalls(toolCalls),
426
- primaryTools,
427
- toolNameMapping,
428
- executableTools,
429
- builtinExecutableTools,
430
- });
431
- if (fallbackEvidenceTool) {
432
- const fallbackArgs = buildGenericFallbackArgsFromSchema(fallbackEvidenceTool.executable.schema, latestUserInput);
433
- const normalizedArgs = normalizeToolArgsForSchema(fallbackArgs, fallbackEvidenceTool.executable.schema, undefined, {
434
- latestUserInput,
435
- });
436
- const compiledTool = toolCatalog.get(fallbackEvidenceTool.requestedName) ?? toolCatalog.get(fallbackEvidenceTool.executable.name);
437
- const gateway = validateToolGatewayInput({
438
- toolName: fallbackEvidenceTool.executable.name,
439
- schema: fallbackEvidenceTool.executable.schema,
440
- args: normalizedArgs,
441
- requiresApproval: compiledTool ? toolRequiresRuntimeApproval(compiledTool) : false,
442
- });
443
- if (gateway.ok) {
444
- const toolResult = toolRuntimeContext
445
- ? await fallbackEvidenceTool.executable.invoke(gateway.input, { toolRuntimeContext })
446
- : await fallbackEvidenceTool.executable.invoke(gateway.input);
447
- const memoryCandidates = compiledTool ? extractMemoryCandidatesFromToolOutput(compiledTool, toolResult) : [];
448
- const safeToolResult = await maybePersistLargeToolOutput({
449
- toolName: fallbackEvidenceTool.executable.name,
450
- output: toolResult,
451
- toolRuntimeContext: toolRuntimeContext,
452
- });
453
- executedToolResults.push({
454
- toolName: fallbackEvidenceTool.executable.name,
455
- output: safeToolResult,
456
- ...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
457
- });
458
- return {
459
- result: buildDeterministicFinalFromToolEvidence(executedToolResults),
460
- executedToolResults,
461
- };
462
- }
463
- }
464
- }
465
255
  if (requiresPlanEvidence(binding)
466
256
  && toolCalls.length > 0
467
257
  && toolCalls.every((toolCall) => isPlanToolName(toolCall.name))
@@ -253,26 +253,6 @@ function readLatestToolResultContent(input) {
253
253
  }
254
254
  return null;
255
255
  }
256
- function readLatestUserContent(input) {
257
- if (Array.isArray(input)) {
258
- for (let index = input.length - 1; index >= 0; index -= 1) {
259
- const message = input[index];
260
- if (mapMessageRole(message) !== "USER") {
261
- continue;
262
- }
263
- const content = readPromptContent(message).trim();
264
- if (content) {
265
- return content;
266
- }
267
- }
268
- return undefined;
269
- }
270
- if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
271
- return readLatestUserContent(input.messages);
272
- }
273
- const content = readPromptContent(input).trim();
274
- return content || undefined;
275
- }
276
256
  function readBoundToolName(tool) {
277
257
  return typeof tool === "object" && tool !== null && typeof tool.name === "string"
278
258
  ? tool.name.trim()
@@ -598,33 +578,10 @@ function isLowSignalPlanningLine(value) {
598
578
  || /^#+\s*/.test(normalized)
599
579
  || /^(?:ok|okay|sure|understood|got it|plan|todo|steps?)\.?$/.test(normalized));
600
580
  }
601
- function normalizeDomainToolName(name) {
602
- return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
603
- }
604
- function selectFallbackEvidenceToolName(tools, rawText = "") {
605
- const normalizedRawText = rawText.toLowerCase();
606
- const available = tools
607
- .map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
608
- .filter((name) => name && !isTodoPlanningToolName(name) && !isTodoPlanningToolName(`tool_call_${name}`));
609
- if (available.length === 0) {
610
- return null;
611
- }
612
- const mentioned = available.find((name) => normalizedRawText.includes(name.toLowerCase()));
613
- return mentioned ?? available[0] ?? null;
614
- }
615
- function buildToolAwareFallbackTodoContents(tools, rawText = "") {
616
- const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
617
- if (!evidenceToolName) {
618
- return [
619
- "Identify the concrete evidence tool required for this request",
620
- "Run the selected non-planning evidence tool and inspect its result",
621
- "Update TODO status from the observed evidence",
622
- "Return the final answer grounded in tool output",
623
- ];
624
- }
581
+ function buildFallbackTodoContents() {
625
582
  return [
626
- `Run ${evidenceToolName} for the requested evidence`,
627
- `Inspect the ${evidenceToolName} result and extract concrete findings`,
583
+ "Identify the concrete evidence tool required for this request",
584
+ "Run the selected non-planning evidence tool and inspect its result",
628
585
  "Update TODO status from the observed evidence",
629
586
  "Return the final answer grounded in tool output",
630
587
  ];
@@ -632,7 +589,7 @@ function buildToolAwareFallbackTodoContents(tools, rawText = "") {
632
589
  function isGenericFallbackTodoContent(value) {
633
590
  return /^(?:gather concrete evidence|inspect the most relevant runtime signals|analyze (?:the )?evidence|produce the final rca)/i.test(value.trim());
634
591
  }
635
- function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText) {
592
+ function buildFallbackPlanningToolCall(input, planningTools, rawText) {
636
593
  const toolName = planningTools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
637
594
  if (!toolName) {
638
595
  return null;
@@ -641,7 +598,7 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
641
598
  const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
642
599
  const fallbackItems = hasUsefulModelPlan
643
600
  ? modelPlannedItems
644
- : buildToolAwareFallbackTodoContents(allTools, rawText);
601
+ : buildFallbackTodoContents();
645
602
  const todos = fallbackItems.map((content, index) => ({
646
603
  content,
647
604
  status: index === 0 ? "in_progress" : "pending",
@@ -656,63 +613,6 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
656
613
  }],
657
614
  });
658
615
  }
659
- function buildFallbackEvidenceToolArgs(tool, latestUserInput) {
660
- const schema = normalizeModelFacingToolSchema(tool);
661
- if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
662
- return {};
663
- }
664
- const required = Array.isArray(schema.required)
665
- ? schema.required.filter((name) => typeof name === "string")
666
- : [];
667
- const values = {};
668
- for (const [key, value] of Object.entries(schema.properties)) {
669
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
670
- continue;
671
- }
672
- const property = value;
673
- if ("default" in property) {
674
- values[key] = property.default;
675
- continue;
676
- }
677
- if ("const" in property) {
678
- values[key] = property.const;
679
- continue;
680
- }
681
- if (required.includes(key) && Array.isArray(property.enum) && property.enum.length > 0) {
682
- values[key] = property.enum[0];
683
- continue;
684
- }
685
- if (latestUserInput
686
- && !values[key]
687
- && /(?:query|question|prompt|input|text)/iu.test(`${key} ${typeof property.description === "string" ? property.description : ""}`)) {
688
- values[key] = latestUserInput;
689
- }
690
- }
691
- return values;
692
- }
693
- function buildFallbackEvidenceToolCall(input, tools, rawText = "") {
694
- if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
695
- return null;
696
- }
697
- const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
698
- if (!evidenceToolName) {
699
- return null;
700
- }
701
- const boundTool = tools.find((tool) => normalizeDomainToolName(readBoundToolName(tool)) === evidenceToolName);
702
- const boundToolName = readBoundToolName(boundTool);
703
- if (!boundTool || !boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
704
- return null;
705
- }
706
- return new AIMessage({
707
- content: "",
708
- tool_calls: [{
709
- id: `fallback-evidence-${Math.random().toString(36).slice(2, 10)}`,
710
- name: boundToolName,
711
- args: buildFallbackEvidenceToolArgs(boundTool, readLatestUserContent(input)),
712
- type: "tool_call",
713
- }],
714
- });
715
- }
716
616
  function buildFallbackTodoCompletionToolCall(input, tools) {
717
617
  const prompt = stringifyNodeLlamaCppInput(input);
718
618
  if (/TODO completed:|\[x\]/i.test(prompt)) {
@@ -722,17 +622,10 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
722
622
  if (!planningToolName) {
723
623
  return null;
724
624
  }
725
- const evidenceToolName = selectFallbackEvidenceToolName(tools, prompt);
726
- if (!evidenceToolName) {
727
- return null;
728
- }
729
- const hasEvidenceResult = hasPriorToolResultForToolName(input, evidenceToolName)
730
- || hasPriorToolResultForToolName(input, `tool_call_${evidenceToolName}`)
731
- || hasPriorNonPlanningToolResult(input, tools);
732
- if (!hasEvidenceResult) {
625
+ if (!hasPriorNonPlanningToolResult(input, tools)) {
733
626
  return null;
734
627
  }
735
- const todos = buildToolAwareFallbackTodoContents(tools, prompt).map((content) => ({
628
+ const todos = buildFallbackTodoContents().map((content) => ({
736
629
  content,
737
630
  status: "completed",
738
631
  }));
@@ -848,7 +741,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
848
741
  const parsedToolCall = normalizeParsedToolCall(extractToolCallPayload(text));
849
742
  if (!parsedToolCall) {
850
743
  if (forcePlanningToolCall) {
851
- const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, boundTools, text);
744
+ const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, text);
852
745
  if (fallbackToolCall) {
853
746
  return fallbackToolCall;
854
747
  }
@@ -858,23 +751,12 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
858
751
  if (fallbackCompletionToolCall) {
859
752
  return fallbackCompletionToolCall;
860
753
  }
861
- const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
862
- if (fallbackToolCall) {
863
- return fallbackToolCall;
864
- }
865
754
  }
866
755
  return rawResult;
867
756
  }
868
757
  const effectiveParsedToolCall = forcePlanningToolCall
869
758
  ? normalizeInitialTodoPlanToolCall(parsedToolCall)
870
759
  : parsedToolCall;
871
- if (parsedToolCallCompletesTodoPlan(effectiveParsedToolCall)
872
- && !hasPriorNonPlanningToolResult(input, effectiveBoundTools)) {
873
- const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
874
- if (fallbackToolCall) {
875
- return fallbackToolCall;
876
- }
877
- }
878
760
  if (hasPriorToolResultForToolName(input, effectiveParsedToolCall.name) || hasAnyPriorToolResult(input)) {
879
761
  return rawResult;
880
762
  }
@@ -1,4 +1,5 @@
1
1
  import { salvageToolArgs } from "../../parsing/output-parsing.js";
2
+ import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
2
3
  import { isRecord } from "../../../utils/object.js";
3
4
  import { extractExplicitResourceReferences, hasExplicitResourceReference } from "../../harness/system/runtime-memory-policy.js";
4
5
  function isObject(value) {
@@ -70,6 +71,44 @@ function mapSingleRemainingScalarArg(args, expectedKey) {
70
71
  [expectedKey]: value,
71
72
  };
72
73
  }
74
+ function readSchemaDescription(schemaPart) {
75
+ if (!isObject(schemaPart)) {
76
+ return "";
77
+ }
78
+ const direct = schemaPart.description;
79
+ if (typeof direct === "string") {
80
+ return direct;
81
+ }
82
+ const def = schemaPart._def;
83
+ if (typeof def?.description === "string") {
84
+ return def.description;
85
+ }
86
+ return readSchemaDescription(def?.innerType);
87
+ }
88
+ function fillLatestUserInputForQueryLikeFields(args, shape, latestUserInput) {
89
+ const userInput = typeof latestUserInput === "string" ? latestUserInput.trim() : "";
90
+ if (!userInput) {
91
+ return args;
92
+ }
93
+ let next = args;
94
+ for (const [key, schemaPart] of Object.entries(shape)) {
95
+ if (key in next) {
96
+ continue;
97
+ }
98
+ const normalizedKey = key.trim().toLowerCase();
99
+ const description = readSchemaDescription(schemaPart);
100
+ const keyIsQueryLike = ["query", "question", "prompt", "input", "text"].includes(normalizedKey);
101
+ const descriptionIsQueryLike = /\b(?:query|question|prompt|input|text)\b/iu.test(description);
102
+ if (!keyIsQueryLike && !descriptionIsQueryLike) {
103
+ continue;
104
+ }
105
+ next = {
106
+ ...next,
107
+ [key]: userInput,
108
+ };
109
+ }
110
+ return next;
111
+ }
73
112
  export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options = {}) {
74
113
  const schemaDef = isObject(schema) ? schema._def : undefined;
75
114
  const zodShape = schemaDef
@@ -88,7 +127,7 @@ export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options =
88
127
  }
89
128
  const keys = Object.keys(shape);
90
129
  if (keys.length !== 1) {
91
- return args;
130
+ return fillLatestUserInputForQueryLikeFields(args, shape, options.latestUserInput);
92
131
  }
93
132
  const [expectedKey] = keys;
94
133
  if (expectedKey in args) {
@@ -126,7 +165,7 @@ export function extractToolCallsFromResult(result) {
126
165
  : Array.isArray(messageKwargs?.tool_calls)
127
166
  ? messageKwargs.tool_calls
128
167
  : [];
129
- return rawToolCalls
168
+ const extracted = rawToolCalls
130
169
  .map((toolCall) => {
131
170
  if (!isObject(toolCall)) {
132
171
  return null;
@@ -149,4 +188,33 @@ export function extractToolCallsFromResult(result) {
149
188
  return { id, name, args: rawArgs, rawArgsInput };
150
189
  })
151
190
  .filter((item) => item !== null);
191
+ if (extracted.length > 0) {
192
+ return extracted;
193
+ }
194
+ const directRole = typeof lastMessage.role === "string"
195
+ ? lastMessage.role.trim().toLowerCase()
196
+ : undefined;
197
+ const messageType = typeof lastMessage._getType === "function"
198
+ ? String(lastMessage._getType())
199
+ : undefined;
200
+ const constructorType = Array.isArray(lastMessage.id)
201
+ ? lastMessage.id.at(-1)
202
+ : undefined;
203
+ if (directRole === "tool" || messageType === "tool" || constructorType === "ToolMessage") {
204
+ return [];
205
+ }
206
+ const content = typeof lastMessage.content === "string"
207
+ ? lastMessage.content
208
+ : typeof messageKwargs?.content === "string"
209
+ ? messageKwargs.content
210
+ : "";
211
+ if (!content.trim()) {
212
+ return [];
213
+ }
214
+ return salvageJsonToolCalls(content).map((toolCall, index) => ({
215
+ id: `salvaged-json-${index + 1}`,
216
+ name: toolCall.name,
217
+ args: toolCall.args,
218
+ rawArgsInput: content,
219
+ }));
152
220
  }
@@ -420,12 +420,17 @@ function normalizeWriteTodosArgs(args) {
420
420
  ? record.content
421
421
  : typeof record.description === "string" && record.description.trim().length > 0
422
422
  ? record.description
423
- : `Step ${index + 1}`;
423
+ : typeof record.title === "string" && record.title.trim().length > 0
424
+ ? record.title
425
+ : typeof record.name === "string" && record.name.trim().length > 0
426
+ ? record.name
427
+ : typeof record.text === "string" && record.text.trim().length > 0
428
+ ? record.text
429
+ : `Step ${index + 1}`;
424
430
  const normalized = {};
425
431
  if (content !== undefined)
426
432
  normalized.content = content;
427
- if (typeof record.status === "string")
428
- normalized.status = record.status;
433
+ normalized.status = typeof record.status === "string" && record.status.trim().length > 0 ? record.status : "pending";
429
434
  return Object.keys(normalized).length > 0 ? normalized : todo;
430
435
  }),
431
436
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.403",
3
+ "version": "0.0.405",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",