@botbotgo/agent-harness 0.0.455 → 0.0.456

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.455";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.456";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.455";
1
+ export const AGENT_HARNESS_VERSION = "0.0.456";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -43,6 +43,12 @@ function buildRunEvidenceAfterPlanInstruction(primaryTools) {
43
43
  `Available non-planning tool names: ${toolNames.join(", ")}.`,
44
44
  ].join("\n");
45
45
  }
46
+ function resolveConfiguredPlanEvidenceTool(primaryTools) {
47
+ const toolName = primaryTools
48
+ .map(readPrimaryToolName)
49
+ .find((name) => name.length > 0 && !isPlanToolName(name));
50
+ return toolName ? [{ name: toolName, args: {}, id: "stream-configured-plan-evidence-tool-1" }] : [];
51
+ }
46
52
  const INITIAL_REQUIRED_PLAN_INSTRUCTION = [
47
53
  "This agent has a required visible planning contract.",
48
54
  "Your first action for this request must be write_todos with concrete task steps and statuses.",
@@ -129,135 +135,6 @@ function hasIncompletePlanOutput(value) {
129
135
  }
130
136
  return null;
131
137
  }
132
- function extractInProgressTodoContents(value) {
133
- if (typeof value !== "object" || value === null) {
134
- return [];
135
- }
136
- const typed = value;
137
- const arrays = [typed.todos, typed.items];
138
- const contents = [];
139
- for (const candidate of arrays) {
140
- if (!Array.isArray(candidate)) {
141
- continue;
142
- }
143
- for (const todo of candidate) {
144
- if (typeof todo !== "object" || todo === null) {
145
- continue;
146
- }
147
- const item = todo;
148
- if (item.status !== "in_progress") {
149
- continue;
150
- }
151
- const content = [item.content, item.description, item.title, item.name, item.text]
152
- .find((value) => typeof value === "string" && value.trim().length > 0);
153
- if (content) {
154
- contents.push(content.trim());
155
- }
156
- }
157
- }
158
- for (const nested of [typed.summary, typed.update, typed.data, typed.output]) {
159
- contents.push(...extractInProgressTodoContents(nested));
160
- }
161
- return [...new Set(contents)];
162
- }
163
- function extractTodoContentsForEvidenceResolution(value) {
164
- const inProgress = extractTodoContentsByStatus(value, "in_progress");
165
- if (inProgress.length > 0) {
166
- return inProgress;
167
- }
168
- return extractTodoContentsByStatus(value, "pending");
169
- }
170
- function extractTodoContentsByStatus(value, status) {
171
- if (Array.isArray(value)) {
172
- return value.flatMap((item) => extractTodoContentsByStatus(item, status));
173
- }
174
- if (typeof value !== "object" || value === null) {
175
- return [];
176
- }
177
- const typed = value;
178
- const arrays = [typed.todos, typed.items];
179
- const contents = [];
180
- for (const array of arrays) {
181
- if (!Array.isArray(array)) {
182
- continue;
183
- }
184
- for (const item of array) {
185
- if (typeof item !== "object" || item === null) {
186
- continue;
187
- }
188
- const typedItem = item;
189
- if (typedItem.status !== status) {
190
- continue;
191
- }
192
- const content = [
193
- typedItem.content,
194
- typedItem.description,
195
- typedItem.title,
196
- typedItem.name,
197
- typedItem.text,
198
- ].find((value) => typeof value === "string" && value.trim().length > 0);
199
- if (content) {
200
- contents.push(content.trim());
201
- }
202
- }
203
- }
204
- for (const nested of [typed.summary, typed.update, typed.data, typed.output]) {
205
- contents.push(...extractTodoContentsByStatus(nested, status));
206
- }
207
- return [...new Set(contents)];
208
- }
209
- function buildRunCommittedTodoEvidenceInstruction(primaryTools, planToolOutput) {
210
- const todoContents = extractInProgressTodoContents(planToolOutput);
211
- const todoText = todoContents.length > 0
212
- ? todoContents.map((content, index) => `${index + 1}. ${content}`).join("\n")
213
- : "(no in-progress todo content was readable)";
214
- return [
215
- buildRunEvidenceAfterPlanInstruction(primaryTools),
216
- "",
217
- "The completed write_todos result contains these in-progress evidence commitments:",
218
- todoText,
219
- "",
220
- "Your next action must execute the non-planning tool named by the in-progress TODO. If every TODO is pending, execute the non-planning tool named by the first pending TODO. Do not call write_todos or read_todos again before that evidence tool returns.",
221
- ].join("\n");
222
- }
223
- function resolveCommittedPlanEvidenceTool(primaryTools, planToolOutput) {
224
- return resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput)[0];
225
- }
226
- function resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput) {
227
- const availableToolNames = primaryTools
228
- .map(readPrimaryToolName)
229
- .filter((name) => name.length > 0 && !isPlanToolName(name));
230
- const todoContents = extractTodoContentsForEvidenceResolution(planToolOutput);
231
- if (todoContents.length === 0) {
232
- return [];
233
- }
234
- const resolved = [];
235
- const seen = new Set();
236
- for (const content of todoContents) {
237
- const todoText = content.toLowerCase();
238
- const selectedNames = availableToolNames.filter((name) => hasExplicitToolNameReference(todoText, name));
239
- for (const selectedName of selectedNames) {
240
- if (seen.has(selectedName)) {
241
- continue;
242
- }
243
- seen.add(selectedName);
244
- resolved.push({
245
- name: selectedName,
246
- args: {},
247
- id: `stream-committed-plan-evidence-tool-${resolved.length + 1}`,
248
- });
249
- }
250
- }
251
- return resolved;
252
- }
253
- function hasExplicitToolNameReference(todoText, toolName) {
254
- const normalizedToolName = toolName.trim().toLowerCase();
255
- if (!normalizedToolName) {
256
- return false;
257
- }
258
- const escapedName = normalizedToolName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
259
- return new RegExp(`(?:^|[^\\p{L}\\p{N}_-])${escapedName}(?:$|[^\\p{L}\\p{N}_-])`, "iu").test(todoText);
260
- }
261
138
  function normalizePlanToolName(toolName) {
262
139
  return typeof toolName === "string" ? toolName.trim().toLowerCase().replace(/[\s-]+/gu, "_") : "";
263
140
  }
@@ -827,9 +704,8 @@ export async function* streamRuntimeExecution(options) {
827
704
  && hadPriorPlanToolResult
828
705
  && projectedChunks.some((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName));
829
706
  if (repeatedPlanToolResultBeforeEvidence) {
830
- const planToolResult = projectedChunks.find((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName) && chunk.isError !== true);
831
- earlyStreamExternalPlanEvidenceTools = resolveCommittedPlanEvidenceTools(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
832
- earlyStreamRecoveryInstruction = buildRunCommittedTodoEvidenceInstruction(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
707
+ earlyStreamExternalPlanEvidenceTools = resolveConfiguredPlanEvidenceTool(options.primaryTools);
708
+ earlyStreamRecoveryInstruction = buildRunEvidenceAfterPlanInstruction(options.primaryTools);
833
709
  earlyStreamRecoverySuppressInitialPlan = true;
834
710
  break;
835
711
  }
@@ -874,9 +750,8 @@ export async function* streamRuntimeExecution(options) {
874
750
  && (hadPriorPlanToolResult
875
751
  || projectedChunks.some((chunk) => isCompletedPlanToolResultChunk(chunk)))
876
752
  && !sawSuccessfulNonTodoToolResult) {
877
- const planToolResult = projectedChunks.find((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName) && chunk.isError !== true);
878
- earlyStreamExternalPlanEvidenceTools = resolveCommittedPlanEvidenceTools(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
879
- earlyStreamRecoveryInstruction = buildRunCommittedTodoEvidenceInstruction(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
753
+ earlyStreamExternalPlanEvidenceTools = resolveConfiguredPlanEvidenceTool(options.primaryTools);
754
+ earlyStreamRecoveryInstruction = buildRunEvidenceAfterPlanInstruction(options.primaryTools);
880
755
  earlyStreamRecoverySuppressInitialPlan = true;
881
756
  break;
882
757
  }
@@ -196,58 +196,6 @@ function terminalToolErrorRecoveryInstruction(terminalText) {
196
196
  function requiresPlanEvidence(binding) {
197
197
  return binding.harnessRuntime.executionContract?.requiresPlan === true;
198
198
  }
199
- function escapeRegExp(value) {
200
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
201
- }
202
- function textExplicitlyNamesTool(text, toolName) {
203
- const trimmedToolName = toolName.trim();
204
- if (trimmedToolName.length === 0) {
205
- return false;
206
- }
207
- return new RegExp(`(?:^|[^\\p{L}\\p{N}_-])${escapeRegExp(trimmedToolName)}(?:$|[^\\p{L}\\p{N}_-])`, "iu").test(text);
208
- }
209
- function resolveCommittedTodoEvidenceTool(executedToolResults, primaryTools) {
210
- const availableTools = primaryTools
211
- .filter((tool) => typeof tool.name === "string" && tool.name.length > 0 && !isPlanToolName(tool.name));
212
- if (availableTools.length === 0) {
213
- return null;
214
- }
215
- for (let index = executedToolResults.length - 1; index >= 0; index -= 1) {
216
- const result = executedToolResults[index];
217
- if (!result || result.isError === true || !isPlanToolName(result.toolName)) {
218
- continue;
219
- }
220
- const output = result.output;
221
- const summary = typeof output === "object" && output !== null
222
- ? output.summary
223
- : undefined;
224
- const items = typeof summary === "object" && summary !== null && Array.isArray(summary.items)
225
- ? summary.items
226
- : [];
227
- const activeItems = items.filter((item) => item.status === "in_progress");
228
- const candidateItems = activeItems.length > 0
229
- ? activeItems
230
- : items.filter((item) => item.status === "pending").slice(0, 1);
231
- for (const item of candidateItems) {
232
- const content = [
233
- item.content,
234
- item.description,
235
- item.title,
236
- item.name,
237
- item.text,
238
- ].filter((value) => typeof value === "string").join(" ");
239
- const matched = availableTools.map((tool) => tool.name).filter((toolName) => textExplicitlyNamesTool(content, toolName));
240
- if (matched.length === 1) {
241
- return {
242
- name: matched[0],
243
- args: {},
244
- id: `todo-committed-evidence-${index}`,
245
- };
246
- }
247
- }
248
- }
249
- return null;
250
- }
251
199
  function extractLatestUserInput(request) {
252
200
  const typedRequest = request;
253
201
  const messages = Array.isArray(typedRequest.messages) ? typedRequest.messages : [];
@@ -344,7 +292,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
344
292
  : await callRuntimeWithToolParseRecovery(activeRequest));
345
293
  pendingResult = undefined;
346
294
  let toolCalls = extractToolCallsFromResult(result);
347
- const committedTodoEvidenceTool = requiresPlanEvidence(binding)
295
+ const structuredExternalPlanEvidenceTool = requiresPlanEvidence(binding)
348
296
  && hasPlanStateEvidence(executedToolResults, externalPlanEvidence)
349
297
  && !hasNonTodoToolEvidence(executedToolResults)
350
298
  && (externalPlanEvidenceTool !== undefined || !hasIncompleteExecutedPlan(executedToolResults, externalPlanEvidence))
@@ -355,10 +303,10 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
355
303
  args: externalPlanEvidenceTool.args ?? {},
356
304
  id: externalPlanEvidenceTool.id ?? "external-plan-evidence-tool",
357
305
  }
358
- : resolveCommittedTodoEvidenceTool(executedToolResults, primaryTools)
306
+ : null
359
307
  : null;
360
- if (committedTodoEvidenceTool) {
361
- toolCalls = [committedTodoEvidenceTool];
308
+ if (structuredExternalPlanEvidenceTool) {
309
+ toolCalls = [structuredExternalPlanEvidenceTool];
362
310
  }
363
311
  if (toolCalls.length === 0) {
364
312
  const terminalText = sanitizeVisibleText(extractVisibleOutput(result) || "");
@@ -514,7 +462,6 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
514
462
  role: "system",
515
463
  content: TOOL_FOLLOW_UP_INSTRUCTION,
516
464
  });
517
- const hadNonTodoEvidenceBeforeToolReplay = hasNonTodoToolEvidence(executedToolResults);
518
465
  for (let toolIndex = 0; toolIndex < toolCalls.length; toolIndex += 1) {
519
466
  const toolCall = toolCalls[toolIndex];
520
467
  const resolvedToolName = resolveModelFacingToolName(toolCall.name, toolNameMapping, primaryTools);
@@ -571,61 +518,6 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
571
518
  content: stringifyToolOutput(safeToolResult),
572
519
  }));
573
520
  }
574
- const committedEvidenceTool = requiresPlanEvidence(binding)
575
- && !hadNonTodoEvidenceBeforeToolReplay
576
- && !hasNonTodoToolEvidence(executedToolResults)
577
- && !hasIncompleteExecutedPlan(executedToolResults, externalPlanEvidence)
578
- ? resolveCommittedTodoEvidenceTool(executedToolResults, primaryTools)
579
- : null;
580
- if (committedEvidenceTool) {
581
- const resolvedToolName = resolveModelFacingToolName(committedEvidenceTool.name, toolNameMapping, primaryTools);
582
- const executable = executableTools.get(committedEvidenceTool.name) ?? executableTools.get(resolvedToolName);
583
- if (executable) {
584
- const compiledTool = toolCatalog.get(committedEvidenceTool.name) ?? toolCatalog.get(resolvedToolName);
585
- const normalizedArgs = normalizeToolArgsForSchema(committedEvidenceTool.args, executable.schema, undefined, {
586
- latestUserInput,
587
- });
588
- const gateway = validateToolGatewayInput({
589
- toolName: executable.name,
590
- schema: executable.schema,
591
- args: normalizedArgs,
592
- requiresApproval: compiledTool ? toolRequiresRuntimeApproval(compiledTool) : false,
593
- });
594
- if (gateway.ok) {
595
- const toolResult = toolRuntimeContext
596
- ? await executable.invoke(gateway.input, { toolRuntimeContext })
597
- : await executable.invoke(gateway.input);
598
- const memoryCandidates = compiledTool ? extractMemoryCandidatesFromToolOutput(compiledTool, toolResult) : [];
599
- const safeToolResult = await maybePersistLargeToolOutput({
600
- toolName: executable.name,
601
- output: toolResult,
602
- toolRuntimeContext,
603
- });
604
- executedToolResults.push({
605
- toolName: executable.name,
606
- output: safeToolResult,
607
- ...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
608
- });
609
- nextMessages.push(new ToolMessage({
610
- name: executable.name,
611
- tool_call_id: committedEvidenceTool.id,
612
- content: stringifyToolOutput(safeToolResult),
613
- }));
614
- }
615
- else {
616
- executedToolResults.push({
617
- toolName: executable.name,
618
- output: gateway.error,
619
- isError: true,
620
- });
621
- nextMessages.push(new ToolMessage({
622
- name: executable.name,
623
- tool_call_id: committedEvidenceTool.id,
624
- content: stringifyToolOutput(gateway.error),
625
- }));
626
- }
627
- }
628
- }
629
521
  if (usedExternalPlanEvidenceToolThisIteration && hasNonTodoToolEvidence(executedToolResults)) {
630
522
  return {
631
523
  result: buildDeterministicFinalFromToolEvidence(executedToolResults),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.455",
3
+ "version": "0.0.456",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",