@botbotgo/agent-harness 0.0.461 → 0.0.463

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.
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.461";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.463";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.461";
1
+ export const AGENT_HARNESS_VERSION = "0.0.463";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -25,6 +25,8 @@ const RUN_EVIDENCE_AFTER_PREMATURE_PLAN_CLOSE_INSTRUCTION = [
25
25
  "The required todo board was closed before any non-TODO evidence tool returned.",
26
26
  "Do not call write_todos again yet.",
27
27
  "Your next action must be exactly one non-TODO evidence tool call selected from the available tool descriptions and schemas.",
28
+ "If the current request or todo board explicitly names one available non-TODO tool, call that named tool.",
29
+ "Do not substitute a neighboring, broader, narrower, or similarly named tool when an exact available tool name is present.",
28
30
  "After that evidence tool returns, update the todo board and then provide the final answer required by the agent response format.",
29
31
  ].join("\n");
30
32
  function readPrimaryToolName(tool) {
@@ -43,11 +45,11 @@ function buildRunEvidenceAfterPlanInstruction(primaryTools) {
43
45
  `Available non-planning tool names: ${toolNames.join(", ")}.`,
44
46
  ].join("\n");
45
47
  }
46
- function resolveConfiguredPlanEvidenceTool(primaryTools) {
47
- const toolName = primaryTools
48
+ function resolveSingleConfiguredPlanEvidenceTool(primaryTools) {
49
+ const toolNames = primaryTools
48
50
  .map(readPrimaryToolName)
49
- .find((name) => name.length > 0 && !isPlanToolName(name));
50
- return toolName ? [{ name: toolName, args: {}, id: "stream-configured-plan-evidence-tool-1" }] : [];
51
+ .filter((name) => name.length > 0 && !isPlanToolName(name));
52
+ return toolNames.length === 1 ? [{ name: toolNames[0], args: {}, id: "stream-single-plan-evidence-tool-1" }] : [];
51
53
  }
52
54
  const INITIAL_REQUIRED_PLAN_INSTRUCTION = [
53
55
  "This agent has a required visible planning contract.",
@@ -704,7 +706,7 @@ export async function* streamRuntimeExecution(options) {
704
706
  && hadPriorPlanToolResult
705
707
  && projectedChunks.some((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName));
706
708
  if (repeatedPlanToolResultBeforeEvidence) {
707
- earlyStreamExternalPlanEvidenceTools = resolveConfiguredPlanEvidenceTool(options.primaryTools);
709
+ earlyStreamExternalPlanEvidenceTools = resolveSingleConfiguredPlanEvidenceTool(options.primaryTools);
708
710
  earlyStreamRecoveryInstruction = buildRunEvidenceAfterPlanInstruction(options.primaryTools);
709
711
  earlyStreamRecoverySuppressInitialPlan = true;
710
712
  break;
@@ -750,7 +752,7 @@ export async function* streamRuntimeExecution(options) {
750
752
  && (hadPriorPlanToolResult
751
753
  || projectedChunks.some((chunk) => isCompletedPlanToolResultChunk(chunk)))
752
754
  && !sawSuccessfulNonTodoToolResult) {
753
- earlyStreamExternalPlanEvidenceTools = resolveConfiguredPlanEvidenceTool(options.primaryTools);
755
+ earlyStreamExternalPlanEvidenceTools = resolveSingleConfiguredPlanEvidenceTool(options.primaryTools);
754
756
  earlyStreamRecoveryInstruction = buildRunEvidenceAfterPlanInstruction(options.primaryTools);
755
757
  earlyStreamRecoverySuppressInitialPlan = true;
756
758
  break;
@@ -13,15 +13,21 @@ const TOOL_FOLLOW_UP_INSTRUCTION = "One or more tool results are already availab
13
13
  const DEFAULT_MAX_TOOL_ITERATIONS = 10_000;
14
14
  const MAX_REPEATED_RECOVERY_WITHOUT_PROGRESS = 2;
15
15
  const MAX_REPEATED_PLAN_ONLY_AFTER_PLAN = 2;
16
- function prioritizeBootstrapEvidenceTools(primaryTools) {
16
+ const REQUIRED_PLAN_CONTRACT_MARKER = "This agent has a required visible planning contract.";
17
+ const INITIAL_WRITE_TODOS_MARKER = "Your first action for this request must be write_todos";
18
+ function resolveSingleBootstrapEvidenceTool(primaryTools) {
17
19
  const evidenceTools = primaryTools
18
20
  .map((tool) => typeof tool.name === "string" ? tool.name.trim() : "")
19
21
  .filter((name) => name.length > 0 && !isPlanToolName(name));
20
- return evidenceTools.slice(0, 4);
22
+ return evidenceTools.length === 1 ? evidenceTools[0] : undefined;
21
23
  }
22
24
  function createBootstrapTodoPlan(primaryTools) {
23
- const evidenceTool = prioritizeBootstrapEvidenceTools(primaryTools)[0];
24
- if (!evidenceTool) {
25
+ const evidenceTool = resolveSingleBootstrapEvidenceTool(primaryTools);
26
+ const evidenceToolCount = primaryTools
27
+ .map((tool) => typeof tool.name === "string" ? tool.name.trim() : "")
28
+ .filter((name) => name.length > 0 && !isPlanToolName(name))
29
+ .length;
30
+ if (evidenceToolCount === 0) {
25
31
  return [
26
32
  {
27
33
  content: "Establish the required visible plan for this request",
@@ -35,7 +41,9 @@ function createBootstrapTodoPlan(primaryTools) {
35
41
  }
36
42
  return [
37
43
  {
38
- content: `Run the configured non-planning evidence tool: ${evidenceTool}`,
44
+ content: evidenceTool
45
+ ? `Run the only configured non-planning evidence tool: ${evidenceTool}`
46
+ : "Select and run the appropriate non-planning evidence tool from the declared tool surface",
39
47
  status: "in_progress",
40
48
  },
41
49
  {
@@ -76,6 +84,15 @@ function buildExternalPlanEvidenceToolResult(tools) {
76
84
  }],
77
85
  };
78
86
  }
87
+ function stripSatisfiedInitialPlanInstruction(messages) {
88
+ return messages.filter((message) => {
89
+ const typed = typeof message === "object" && message !== null ? message : {};
90
+ if (typeof typed.content !== "string") {
91
+ return true;
92
+ }
93
+ return !(typed.content.includes(REQUIRED_PLAN_CONTRACT_MARKER) && typed.content.includes(INITIAL_WRITE_TODOS_MARKER));
94
+ });
95
+ }
79
96
  function readPlanStateSummary(output) {
80
97
  if (typeof output !== "object" || output === null) {
81
98
  return null;
@@ -217,7 +234,7 @@ function debugLocalToolReplay(input) {
217
234
  }
218
235
  console.error(JSON.stringify({
219
236
  type: "local-tool-replay",
220
- toolCallNames: input.toolCalls.map((toolCall) => toolCall.name),
237
+ toolCalls: input.toolCalls.map((toolCall) => ({ name: toolCall.name, args: toolCall.args })),
221
238
  resultMessages: summarizeResultMessages(input.result),
222
239
  executableToolNames: input.executableToolNames,
223
240
  builtinToolNames: input.builtinToolNames,
@@ -534,7 +551,9 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
534
551
  executedToolResults,
535
552
  };
536
553
  }
537
- currentMessages = nextMessages;
554
+ currentMessages = hasPlanStateEvidence(executedToolResults, externalPlanEvidence)
555
+ ? stripSatisfiedInitialPlanInstruction(nextMessages)
556
+ : nextMessages;
538
557
  activeRequest = {
539
558
  ...activeRequest,
540
559
  messages: currentMessages,
@@ -6,7 +6,7 @@ import { ChatOpenAI } from "@langchain/openai";
6
6
  import { AIMessage } from "langchain";
7
7
  import { initChatModel } from "langchain";
8
8
  import { salvageToolArgs, tryParseJson } from "../../parsing/output-parsing.js";
9
- import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
9
+ import { normalizeKnownToolArgs, salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
10
10
  import { normalizeModelFacingToolSchema } from "../tool/resolved-tool.js";
11
11
  import { normalizeOpenAICompatibleInit } from "../compat/openai-compatible.js";
12
12
  import { recordPromptedJsonToolCall } from "./prompted-json-tool-call-capture.js";
@@ -640,7 +640,7 @@ function normalizeParsedToolCall(payload) {
640
640
  const args = Array.isArray(argsCandidate)
641
641
  ? { args: argsCandidate }
642
642
  : salvageToolArgs(argsCandidate) ?? {};
643
- return { name, args };
643
+ return { name, args: normalizeKnownToolArgs(name, args) };
644
644
  }
645
645
  function buildFallbackTodoContents() {
646
646
  return [
@@ -770,6 +770,8 @@ function withPromptedJsonToolPrompt(input, tools, options = {}) {
770
770
  ? [
771
771
  "Required evidence tool call:",
772
772
  "A todo board already exists. Your next action must be exactly one non-planning tool call chosen from the available tool descriptions and schemas.",
773
+ "If the current request or todo board explicitly names one available non-planning tool, call that named tool.",
774
+ "Do not substitute a neighboring, broader, narrower, or similarly named tool when an exact available tool name is present.",
773
775
  "Do not call write_todos or read_todos now.",
774
776
  "Do not write prose, markdown, analysis, or a plain-text plan.",
775
777
  ].join("\n")
@@ -1,5 +1,5 @@
1
1
  import { salvageToolArgs } from "../../parsing/output-parsing.js";
2
- import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
2
+ import { normalizeKnownToolArgs, salvageJsonToolCalls, salvageResultLabeledToolCall } from "../../parsing/output-tool-args.js";
3
3
  import { isRecord } from "../../../utils/object.js";
4
4
  import { extractExplicitResourceReferences, hasExplicitResourceReference } from "../../harness/system/runtime-memory-policy.js";
5
5
  import { readCapturedPromptedJsonToolCalls } from "../model/prompted-json-tool-call-capture.js";
@@ -175,6 +175,29 @@ function mapDelimitedListLikeArgs(args) {
175
175
  }
176
176
  return next;
177
177
  }
178
+ function dropDelimitedScalarPathArgs(args, shape) {
179
+ let next = args;
180
+ for (const [key, schemaPart] of Object.entries(shape)) {
181
+ const value = next[key];
182
+ if (typeof value !== "string") {
183
+ continue;
184
+ }
185
+ const normalizedKey = key.trim().toLowerCase();
186
+ if (!/(?:^path$|path$|^filepath$|^targetpath$)/u.test(normalizedKey)) {
187
+ continue;
188
+ }
189
+ if (schemaPartExpectsArray(schemaPart)) {
190
+ continue;
191
+ }
192
+ const raw = value.trim();
193
+ if (!/[,;\n]/u.test(raw)) {
194
+ continue;
195
+ }
196
+ const { [key]: _dropped, ...rest } = next;
197
+ next = rest;
198
+ }
199
+ return next;
200
+ }
178
201
  export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options = {}) {
179
202
  const schemaDef = isObject(schema) ? schema._def : undefined;
180
203
  const zodShape = schemaDef
@@ -191,7 +214,7 @@ export function normalizeToolArgsForSchema(args, schema, rawArgsInput, options =
191
214
  if (!shape || !isRecord(shape)) {
192
215
  return mapDelimitedListLikeArgs(args);
193
216
  }
194
- const aliasMappedArgs = mapStringArrayFields(mapCommonArgumentAliases(args, shape), shape);
217
+ const aliasMappedArgs = dropDelimitedScalarPathArgs(mapStringArrayFields(mapCommonArgumentAliases(args, shape), shape), shape);
195
218
  const keys = Object.keys(shape);
196
219
  if (keys.length !== 1) {
197
220
  return fillLatestUserInputForQueryLikeFields(aliasMappedArgs, shape, options.latestUserInput);
@@ -270,7 +293,7 @@ export function extractToolCallsFromResult(result) {
270
293
  if (id && answeredToolCallIds.has(id)) {
271
294
  return null;
272
295
  }
273
- return { id, name, args: rawArgs, rawArgsInput };
296
+ return { id, name, args: normalizeKnownToolArgs(name, rawArgs), rawArgsInput };
274
297
  })
275
298
  .filter((item) => item !== null);
276
299
  if (extracted.length > 0) {
@@ -296,6 +319,15 @@ export function extractToolCallsFromResult(result) {
296
319
  if (!content.trim()) {
297
320
  continue;
298
321
  }
322
+ const resultLabeledToolCall = salvageResultLabeledToolCall(content);
323
+ if (resultLabeledToolCall) {
324
+ return [{
325
+ id: "salvaged-result-label-1",
326
+ name: resultLabeledToolCall.name,
327
+ args: resultLabeledToolCall.args,
328
+ rawArgsInput: content,
329
+ }];
330
+ }
299
331
  const salvaged = salvageJsonToolCalls(content);
300
332
  if (salvaged.length > 0) {
301
333
  return salvaged.map((toolCall, salvageIndex) => ({
@@ -7,7 +7,6 @@ import { salvageJsonToolCalls } from "./parsing/output-tool-args.js";
7
7
  import { extractMessageText } from "../utils/message-content.js";
8
8
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityCatalog, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
9
9
  import { resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, } from "./adapter/compat/deepagent-compat.js";
10
- import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
11
10
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
12
11
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
13
12
  import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
@@ -25,16 +24,6 @@ export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter
25
24
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
26
25
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
27
26
  import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingSubagents, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
28
- function hasDelegatedExecutionToolEvidence(result) {
29
- const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
30
- ? result.metadata.executedToolResults
31
- : [];
32
- return executedToolResults.some((toolResult) => (toolResult.isError !== true
33
- && !isPlanToolName(toolResult.toolName)));
34
- }
35
- function hasRequiredDelegatedExecutionToolEvidence(result) {
36
- return hasDelegatedExecutionToolEvidence(result);
37
- }
38
27
  function buildDelegatedPlanEvidenceBlocker(agentId) {
39
28
  return JSON.stringify({
40
29
  status: "blocked",
@@ -50,30 +39,6 @@ function buildDelegatedPlanEvidenceBlocker(agentId) {
50
39
  report: `routing delegated to ${agentId}; todoTrace ${agentId}: TODO evidence missing; stepResults blocked; summary missing planning evidence; findings require retry; blockers missing TODO planning evidence; nextActions inspect delegated model/tool behavior; report task delegated to ${agentId}.`,
51
40
  });
52
41
  }
53
- function buildDelegatedExecutionEvidenceBlocker(agentId, expectedToolNames = []) {
54
- const expectedTools = expectedToolNames.length > 0 ? expectedToolNames.join(", ") : "configured non-planning tools";
55
- return JSON.stringify({
56
- status: "blocked",
57
- routing: [`delegated agent ${agentId}`],
58
- plan: ["delegate to specialist", "require non-planning tool evidence", "return blocker when evidence is absent"],
59
- execution: [
60
- `task delegated to ${agentId}`,
61
- `expected evidence tools: ${expectedTools}`,
62
- `delegated agent ${agentId} did not return any non-planning tool evidence after retry`,
63
- ],
64
- todoTrace: [`${agentId}: TODO evidence observed; delegated planning board did not produce completed non-planning evidence.`],
65
- stepResults: ["delegated execution evidence was not observed"],
66
- summary: [`Delegated agent ${agentId} did not return any non-planning tool evidence after retry.`],
67
- findings: [
68
- `Expected evidence tools from configuration: ${expectedTools}.`,
69
- "The TODO board alone is not execution evidence.",
70
- "The framework cannot mark the delegated task complete without a non-planning tool result or an explicit blocker from that tool path.",
71
- ],
72
- blockers: ["missing delegated non-planning tool evidence"],
73
- nextActions: ["Retry the request or inspect the delegated agent's model/tool-call behavior."],
74
- report: `routing delegated to ${agentId}; todoTrace ${agentId}: TODO evidence observed but non-planning evidence missing; stepResults blocked; summary missing non-planning tool evidence; findings expected evidence tools ${expectedTools}; blockers missing execution evidence; nextActions inspect delegated model/tool behavior; report task delegated to ${agentId}.`,
75
- });
76
- }
77
42
  function normalizePlanToolName(toolName) {
78
43
  return typeof toolName === "string" ? toolName.trim().toLowerCase().replace(/[\s-]+/gu, "_") : "";
79
44
  }
@@ -282,6 +247,32 @@ function hasDelegatedPlanEvidence(result) {
282
247
  return Array.isArray(toolResults)
283
248
  && toolResults.some((item) => isPlanToolName(item.toolName));
284
249
  }
250
+ function hasIncompleteDelegatedTodos(value) {
251
+ if (Array.isArray(value)) {
252
+ return value.some((item) => hasIncompleteDelegatedTodos(item));
253
+ }
254
+ if (typeof value !== "object" || value === null) {
255
+ return false;
256
+ }
257
+ const record = value;
258
+ const status = typeof record.status === "string" ? record.status.trim().toLowerCase() : "";
259
+ if (status === "pending" || status === "in_progress") {
260
+ return true;
261
+ }
262
+ return hasIncompleteDelegatedTodos(record.todos)
263
+ || hasIncompleteDelegatedTodos(record.update)
264
+ || hasIncompleteDelegatedTodos(record.stateSnapshot)
265
+ || hasIncompleteDelegatedTodos(record.metadata);
266
+ }
267
+ function hasIncompleteDelegatedPlanState(result) {
268
+ const toolResults = result?.metadata?.executedToolResults;
269
+ return Array.isArray(toolResults)
270
+ && toolResults.some((item) => isPlanToolName(item.toolName) && hasIncompleteDelegatedTodos(item.output));
271
+ }
272
+ function needsDelegatedPlanRecovery(binding, result) {
273
+ return binding?.harnessRuntime.executionContract?.requiresPlan === true
274
+ && (!hasDelegatedPlanEvidence(result) || hasIncompleteDelegatedPlanState(result));
275
+ }
285
276
  function readUpstreamToolEvidence(event) {
286
277
  if (typeof event !== "object" || event === null) {
287
278
  return null;
@@ -342,6 +333,12 @@ const DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION = [
342
333
  "Before any other tool call or final answer, call write_todos with concrete task steps and statuses.",
343
334
  "Then continue the task to completion, update TODO statuses after evidence steps, and close every TODO as completed or failed before the final answer.",
344
335
  ].join("\n");
336
+ const DELEGATED_PLAN_EVIDENCE_FINAL_RETRY_INSTRUCTION = [
337
+ "The delegated task still has no visible TODO planning evidence.",
338
+ "Use the actual write_todos tool interface now. Do not print JSON, markdown, or a tool-call transcript as text.",
339
+ "The next runtime event must be the write_todos tool call result, not an assistant message describing the call.",
340
+ "After write_todos succeeds, continue the delegated task and close every TODO as completed or failed.",
341
+ ].join("\n");
345
342
  function looksLikeRawCommandTranscript(value) {
346
343
  const normalized = value.trim();
347
344
  return /^(?:stdout|stderr)\s*:/iu.test(normalized)
@@ -868,6 +865,28 @@ export class AgentRuntimeAdapter {
868
865
  const inlineSubagents = input.resolvedSubagents.filter((subagent) => !("graphId" in subagent));
869
866
  const asyncSubagents = input.resolvedSubagents.filter((subagent) => "graphId" in subagent);
870
867
  const subagents = inlineSubagents;
868
+ const subagentDefaultMiddleware = [
869
+ ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
870
+ ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
871
+ createSummarizationMiddleware({
872
+ model: input.resolvedModel,
873
+ backend,
874
+ }),
875
+ createPatchToolCallsMiddleware(),
876
+ ];
877
+ const generalPurposeMiddleware = [
878
+ ...subagentDefaultMiddleware,
879
+ ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
880
+ backend,
881
+ sources: resolveDeepAgentSkillSourceRootPaths({
882
+ workspaceRoot: binding.harnessRuntime.workspaceRoot,
883
+ runtimeRoot: binding.harnessRuntime.runtimeRoot,
884
+ ownerId: binding.agent.id,
885
+ skillPaths: input.resolvedSkills,
886
+ }) ?? input.resolvedSkills,
887
+ })] : []),
888
+ ];
889
+ const hasGeneralPurposeOverride = subagents.some((subagent) => subagent.name === "general-purpose");
871
890
  const middleware = [
872
891
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
873
892
  ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
@@ -880,15 +899,15 @@ export class AgentRuntimeAdapter {
880
899
  }) ?? input.resolvedSkills,
881
900
  })] : []),
882
901
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
883
- ...(subagents.length > 0
884
- ? [createSubAgentMiddleware({
885
- defaultModel: input.resolvedModel,
886
- defaultTools: input.resolvedTools,
887
- defaultInterruptOn: input.resolvedInterruptOn,
888
- subagents: subagents,
889
- generalPurposeAgent: false,
890
- })]
891
- : []),
902
+ createSubAgentMiddleware({
903
+ defaultModel: input.resolvedModel,
904
+ defaultTools: input.resolvedTools,
905
+ defaultMiddleware: subagentDefaultMiddleware,
906
+ generalPurposeMiddleware: generalPurposeMiddleware,
907
+ defaultInterruptOn: input.resolvedInterruptOn,
908
+ subagents: subagents,
909
+ generalPurposeAgent: !hasGeneralPurposeOverride,
910
+ }),
892
911
  createSummarizationMiddleware({
893
912
  model: input.resolvedModel,
894
913
  backend,
@@ -1224,8 +1243,7 @@ export class AgentRuntimeAdapter {
1224
1243
  };
1225
1244
  }
1226
1245
  }
1227
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1228
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1246
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1229
1247
  try {
1230
1248
  delegatedResult = await runDelegatedRequest([requestText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry");
1231
1249
  }
@@ -1243,8 +1261,7 @@ export class AgentRuntimeAdapter {
1243
1261
  };
1244
1262
  }
1245
1263
  }
1246
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1247
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1264
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1248
1265
  const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1249
1266
  return {
1250
1267
  toolOutput: output,
@@ -1635,12 +1652,16 @@ export class AgentRuntimeAdapter {
1635
1652
  agentId: selectedBinding?.agent.id ?? planned.subagentType,
1636
1653
  };
1637
1654
  let delegatedResult = yield* runPlannedDelegation(planned.subagentType, delegatedText);
1638
- if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
1655
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1639
1656
  const previousDelegatedResult = delegatedResult;
1640
1657
  delegatedResult = mergeDelegatedResultToolEvidence(yield* runPlannedDelegation(planned.subagentType, [delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
1641
1658
  }
1642
- if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
1643
- const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1659
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1660
+ const previousDelegatedResult = delegatedResult;
1661
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runPlannedDelegation(planned.subagentType, [delegatedText, DELEGATED_PLAN_EVIDENCE_FINAL_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-final-retry"), previousDelegatedResult);
1662
+ }
1663
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1664
+ const output = buildDelegatedPlanEvidenceBlocker(selectedBinding?.agent.id ?? planned.subagentType);
1644
1665
  delegatedResult = {
1645
1666
  ...delegatedResult,
1646
1667
  state: "failed",
@@ -1832,21 +1853,15 @@ export class AgentRuntimeAdapter {
1832
1853
  originalRequest: requestText,
1833
1854
  });
1834
1855
  let delegatedResult = yield* runDelegatedStreamAttempt(delegatedText);
1835
- const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(selectedBinding).length > 0;
1836
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1837
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1856
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1838
1857
  const previousDelegatedResult = delegatedResult;
1839
1858
  delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
1840
1859
  }
1841
- if (targetRequiresExecutionToolEvidence && !hasRequiredDelegatedExecutionToolEvidence(delegatedResult)) {
1860
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1842
1861
  const previousDelegatedResult = delegatedResult;
1843
- delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([
1844
- delegatedText,
1845
- EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION,
1846
- ].filter(Boolean).join("\n\n"), ":tool-evidence-retry"), previousDelegatedResult);
1862
+ delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, DELEGATED_PLAN_EVIDENCE_FINAL_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-final-retry"), previousDelegatedResult);
1847
1863
  }
1848
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1849
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1864
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1850
1865
  const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1851
1866
  delegatedResult = {
1852
1867
  ...delegatedResult,
@@ -1855,15 +1870,6 @@ export class AgentRuntimeAdapter {
1855
1870
  finalMessageText: output,
1856
1871
  };
1857
1872
  }
1858
- if (targetRequiresExecutionToolEvidence && !hasRequiredDelegatedExecutionToolEvidence(delegatedResult)) {
1859
- const output = buildDelegatedExecutionEvidenceBlocker(selectedBinding.agent.id, getBindingPrimaryTools(selectedBinding).map((tool) => tool.name));
1860
- delegatedResult = {
1861
- ...delegatedResult,
1862
- state: "failed",
1863
- output,
1864
- finalMessageText: output,
1865
- };
1866
- }
1867
1873
  const delegatedToolResults = Array.isArray(delegatedResult.metadata?.executedToolResults)
1868
1874
  ? delegatedResult.metadata.executedToolResults
1869
1875
  : [];
@@ -7,6 +7,10 @@ export declare function salvageLabeledToolCall(value: unknown): {
7
7
  name: string;
8
8
  args: Record<string, unknown>;
9
9
  } | null;
10
+ export declare function salvageResultLabeledToolCall(value: unknown): {
11
+ name: string;
12
+ args: Record<string, unknown>;
13
+ } | null;
10
14
  export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
11
15
  export declare function salvageJsonToolCalls(value: unknown): Array<{
12
16
  name: string;
@@ -179,6 +179,22 @@ export function salvageLabeledToolCall(value) {
179
179
  }
180
180
  return null;
181
181
  }
182
+ export function salvageResultLabeledToolCall(value) {
183
+ if (typeof value !== "string") {
184
+ return null;
185
+ }
186
+ const lines = value
187
+ .split("\n")
188
+ .map((line) => line.trim())
189
+ .filter(Boolean);
190
+ const label = lines[0]?.replace(/[*`#]/gu, "").trim() ?? "";
191
+ const match = /^([A-Za-z_][A-Za-z0-9_]*)\s+result\b/iu.exec(label);
192
+ if (!match || !isToolName(match[1])) {
193
+ return null;
194
+ }
195
+ const args = salvageToolArgs(lines.slice(1).join("\n")) ?? {};
196
+ return { name: match[1], args: normalizeKnownToolArgs(match[1], args) };
197
+ }
182
198
  function extractBalancedJsonValue(value, openChar, closeChar) {
183
199
  const start = value.indexOf(openChar);
184
200
  if (start < 0)
@@ -514,12 +530,26 @@ function normalizeWriteTodosArgs(args) {
514
530
  if (Array.isArray(args.items) && !Array.isArray(args.todos)) {
515
531
  return normalizeWriteTodosArgs({ ...args, todos: args.items });
516
532
  }
533
+ if (Array.isArray(args.tasks) && !Array.isArray(args.todos)) {
534
+ return normalizeWriteTodosArgs({ ...args, todos: args.tasks });
535
+ }
536
+ if (Array.isArray(args.todo) && !Array.isArray(args.todos)) {
537
+ return normalizeWriteTodosArgs({ ...args, todos: args.todo });
538
+ }
517
539
  if (!Array.isArray(args.todos)) {
518
540
  return args;
519
541
  }
542
+ const { items: _items, tasks: _tasks, todo: _todo, ...rest } = args;
520
543
  return {
521
- ...args,
544
+ ...rest,
522
545
  todos: args.todos.map((todo, index) => {
546
+ if (typeof todo === "string") {
547
+ const content = todo.trim();
548
+ return {
549
+ content: content.length > 0 ? content : `Step ${index + 1}`,
550
+ status: index === 0 ? "in_progress" : "pending",
551
+ };
552
+ }
523
553
  if (typeof todo !== "object" || !todo || Array.isArray(todo)) {
524
554
  return todo;
525
555
  }
@@ -534,7 +564,13 @@ function normalizeWriteTodosArgs(args) {
534
564
  ? record.name
535
565
  : typeof record.text === "string" && record.text.trim().length > 0
536
566
  ? record.text
537
- : `Step ${index + 1}`;
567
+ : typeof record.task === "string" && record.task.trim().length > 0
568
+ ? record.task
569
+ : typeof record.action === "string" && record.action.trim().length > 0
570
+ ? record.action
571
+ : typeof record.step === "string" && record.step.trim().length > 0
572
+ ? record.step
573
+ : `Step ${index + 1}`;
538
574
  const normalized = {};
539
575
  if (content !== undefined)
540
576
  normalized.content = content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.461",
3
+ "version": "0.0.463",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",