@botbotgo/agent-harness 0.0.355 → 0.0.359

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.355";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.359";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.355";
1
+ export const AGENT_HARNESS_VERSION = "0.0.359";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { existsSync } from "node:fs";
3
2
  export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
4
3
  if (!workspaceRoot || !skillPaths) {
5
4
  return skillPaths;
@@ -26,17 +25,5 @@ export function resolveDeepAgentSkillSourcePaths(options) {
26
25
  return relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
27
26
  }
28
27
  export function resolveDeepAgentSkillSourceRootPaths(options) {
29
- const { workspaceRoot, skillPaths } = options;
30
- if (!skillPaths) {
31
- return skillPaths;
32
- }
33
- const sourceRoots = Array.from(new Set(skillPaths.map((skillPath) => {
34
- const absolutePath = path.isAbsolute(skillPath) || !workspaceRoot
35
- ? skillPath
36
- : path.resolve(workspaceRoot, skillPath);
37
- return existsSync(path.join(absolutePath, "SKILL.md"))
38
- ? path.dirname(absolutePath)
39
- : absolutePath;
40
- })));
41
- return relativizeDeepAgentSkillSourcePaths(workspaceRoot, sourceRoots) ?? sourceRoots;
28
+ return resolveDeepAgentSkillSourcePaths(options);
42
29
  }
@@ -37,6 +37,9 @@ function isDelegationOnlyBinding(binding) {
37
37
  function hasTaskDelegationEvidence(executedToolResults) {
38
38
  return executedToolResults.some((item) => item.toolName === "task");
39
39
  }
40
+ function hasPlanToolEvidence(executedToolResults) {
41
+ return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
42
+ }
40
43
  function hasIncompleteTodos(value) {
41
44
  if (!Array.isArray(value)) {
42
45
  return false;
@@ -314,6 +317,19 @@ export async function executeRequestInvocation(options) {
314
317
  if (!result) {
315
318
  throw new Error("Agent invocation returned no result");
316
319
  }
320
+ if (options.resumePayload === undefined
321
+ && options.binding.harnessRuntime.executionContract?.requiresPlan === true
322
+ && !hasPlanToolEvidence(executedToolResults)) {
323
+ const messages = Array.isArray(result.messages)
324
+ ? result.messages
325
+ : undefined;
326
+ const recoveryBase = messages ? { messages } : request;
327
+ const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION);
328
+ const recoveredInvocation = await invokeOnce(recoveredRequest);
329
+ localOrUpstreamInvocation = recoveredInvocation;
330
+ result = recoveredInvocation.result;
331
+ executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
332
+ }
317
333
  if (options.resumePayload === undefined
318
334
  && options.binding.harnessRuntime.executionContract?.requiresPlan === true
319
335
  && hasIncompleteUpstreamPlan(result)
@@ -36,6 +36,17 @@ function hasIncompleteStateSnapshotPlan(stateSnapshot) {
36
36
  return status === "pending" || status === "in_progress";
37
37
  });
38
38
  }
39
+ function hasStateSnapshotPlan(stateSnapshot) {
40
+ return typeof stateSnapshot === "object"
41
+ && stateSnapshot !== null
42
+ && Array.isArray(stateSnapshot.todos);
43
+ }
44
+ function hasPlanToolEvidence(executedToolResults) {
45
+ return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
46
+ }
47
+ function hasExecutionToolEvidence(executedToolResults) {
48
+ return executedToolResults.some((item) => item.isError !== true && item.toolName !== "write_todos" && item.toolName !== "read_todos");
49
+ }
39
50
  function isPlaceholderTaskCompletion(value) {
40
51
  const normalized = sanitizeVisibleText(value).trim();
41
52
  return normalized === "Task completed";
@@ -357,6 +368,10 @@ export function finalizeRequestResult(params) {
357
368
  const stateSnapshot = buildStateSnapshot(result);
358
369
  const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
359
370
  && hasIncompleteStateSnapshotPlan(stateSnapshot);
371
+ const hasMissingRequiredPlanEvidence = binding?.harnessRuntime?.executionContract?.requiresPlan === true
372
+ && !hasStateSnapshotPlan(stateSnapshot)
373
+ && !hasPlanToolEvidence(allExecutedToolResults)
374
+ && !hasExecutionToolEvidence(allExecutedToolResults);
360
375
  const serializedResult = JSON.stringify(result, null, 2);
361
376
  let output = resolveDeterministicFinalOutput({
362
377
  visibleOutput,
@@ -369,7 +384,10 @@ export function finalizeRequestResult(params) {
369
384
  && !visibleOutput
370
385
  && !preliminaryTerminalStatus
371
386
  && allExecutedToolResults.some((toolResult) => toolResult.isError !== true && toolResult.toolName !== "write_todos" && toolResult.toolName !== "read_todos");
372
- if (hasIncompleteRequiredPlan && !visibleOutput) {
387
+ if (hasMissingRequiredPlanEvidence) {
388
+ output = "runtime_error=Agent ended before producing required plan evidence.";
389
+ }
390
+ else if (hasIncompleteRequiredPlan && !visibleOutput) {
373
391
  output = "runtime_error=Agent ended while required plan still had unfinished work.";
374
392
  }
375
393
  else if (hasMissingRequiredFinalAnswer) {
@@ -386,7 +404,7 @@ export function finalizeRequestResult(params) {
386
404
  agentId: bindingAgentId,
387
405
  state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
388
406
  ? "waiting_for_approval"
389
- : (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
407
+ : hasMissingRequiredPlanEvidence || (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
390
408
  ? "failed"
391
409
  : hasTerminalToolBlocker
392
410
  ? "failed"
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
4
  import { sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
+ import { salvageJsonToolCalls } from "./parsing/output-tool-args.js";
5
6
  import { extractMessageText } from "../utils/message-content.js";
6
7
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
7
8
  import { resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, } from "./adapter/compat/deepagent-compat.js";
@@ -105,12 +106,54 @@ function parseFirstJsonObject(value) {
105
106
  }
106
107
  return null;
107
108
  }
109
+ function parseCompactRouterSelection(value, subagentNames) {
110
+ const trimmed = value.trim();
111
+ if (subagentNames.has(trimmed)) {
112
+ return { subagentType: trimmed };
113
+ }
114
+ const parsed = parseFirstJsonObject(trimmed);
115
+ const toolCall = salvageJsonToolCalls(trimmed).at(0);
116
+ const payload = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)
117
+ ? parsed
118
+ : toolCall
119
+ ? { name: toolCall.name, arguments: toolCall.args }
120
+ : null;
121
+ if (!payload) {
122
+ return null;
123
+ }
124
+ const args = typeof payload.arguments === "object" && payload.arguments !== null && !Array.isArray(payload.arguments)
125
+ ? payload.arguments
126
+ : typeof payload.args === "object" && payload.args !== null && !Array.isArray(payload.args)
127
+ ? payload.args
128
+ : payload;
129
+ const subagentType = typeof payload.subagent_type === "string"
130
+ ? payload.subagent_type.trim()
131
+ : typeof args.subagent_type === "string"
132
+ ? args.subagent_type.trim()
133
+ : "";
134
+ if (subagentNames.has(subagentType)) {
135
+ return { subagentType };
136
+ }
137
+ const status = typeof payload.status === "string" ? payload.status.trim().toLowerCase() : "";
138
+ if (status === "refused") {
139
+ const reason = typeof payload.reason === "string" && payload.reason.trim()
140
+ ? payload.reason.trim()
141
+ : "No configured subagent can handle the request.";
142
+ return { refusedReason: reason };
143
+ }
144
+ return null;
145
+ }
108
146
  function isDelegationOnlyDeepAgentBinding(binding) {
109
147
  return isDeepAgentBinding(binding)
110
148
  && getBindingSubagents(binding).length > 0
111
149
  && getBindingPrimaryTools(binding).length === 0
112
150
  && getBindingSkills(binding).length === 0;
113
151
  }
152
+ function hasDelegatedPlanEvidence(result) {
153
+ const toolResults = result?.metadata?.executedToolResults;
154
+ return Array.isArray(toolResults)
155
+ && toolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos");
156
+ }
114
157
  export class AgentRuntimeAdapter {
115
158
  options;
116
159
  modelCache = new Map();
@@ -742,6 +785,7 @@ export class AgentRuntimeAdapter {
742
785
  return null;
743
786
  }
744
787
  const subagents = getBindingSubagents(binding);
788
+ const subagentNames = new Set(subagents.map((subagent) => subagent.name));
745
789
  const subagentCatalog = subagents
746
790
  .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
747
791
  .join("\n");
@@ -772,26 +816,86 @@ export class AgentRuntimeAdapter {
772
816
  requestId,
773
817
  }),
774
818
  })), resolveBindingTimeout(binding), "delegation router invoke", "invoke"));
775
- const parsed = parseFirstJsonObject(readModelText(raw));
776
- if (typeof parsed !== "object" || parsed === null) {
777
- return null;
819
+ const rawText = readModelText(raw);
820
+ let selection = parseCompactRouterSelection(rawText, subagentNames);
821
+ if (!selection) {
822
+ const retryPrompt = [
823
+ prompt,
824
+ "Your previous router output was invalid.",
825
+ "Previous output:",
826
+ rawText,
827
+ "Return only one JSON object now. Do not include prose, markdown, labels, or tool-call wrappers.",
828
+ ].join("\n\n");
829
+ const retryRaw = await this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(retryPrompt, resolveLangChainInvocationConfig(binding, {
830
+ sessionId,
831
+ requestId,
832
+ context: options.context,
833
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
834
+ ...options,
835
+ sessionId,
836
+ requestId,
837
+ }),
838
+ })), resolveBindingTimeout(binding), "delegation router retry invoke", "invoke"));
839
+ selection = parseCompactRouterSelection(readModelText(retryRaw), subagentNames);
778
840
  }
779
- const subagentType = typeof parsed.subagent_type === "string"
780
- ? parsed.subagent_type
781
- : "";
782
- if (!subagents.some((subagent) => subagent.name === subagentType)) {
841
+ if (selection?.refusedReason) {
842
+ return {
843
+ toolOutput: selection.refusedReason,
844
+ delegatedResult: {
845
+ sessionId,
846
+ requestId,
847
+ agentId: binding.agent.id,
848
+ state: "failed",
849
+ output: selection.refusedReason,
850
+ finalMessageText: selection.refusedReason,
851
+ },
852
+ };
853
+ }
854
+ const subagentType = selection?.subagentType ?? "";
855
+ if (!subagentNames.has(subagentType)) {
783
856
  return null;
784
857
  }
785
858
  const selectedBinding = this.options.bindingResolver(subagentType);
786
859
  if (!selectedBinding) {
787
860
  return null;
788
861
  }
789
- const delegatedResult = await this.invoke(selectedBinding, requestText, sessionId, `${requestId}:${subagentType}`, undefined, [], {
862
+ const runDelegatedRequest = (text, requestSuffix = "") => this.invoke(selectedBinding, text, sessionId, `${requestId}:${subagentType}${requestSuffix}`, undefined, [], {
790
863
  context: options.context,
791
864
  state: options.state,
792
865
  files: options.files,
793
866
  memoryContext: options.memoryContext,
794
867
  });
868
+ let delegatedResult = await runDelegatedRequest(requestText);
869
+ const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(selectedBinding).length > 0;
870
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
871
+ delegatedResult = await runDelegatedRequest([requestText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry");
872
+ }
873
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
874
+ const output = `runtime_error=Delegated agent ${selectedBinding.agent.id} completed without tool execution evidence.`;
875
+ return {
876
+ toolOutput: output,
877
+ delegatedResult: {
878
+ ...delegatedResult,
879
+ state: "failed",
880
+ output,
881
+ finalMessageText: output,
882
+ },
883
+ };
884
+ }
885
+ if (selectedBinding.harnessRuntime.executionContract?.requiresPlan === true
886
+ && !hasDelegatedPlanEvidence(delegatedResult)
887
+ && !hasDelegatedExecutionToolEvidence(delegatedResult)) {
888
+ const output = "runtime_error=Delegated agent ended before producing required plan evidence.";
889
+ return {
890
+ toolOutput: output,
891
+ delegatedResult: {
892
+ ...delegatedResult,
893
+ state: "failed",
894
+ output,
895
+ finalMessageText: output,
896
+ },
897
+ };
898
+ }
795
899
  return { toolOutput: delegatedResult.output, delegatedResult };
796
900
  }
797
901
  async *stream(binding, input, sessionId, history = [], options = {}) {
@@ -1,5 +1,5 @@
1
1
  import { AIMessage } from "langchain";
2
- import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
2
+ import { salvageFunctionLikeToolCall, salvageJsonToolCalls, salvageLabeledToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
3
3
  function consumeLeadingFunctionLikeToolCall(value) {
4
4
  const match = /^([A-Za-z_][A-Za-z0-9_]*)\(/.exec(value);
5
5
  if (!match) {
@@ -59,6 +59,9 @@ function consumeLeadingFunctionLikeToolCall(value) {
59
59
  function stripVisibleFunctionLikeToolCallText(value) {
60
60
  let remaining = value.trim();
61
61
  let removedLeadingCall = false;
62
+ if (salvageLabeledToolCall(remaining)) {
63
+ return "";
64
+ }
62
65
  if (salvageJsonToolCalls(remaining).length > 0) {
63
66
  return "";
64
67
  }
@@ -88,6 +91,7 @@ function stripVisibleFunctionLikeToolCallText(value) {
88
91
  export function sanitizeVisibleText(value) {
89
92
  return stripVisibleFunctionLikeToolCallText(value
90
93
  .replace(/<agent_memory>[\s\S]*?<\/agent_memory>/giu, "")
94
+ .replace(/<agent_memory>[\s\S]*$/giu, "")
91
95
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
92
96
  .replace(/todoListMiddleware\.after_model/g, "")
93
97
  .replace(/__end__+/g, "")
@@ -499,10 +503,13 @@ function normalizeAgentMessage(value) {
499
503
  const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
500
504
  ? salvageFunctionLikeToolCall(normalizedContent)
501
505
  : null;
502
- const jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
506
+ const labeledToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && typeof normalizedContent === "string"
507
+ ? salvageLabeledToolCall(normalizedContent)
508
+ : null;
509
+ const jsonToolCalls = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && !functionLikeToolCall && !labeledToolCall && typeof normalizedContent === "string"
503
510
  ? salvageJsonToolCalls(normalizedContent)
504
511
  : [];
505
- const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || jsonToolCalls.length > 0;
512
+ const hasRecoveredContentToolCalls = Boolean(functionLikeToolCall) || Boolean(labeledToolCall) || jsonToolCalls.length > 0;
506
513
  return new AIMessage({
507
514
  content: hasRecoveredContentToolCalls ? "" : normalizedContent,
508
515
  name: typeof typed.name === "string" ? typed.name : undefined,
@@ -513,6 +520,7 @@ function normalizeAgentMessage(value) {
513
520
  ...normalizedToolCalls,
514
521
  ...recoveredToolCalls,
515
522
  ...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
523
+ ...(labeledToolCall ? [{ name: labeledToolCall.name, args: labeledToolCall.args }] : []),
516
524
  ...jsonToolCalls.map((toolCall) => ({ name: toolCall.name, args: toolCall.args })),
517
525
  ],
518
526
  invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
@@ -3,6 +3,10 @@ export declare function salvageFunctionLikeToolCall(value: unknown): {
3
3
  name: string;
4
4
  args: Record<string, unknown>;
5
5
  } | null;
6
+ export declare function salvageLabeledToolCall(value: unknown): {
7
+ name: string;
8
+ args: Record<string, unknown>;
9
+ } | null;
6
10
  export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
7
11
  export declare function salvageJsonToolCalls(value: unknown): Array<{
8
12
  name: string;
@@ -112,6 +112,73 @@ export function salvageFunctionLikeToolCall(value) {
112
112
  }
113
113
  return { name, args: normalizeKnownToolArgs(name, args) };
114
114
  }
115
+ function normalizeFieldLabel(value) {
116
+ return value
117
+ .trim()
118
+ .toLowerCase()
119
+ .split("")
120
+ .filter((char) => {
121
+ const code = char.charCodeAt(0);
122
+ return (code >= 97 && code <= 122) || (code >= 48 && code <= 57);
123
+ })
124
+ .join("");
125
+ }
126
+ function splitLabeledLine(line) {
127
+ const separatorIndex = line.indexOf(":");
128
+ if (separatorIndex <= 0) {
129
+ return null;
130
+ }
131
+ const label = normalizeFieldLabel(line.slice(0, separatorIndex));
132
+ const value = line.slice(separatorIndex + 1).trim();
133
+ return label ? { label, value } : null;
134
+ }
135
+ function isToolName(value) {
136
+ if (!value) {
137
+ return false;
138
+ }
139
+ const first = value.charCodeAt(0);
140
+ if (!((first >= 65 && first <= 90) || (first >= 97 && first <= 122) || first === 95)) {
141
+ return false;
142
+ }
143
+ for (let index = 1; index < value.length; index += 1) {
144
+ const code = value.charCodeAt(index);
145
+ if (!((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || (code >= 48 && code <= 57) || code === 95)) {
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+ function isToolNameLabel(label) {
152
+ return label === "toolcall" || label === "tool" || label === "functioncall" || label === "function";
153
+ }
154
+ function isToolArgsLabel(label) {
155
+ return label === "arguments" || label === "args" || label === "parameters" || label === "input";
156
+ }
157
+ export function salvageLabeledToolCall(value) {
158
+ if (typeof value !== "string") {
159
+ return null;
160
+ }
161
+ const lines = value
162
+ .split("\n")
163
+ .map((line) => line.trim())
164
+ .filter(Boolean);
165
+ for (let index = 0; index < lines.length; index += 1) {
166
+ const toolLine = splitLabeledLine(lines[index]);
167
+ if (!toolLine || !isToolNameLabel(toolLine.label) || !isToolName(toolLine.value)) {
168
+ continue;
169
+ }
170
+ for (let argsIndex = index + 1; argsIndex < lines.length; argsIndex += 1) {
171
+ const argsLine = splitLabeledLine(lines[argsIndex]);
172
+ if (!argsLine || !isToolArgsLabel(argsLine.label)) {
173
+ continue;
174
+ }
175
+ const argsText = [argsLine.value, ...lines.slice(argsIndex + 1)].filter(Boolean).join("\n").trim();
176
+ const args = salvageToolArgs(argsText) ?? {};
177
+ return { name: toolLine.value, args: normalizeKnownToolArgs(toolLine.value, args) };
178
+ }
179
+ }
180
+ return null;
181
+ }
115
182
  function extractBalancedJsonValue(value, openChar, closeChar) {
116
183
  const start = value.indexOf(openChar);
117
184
  if (start < 0)
@@ -306,16 +373,16 @@ export function salvageJsonToolCalls(value) {
306
373
  return parsed;
307
374
  }
308
375
  }
309
- const embeddedArray = extractBalancedJsonArray(trimmed);
310
- if (embeddedArray) {
311
- const parsed = tryParseJson(embeddedArray);
376
+ const embeddedObject = extractBalancedJsonObject(trimmed);
377
+ if (embeddedObject) {
378
+ const parsed = tryParseJson(embeddedObject);
312
379
  if (parsed) {
313
380
  return parsed;
314
381
  }
315
382
  }
316
- const embeddedObject = extractBalancedJsonObject(trimmed);
317
- if (embeddedObject) {
318
- const parsed = tryParseJson(embeddedObject);
383
+ const embeddedArray = extractBalancedJsonArray(trimmed);
384
+ if (embeddedArray) {
385
+ const parsed = tryParseJson(embeddedArray);
319
386
  if (parsed) {
320
387
  return parsed;
321
388
  }
@@ -334,7 +401,7 @@ function normalizeWriteTodosArgs(args) {
334
401
  }
335
402
  return {
336
403
  ...args,
337
- todos: args.todos.map((todo) => {
404
+ todos: args.todos.map((todo, index) => {
338
405
  if (typeof todo !== "object" || !todo || Array.isArray(todo)) {
339
406
  return todo;
340
407
  }
@@ -343,7 +410,7 @@ function normalizeWriteTodosArgs(args) {
343
410
  ? record.content
344
411
  : typeof record.description === "string" && record.description.trim().length > 0
345
412
  ? record.description
346
- : undefined;
413
+ : `Step ${index + 1}`;
347
414
  const normalized = {};
348
415
  if (content !== undefined)
349
416
  normalized.content = content;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.355",
3
+ "version": "0.0.359",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,8 +8,8 @@
8
8
  "main": "./dist/index.js",
9
9
  "types": "./dist/index.d.ts",
10
10
  "bin": {
11
- "agent-harness": "./dist/cli.js",
12
- "botbotgo": "./dist/cli.js"
11
+ "agent-harness": "dist/cli.js",
12
+ "botbotgo": "dist/cli.js"
13
13
  },
14
14
  "files": [
15
15
  "dist"