@botbotgo/agent-harness 0.0.419 → 0.0.421

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.
@@ -166,7 +166,7 @@ export async function runInteractiveChatLoop(input) {
166
166
  }
167
167
  activeSessionId = chatCommand.arg;
168
168
  latestRequestId = session.latestRequestId;
169
- activeAgentId = session.currentAgentId ?? session.entryAgentId ?? activeAgentId;
169
+ activeAgentId = session.entryAgentId ?? session.currentAgentId ?? activeAgentId;
170
170
  input.stdout(`session=${activeSessionId}\n`);
171
171
  continue;
172
172
  }
@@ -10,6 +10,7 @@ export async function streamChatMessage(input) {
10
10
  let latestSessionId = input.sessionId;
11
11
  let latestRequestId;
12
12
  let latestAgentId = input.agentId;
13
+ let entryAgentId = input.agentId;
13
14
  let wroteContent = false;
14
15
  let wroteRenderableBlocks = false;
15
16
  let renderedAssistantOutput = "";
@@ -348,6 +349,7 @@ export async function streamChatMessage(input) {
348
349
  writeChatStderr(lines.join(""));
349
350
  };
350
351
  const renderContentBlocks = (contentBlocks, agentId) => {
352
+ entryAgentId ??= agentId;
351
353
  latestAgentId = agentId || latestAgentId;
352
354
  const rendered = contentBlocks
353
355
  .map((block) => {
@@ -373,6 +375,7 @@ export async function streamChatMessage(input) {
373
375
  markRuntimeProgress();
374
376
  latestSessionId = snapshot.sessionId || latestSessionId;
375
377
  latestRequestId = snapshot.requestId || latestRequestId;
378
+ entryAgentId ??= snapshot.agentId;
376
379
  latestAgentId = snapshot.agentId || latestAgentId;
377
380
  latestSnapshot = snapshot;
378
381
  firstSnapshotAt ??= Date.now();
@@ -421,16 +424,19 @@ export async function streamChatMessage(input) {
421
424
  latestRequestId = delta.requestId || latestRequestId;
422
425
  firstDataAt ??= Date.now();
423
426
  if (delta.type === "output.text.delta") {
427
+ entryAgentId ??= delta.agentId;
424
428
  latestAgentId = delta.agentId || latestAgentId;
425
429
  writeAssistantOutput(delta.text);
426
430
  return;
427
431
  }
428
432
  if (delta.type === "output.content-blocks") {
433
+ entryAgentId ??= delta.agentId;
429
434
  suspendRequestTreeRendering();
430
435
  renderContentBlocks(delta.contentBlocks, delta.agentId);
431
436
  return;
432
437
  }
433
438
  if (delta.type === "plan.step") {
439
+ entryAgentId ??= delta.agentId;
434
440
  latestAgentId = delta.agentId || latestAgentId;
435
441
  const item = delta.item;
436
442
  const status = typeof item?.status === "string" ? item.status : "unknown";
@@ -441,6 +447,7 @@ export async function streamChatMessage(input) {
441
447
  return;
442
448
  }
443
449
  if (delta.type === "tool.result") {
450
+ entryAgentId ??= delta.agentId;
444
451
  latestAgentId = delta.agentId || latestAgentId;
445
452
  if ((input.showToolResults ?? true) && !input.requestEvents) {
446
453
  writeChatStderr(`\n[${formatPerfClock(Date.now())} +${formatElapsed(Date.now())}]${formatAgentProgressLabel(delta.agentId)} [tool:${delta.toolName}] ${summarizeChatToolResult(delta.output, delta.isError === true)}${delta.isError ? " (error)" : ""}\n`);
@@ -448,6 +455,7 @@ export async function streamChatMessage(input) {
448
455
  return;
449
456
  }
450
457
  if (delta.type === "progress.commentary") {
458
+ entryAgentId ??= delta.agentId;
451
459
  latestAgentId = delta.agentId || latestAgentId;
452
460
  if (wroteContent || wroteRenderableBlocks) {
453
461
  return;
@@ -500,5 +508,5 @@ export async function streamChatMessage(input) {
500
508
  writeChatStdout("\n");
501
509
  }
502
510
  await Promise.allSettled([stdoutWriteChain, stderrWriteChain]);
503
- return { sessionId: latestSessionId, requestId: latestRequestId, agentId: latestAgentId };
511
+ return { sessionId: latestSessionId, requestId: latestRequestId, agentId: entryAgentId ?? latestAgentId };
504
512
  }
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.419";
2
- export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.421";
2
+ export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.419";
2
- export const AGENT_HARNESS_RELEASE_DATE = "2026-05-02";
1
+ export const AGENT_HARNESS_VERSION = "0.0.421";
2
+ export const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
@@ -16,6 +16,16 @@ export declare function executeRequestInvocation(options: {
16
16
  toolRuntimeContext?: Record<string, unknown>;
17
17
  suppressInitialRequiredPlanInstruction?: boolean;
18
18
  externalPlanEvidence?: boolean;
19
+ externalPlanEvidenceTool?: {
20
+ name: string;
21
+ args?: Record<string, unknown>;
22
+ id?: string;
23
+ };
24
+ externalPlanEvidenceTools?: Array<{
25
+ name: string;
26
+ args?: Record<string, unknown>;
27
+ id?: string;
28
+ }>;
19
29
  };
20
30
  resolveTools: (tools: CompiledTool[], binding?: CompiledAgentBinding) => unknown[];
21
31
  getToolNameMapping: (binding: CompiledAgentBinding) => ToolNameMapping;
@@ -339,6 +339,8 @@ export async function executeRequestInvocation(options) {
339
339
  callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
340
340
  toolRuntimeContext: invokeOptions.toolRuntimeContext,
341
341
  externalPlanEvidence: invokeOptions.externalPlanEvidence,
342
+ externalPlanEvidenceTool: invokeOptions.externalPlanEvidenceTool,
343
+ externalPlanEvidenceTools: invokeOptions.externalPlanEvidenceTools,
342
344
  });
343
345
  let localOrUpstreamInvocation = await invokeOnce(request);
344
346
  if (options.resumePayload === undefined
@@ -18,6 +18,16 @@ export declare function invokeRuntimeWithLocalTools(options: {
18
18
  callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
19
19
  toolRuntimeContext?: Record<string, unknown>;
20
20
  externalPlanEvidence?: boolean;
21
+ externalPlanEvidenceTool?: {
22
+ name: string;
23
+ args?: Record<string, unknown>;
24
+ id?: string;
25
+ };
26
+ externalPlanEvidenceTools?: Array<{
27
+ name: string;
28
+ args?: Record<string, unknown>;
29
+ id?: string;
30
+ }>;
21
31
  }): Promise<{
22
32
  result: Record<string, unknown>;
23
33
  executedToolResults: ExecutedToolResult[];
@@ -16,5 +16,7 @@ export async function invokeRuntimeWithLocalTools(options) {
16
16
  callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
17
17
  toolRuntimeContext: options.toolRuntimeContext,
18
18
  externalPlanEvidence: options.externalPlanEvidence,
19
+ externalPlanEvidenceTool: options.externalPlanEvidenceTool,
20
+ externalPlanEvidenceTools: options.externalPlanEvidenceTools,
19
21
  });
20
22
  }
@@ -23,6 +23,16 @@ export declare function streamRuntimeExecution(options: {
23
23
  toolRuntimeContext?: Record<string, unknown>;
24
24
  suppressInitialRequiredPlanInstruction?: boolean;
25
25
  externalPlanEvidence?: boolean;
26
+ externalPlanEvidenceTool?: {
27
+ name: string;
28
+ args?: Record<string, unknown>;
29
+ id?: string;
30
+ };
31
+ externalPlanEvidenceTools?: Array<{
32
+ name: string;
33
+ args?: Record<string, unknown>;
34
+ id?: string;
35
+ }>;
26
36
  };
27
37
  primaryTools: CompiledTool[];
28
38
  toolNameMapping: ToolNameMapping;
@@ -51,6 +61,16 @@ export declare function streamRuntimeExecution(options: {
51
61
  toolRuntimeContext?: Record<string, unknown>;
52
62
  suppressInitialRequiredPlanInstruction?: boolean;
53
63
  externalPlanEvidence?: boolean;
64
+ externalPlanEvidenceTool?: {
65
+ name: string;
66
+ args?: Record<string, unknown>;
67
+ id?: string;
68
+ };
69
+ externalPlanEvidenceTools?: Array<{
70
+ name: string;
71
+ args?: Record<string, unknown>;
72
+ id?: string;
73
+ }>;
54
74
  }) => Promise<{
55
75
  output: string;
56
76
  metadata?: Record<string, unknown>;
@@ -128,6 +128,182 @@ function hasIncompletePlanOutput(value) {
128
128
  }
129
129
  return null;
130
130
  }
131
+ function extractInProgressTodoContents(value) {
132
+ if (typeof value !== "object" || value === null) {
133
+ return [];
134
+ }
135
+ const typed = value;
136
+ const arrays = [typed.todos, typed.items];
137
+ const contents = [];
138
+ for (const candidate of arrays) {
139
+ if (!Array.isArray(candidate)) {
140
+ continue;
141
+ }
142
+ for (const todo of candidate) {
143
+ if (typeof todo !== "object" || todo === null) {
144
+ continue;
145
+ }
146
+ const item = todo;
147
+ if (item.status !== "in_progress") {
148
+ continue;
149
+ }
150
+ const content = [item.content, item.description, item.title, item.name, item.text]
151
+ .find((value) => typeof value === "string" && value.trim().length > 0);
152
+ if (content) {
153
+ contents.push(content.trim());
154
+ }
155
+ }
156
+ }
157
+ for (const nested of [typed.summary, typed.update, typed.data, typed.output]) {
158
+ contents.push(...extractInProgressTodoContents(nested));
159
+ }
160
+ return [...new Set(contents)];
161
+ }
162
+ function extractTodoContentsForEvidenceResolution(value) {
163
+ const inProgress = extractTodoContentsByStatus(value, "in_progress");
164
+ if (inProgress.length > 0) {
165
+ return inProgress;
166
+ }
167
+ return extractTodoContentsByStatus(value, "pending");
168
+ }
169
+ function extractTodoContentsByStatus(value, status) {
170
+ if (Array.isArray(value)) {
171
+ return value.flatMap((item) => extractTodoContentsByStatus(item, status));
172
+ }
173
+ if (typeof value !== "object" || value === null) {
174
+ return [];
175
+ }
176
+ const typed = value;
177
+ const arrays = [typed.todos, typed.items];
178
+ const contents = [];
179
+ for (const array of arrays) {
180
+ if (!Array.isArray(array)) {
181
+ continue;
182
+ }
183
+ for (const item of array) {
184
+ if (typeof item !== "object" || item === null) {
185
+ continue;
186
+ }
187
+ const typedItem = item;
188
+ if (typedItem.status !== status) {
189
+ continue;
190
+ }
191
+ const content = [
192
+ typedItem.content,
193
+ typedItem.description,
194
+ typedItem.title,
195
+ typedItem.name,
196
+ typedItem.text,
197
+ ].find((value) => typeof value === "string" && value.trim().length > 0);
198
+ if (content) {
199
+ contents.push(content.trim());
200
+ }
201
+ }
202
+ }
203
+ for (const nested of [typed.summary, typed.update, typed.data, typed.output]) {
204
+ contents.push(...extractTodoContentsByStatus(nested, status));
205
+ }
206
+ return [...new Set(contents)];
207
+ }
208
+ function buildRunCommittedTodoEvidenceInstruction(primaryTools, planToolOutput) {
209
+ const todoContents = extractInProgressTodoContents(planToolOutput);
210
+ const todoText = todoContents.length > 0
211
+ ? todoContents.map((content, index) => `${index + 1}. ${content}`).join("\n")
212
+ : "(no in-progress todo content was readable)";
213
+ return [
214
+ buildRunEvidenceAfterPlanInstruction(primaryTools),
215
+ "",
216
+ "The completed write_todos result contains these in-progress evidence commitments:",
217
+ todoText,
218
+ "",
219
+ "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.",
220
+ ].join("\n");
221
+ }
222
+ function resolveCommittedPlanEvidenceTool(primaryTools, planToolOutput) {
223
+ return resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput)[0];
224
+ }
225
+ function resolveCommittedPlanEvidenceTools(primaryTools, planToolOutput) {
226
+ const availableToolNames = primaryTools
227
+ .map(readPrimaryToolName)
228
+ .filter((name) => name.length > 0 && !isPlanToolName(name));
229
+ const todoContents = extractTodoContentsForEvidenceResolution(planToolOutput);
230
+ if (todoContents.length === 0) {
231
+ return [];
232
+ }
233
+ const toolsByName = new Map(primaryTools.map((tool) => [tool.name, tool]));
234
+ const resolved = [];
235
+ const seen = new Set();
236
+ for (const content of todoContents) {
237
+ const todoText = content.toLowerCase();
238
+ const matches = availableToolNames.filter((name) => todoText.includes(name.toLowerCase()));
239
+ const selectedNames = matches.length === 1
240
+ ? [matches[0]]
241
+ : resolveBestScoredToolNames(availableToolNames, toolsByName, todoText);
242
+ for (const selectedName of selectedNames) {
243
+ if (seen.has(selectedName)) {
244
+ continue;
245
+ }
246
+ seen.add(selectedName);
247
+ const matchedTool = toolsByName.get(selectedName);
248
+ const args = buildCommittedPlanEvidenceToolArgs(matchedTool, content);
249
+ resolved.push({
250
+ name: selectedName,
251
+ args,
252
+ id: `stream-committed-plan-evidence-tool-${resolved.length + 1}`,
253
+ });
254
+ }
255
+ }
256
+ return resolved;
257
+ }
258
+ function extractSelectionTokens(value) {
259
+ const tokens = new Set();
260
+ for (const match of value.matchAll(/[\p{L}\p{N}_-]+/gu)) {
261
+ const token = match[0].toLowerCase();
262
+ if (token.length >= 2) {
263
+ tokens.add(token);
264
+ }
265
+ for (const part of token.split(/[_-]+/u)) {
266
+ if (part.length >= 2) {
267
+ tokens.add(part);
268
+ }
269
+ }
270
+ }
271
+ return tokens;
272
+ }
273
+ function resolveBestScoredToolNames(availableToolNames, toolsByName, todoText) {
274
+ const requestTokens = extractSelectionTokens(todoText);
275
+ const scored = availableToolNames
276
+ .map((name) => {
277
+ const tool = toolsByName.get(name);
278
+ const toolTokens = extractSelectionTokens(`${name} ${tool?.description ?? ""}`);
279
+ const toolNameTokens = extractSelectionTokens(name);
280
+ let score = 0;
281
+ for (const token of requestTokens) {
282
+ if (toolNameTokens.has(token)) {
283
+ score += 10;
284
+ continue;
285
+ }
286
+ if (toolTokens.has(token)) {
287
+ score += token.length > 3 ? 2 : 1;
288
+ }
289
+ }
290
+ return { name, score };
291
+ })
292
+ .filter((item) => item.score > 0)
293
+ .sort((left, right) => right.score - left.score);
294
+ const topScore = scored[0]?.score ?? 0;
295
+ return topScore > 0 ? scored.filter((item) => item.score === topScore).map((item) => item.name) : [];
296
+ }
297
+ function buildCommittedPlanEvidenceToolArgs(tool, todoText) {
298
+ const properties = typeof tool?.modelSchema === "object" && tool.modelSchema !== null
299
+ ? tool.modelSchema.properties
300
+ : undefined;
301
+ if (typeof properties !== "object" || properties === null) {
302
+ return {};
303
+ }
304
+ const queryLikeField = ["query", "question", "prompt", "input", "text"].find((field) => Object.prototype.hasOwnProperty.call(properties, field));
305
+ return queryLikeField ? { [queryLikeField]: todoText } : {};
306
+ }
131
307
  function normalizePlanToolName(toolName) {
132
308
  return typeof toolName === "string" ? toolName.trim().toLowerCase().replace(/[\s-]+/gu, "_") : "";
133
309
  }
@@ -655,6 +831,7 @@ export async function* streamRuntimeExecution(options) {
655
831
  let sawCompletedPlanToolResult = false;
656
832
  let sawSuccessfulNonTodoToolResult = false;
657
833
  let earlyStreamRecoveryInstruction = null;
834
+ let earlyStreamExternalPlanEvidenceTools;
658
835
  let earlyStreamRecoverySuppressInitialPlan = false;
659
836
  let completedPlanToolResultCount = 0;
660
837
  for await (const event of options.iterateWithTimeout(events, options.streamIdleTimeoutMs, "agent streamEvents", options.streamDeadlineAt, options.invokeTimeoutMs)) {
@@ -677,12 +854,15 @@ export async function* streamRuntimeExecution(options) {
677
854
  && chunk.kind !== "content"
678
855
  && !(chunk.kind === "tool-result" && isPlanToolName(chunk.toolName))
679
856
  && !(chunk.kind === "tool-result" && chunk.isError === true && isRetrySafeInvalidToolSelectionError(chunk.output)));
857
+ const hadPriorPlanToolResult = completedPlanToolResultCount > 0;
680
858
  const repeatedPlanToolResultBeforeEvidence = requiresPlanEvidence(options.binding)
681
859
  && !sawSuccessfulNonTodoToolResult
682
- && completedPlanToolResultCount > 0
860
+ && hadPriorPlanToolResult
683
861
  && projectedChunks.some((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName));
684
862
  if (repeatedPlanToolResultBeforeEvidence) {
685
- earlyStreamRecoveryInstruction = buildRunEvidenceAfterPlanInstruction(options.primaryTools);
863
+ const planToolResult = projectedChunks.find((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName) && chunk.isError !== true);
864
+ earlyStreamExternalPlanEvidenceTools = resolveCommittedPlanEvidenceTools(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
865
+ earlyStreamRecoveryInstruction = buildRunCommittedTodoEvidenceInstruction(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
686
866
  earlyStreamRecoverySuppressInitialPlan = true;
687
867
  break;
688
868
  }
@@ -721,6 +901,18 @@ export async function* streamRuntimeExecution(options) {
721
901
  }
722
902
  yield chunk;
723
903
  }
904
+ const eventContainsPlanToolResult = projectedChunks.some((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName) && chunk.isError !== true);
905
+ if (requiresPlanEvidence(options.binding)
906
+ && eventContainsPlanToolResult
907
+ && (hadPriorPlanToolResult
908
+ || projectedChunks.some((chunk) => isCompletedPlanToolResultChunk(chunk)))
909
+ && !sawSuccessfulNonTodoToolResult) {
910
+ const planToolResult = projectedChunks.find((chunk) => chunk.kind === "tool-result" && isPlanToolName(chunk.toolName) && chunk.isError !== true);
911
+ earlyStreamExternalPlanEvidenceTools = resolveCommittedPlanEvidenceTools(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
912
+ earlyStreamRecoveryInstruction = buildRunCommittedTodoEvidenceInstruction(options.primaryTools, planToolResult?.kind === "tool-result" ? planToolResult.output : undefined);
913
+ earlyStreamRecoverySuppressInitialPlan = true;
914
+ break;
915
+ }
724
916
  if (requiresPlanEvidence(options.binding) && sawCompletedPlanToolResult && sawSuccessfulNonTodoToolResult) {
725
917
  if (hasUsefulVisibleSynthesis(projectionState.emittedOutput)) {
726
918
  if (deferredStreamContent.length > 0) {
@@ -794,7 +986,13 @@ export async function* streamRuntimeExecution(options) {
794
986
  }
795
987
  if (earlyStreamRecoveryInstruction) {
796
988
  const earlyRecoveryRuntimeOptions = earlyStreamRecoverySuppressInitialPlan
797
- ? withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions)
989
+ ? {
990
+ ...withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions),
991
+ externalPlanEvidence: true,
992
+ ...(earlyStreamExternalPlanEvidenceTools && earlyStreamExternalPlanEvidenceTools.length > 0
993
+ ? { externalPlanEvidenceTools: earlyStreamExternalPlanEvidenceTools }
994
+ : {}),
995
+ }
798
996
  : options.runtimeOptions;
799
997
  const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, earlyStreamRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, earlyRecoveryRuntimeOptions);
800
998
  const recoveredToolResults = Array.isArray(recovered.metadata?.executedToolResults)
@@ -16,10 +16,20 @@ type LocalToolInvocationParams = {
16
16
  callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
17
17
  toolRuntimeContext?: Record<string, unknown>;
18
18
  externalPlanEvidence?: boolean;
19
+ externalPlanEvidenceTool?: {
20
+ name: string;
21
+ args?: Record<string, unknown>;
22
+ id?: string;
23
+ };
24
+ externalPlanEvidenceTools?: Array<{
25
+ name: string;
26
+ args?: Record<string, unknown>;
27
+ id?: string;
28
+ }>;
19
29
  };
20
30
  type LocalToolInvocationResult = {
21
31
  result: Record<string, unknown>;
22
32
  executedToolResults: ExecutedToolResult[];
23
33
  };
24
- export declare function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, toolRuntimeContext, externalPlanEvidence, }: LocalToolInvocationParams): Promise<LocalToolInvocationResult>;
34
+ export declare function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, toolRuntimeContext, externalPlanEvidence, externalPlanEvidenceTool, externalPlanEvidenceTools, }: LocalToolInvocationParams): Promise<LocalToolInvocationResult>;
25
35
  export {};