@botbotgo/agent-harness 0.0.462 → 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.462";
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.462";
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) => ({
@@ -247,6 +247,32 @@ function hasDelegatedPlanEvidence(result) {
247
247
  return Array.isArray(toolResults)
248
248
  && toolResults.some((item) => isPlanToolName(item.toolName));
249
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
+ }
250
276
  function readUpstreamToolEvidence(event) {
251
277
  if (typeof event !== "object" || event === null) {
252
278
  return null;
@@ -839,6 +865,28 @@ export class AgentRuntimeAdapter {
839
865
  const inlineSubagents = input.resolvedSubagents.filter((subagent) => !("graphId" in subagent));
840
866
  const asyncSubagents = input.resolvedSubagents.filter((subagent) => "graphId" in subagent);
841
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");
842
890
  const middleware = [
843
891
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
844
892
  ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
@@ -851,15 +899,15 @@ export class AgentRuntimeAdapter {
851
899
  }) ?? input.resolvedSkills,
852
900
  })] : []),
853
901
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
854
- ...(subagents.length > 0
855
- ? [createSubAgentMiddleware({
856
- defaultModel: input.resolvedModel,
857
- defaultTools: input.resolvedTools,
858
- defaultInterruptOn: input.resolvedInterruptOn,
859
- subagents: subagents,
860
- generalPurposeAgent: false,
861
- })]
862
- : []),
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
+ }),
863
911
  createSummarizationMiddleware({
864
912
  model: input.resolvedModel,
865
913
  backend,
@@ -1195,8 +1243,7 @@ export class AgentRuntimeAdapter {
1195
1243
  };
1196
1244
  }
1197
1245
  }
1198
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1199
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1246
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1200
1247
  try {
1201
1248
  delegatedResult = await runDelegatedRequest([requestText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry");
1202
1249
  }
@@ -1214,8 +1261,7 @@ export class AgentRuntimeAdapter {
1214
1261
  };
1215
1262
  }
1216
1263
  }
1217
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1218
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1264
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1219
1265
  const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1220
1266
  return {
1221
1267
  toolOutput: output,
@@ -1606,12 +1652,16 @@ export class AgentRuntimeAdapter {
1606
1652
  agentId: selectedBinding?.agent.id ?? planned.subagentType,
1607
1653
  };
1608
1654
  let delegatedResult = yield* runPlannedDelegation(planned.subagentType, delegatedText);
1609
- if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
1655
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1610
1656
  const previousDelegatedResult = delegatedResult;
1611
1657
  delegatedResult = mergeDelegatedResultToolEvidence(yield* runPlannedDelegation(planned.subagentType, [delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
1612
1658
  }
1613
- if (selectedBinding?.harnessRuntime.executionContract?.requiresPlan === true && !hasDelegatedPlanEvidence(delegatedResult)) {
1614
- 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);
1615
1665
  delegatedResult = {
1616
1666
  ...delegatedResult,
1617
1667
  state: "failed",
@@ -1803,18 +1853,15 @@ export class AgentRuntimeAdapter {
1803
1853
  originalRequest: requestText,
1804
1854
  });
1805
1855
  let delegatedResult = yield* runDelegatedStreamAttempt(delegatedText);
1806
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1807
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1856
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1808
1857
  const previousDelegatedResult = delegatedResult;
1809
1858
  delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, DELEGATED_PLAN_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-retry"), previousDelegatedResult);
1810
1859
  }
1811
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1812
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1860
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1813
1861
  const previousDelegatedResult = delegatedResult;
1814
1862
  delegatedResult = mergeDelegatedResultToolEvidence(yield* runDelegatedStreamAttempt([delegatedText, DELEGATED_PLAN_EVIDENCE_FINAL_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":plan-evidence-final-retry"), previousDelegatedResult);
1815
1863
  }
1816
- if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
1817
- && !hasDelegatedPlanEvidence(delegatedResult)) {
1864
+ if (needsDelegatedPlanRecovery(selectedBinding, delegatedResult)) {
1818
1865
  const output = buildDelegatedPlanEvidenceBlocker(selectedBinding.agent.id);
1819
1866
  delegatedResult = {
1820
1867
  ...delegatedResult,
@@ -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.462",
3
+ "version": "0.0.463",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",