@botbotgo/agent-harness 0.0.419 → 0.0.420

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.420";
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.420";
2
+ export const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
@@ -13,11 +13,38 @@ 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 createBootstrapTodoPlan(primaryTools) {
16
+ function stringifyRequestForToolSelection(request) {
17
+ if (typeof request === "string") {
18
+ return request;
19
+ }
20
+ if (typeof request !== "object" || request === null) {
21
+ return "";
22
+ }
23
+ try {
24
+ return JSON.stringify(request);
25
+ }
26
+ catch {
27
+ return "";
28
+ }
29
+ }
30
+ function prioritizeBootstrapEvidenceTools(primaryTools, request) {
31
+ const requestText = stringifyRequestForToolSelection(request);
32
+ const isFinanceRequest = /\b(?:stock|ticker|finance|market|valuation|quote)\b|股票|股价|行情|估值|财报/iu.test(requestText);
17
33
  const evidenceTools = primaryTools
18
34
  .map((tool) => typeof tool.name === "string" ? tool.name.trim() : "")
19
35
  .filter((name) => name.length > 0 && !isPlanToolName(name))
20
- .slice(0, 3);
36
+ .sort((left, right) => {
37
+ if (!isFinanceRequest) {
38
+ return 0;
39
+ }
40
+ const leftFinance = left.includes("finance") ? 0 : 1;
41
+ const rightFinance = right.includes("finance") ? 0 : 1;
42
+ return leftFinance - rightFinance;
43
+ });
44
+ return evidenceTools.slice(0, 4);
45
+ }
46
+ function createBootstrapTodoPlan(primaryTools, request) {
47
+ const evidenceTools = prioritizeBootstrapEvidenceTools(primaryTools, request);
21
48
  if (evidenceTools.length === 0) {
22
49
  return [
23
50
  {
@@ -48,7 +75,7 @@ function createBootstrapTodoPlan(primaryTools) {
48
75
  },
49
76
  ];
50
77
  }
51
- function buildBootstrapPlanToolResult(primaryTools) {
78
+ function buildBootstrapPlanToolResult(primaryTools, request) {
52
79
  return {
53
80
  messages: [new AIMessage({
54
81
  content: "",
@@ -56,7 +83,7 @@ function buildBootstrapPlanToolResult(primaryTools) {
56
83
  id: `write-todos-bootstrap-${Math.random().toString(36).slice(2, 10)}`,
57
84
  name: "write_todos",
58
85
  args: {
59
- todos: createBootstrapTodoPlan(primaryTools),
86
+ todos: createBootstrapTodoPlan(primaryTools, request),
60
87
  },
61
88
  type: "tool_call",
62
89
  }],
@@ -296,7 +323,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
296
323
  if (requiresPlanEvidence(binding)
297
324
  && !hasPlanStateEvidence(executedToolResults, externalPlanEvidence)
298
325
  && builtinExecutableTools.has("write_todos")) {
299
- pendingResult = buildBootstrapPlanToolResult(primaryTools);
326
+ pendingResult = buildBootstrapPlanToolResult(primaryTools, activeRequest);
300
327
  continue;
301
328
  }
302
329
  if (recoveryInstruction) {
@@ -6,6 +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
10
  import { normalizeModelFacingToolSchema } from "../tool/resolved-tool.js";
10
11
  import { normalizeOpenAICompatibleInit } from "../compat/openai-compatible.js";
11
12
  import { recordPromptedJsonToolCall } from "./prompted-json-tool-call-capture.js";
@@ -497,6 +498,13 @@ function extractToolCallPayload(text) {
497
498
  if (direct) {
498
499
  return direct;
499
500
  }
501
+ const salvagedToolCall = salvageJsonToolCalls(trimmed)[0];
502
+ if (salvagedToolCall) {
503
+ return {
504
+ name: salvagedToolCall.name,
505
+ arguments: salvagedToolCall.args,
506
+ };
507
+ }
500
508
  const firstJsonObject = extractFirstJsonObjectPayload(trimmed);
501
509
  if (firstJsonObject) {
502
510
  return firstJsonObject;
@@ -62,6 +62,12 @@ function inferPlanItemStatusFromTerminalAssistantOutput(value) {
62
62
  }
63
63
  return isSubstantiveTerminalAssistantOutput(value) ? "completed" : null;
64
64
  }
65
+ function mapTerminalStatusToObservedPlanItemStatus(status, sawSuccessfulToolResult) {
66
+ if (status === "blocked" && sawSuccessfulToolResult) {
67
+ return "completed";
68
+ }
69
+ return mapTerminalStatusToPlanItemStatus(status);
70
+ }
65
71
  function reconcilePlanStateToTerminalStatus(planState, status, updatedAt) {
66
72
  const items = planState.items.map((item) => ({
67
73
  ...item,
@@ -1027,7 +1033,7 @@ export async function* streamHarnessRun(options) {
1027
1033
  currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
1028
1034
  const terminalStructuredStatus = readTerminalExecutionStatus(actual.structuredResponse);
1029
1035
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1030
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1036
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToObservedPlanItemStatus(terminalStructuredStatus, sawSuccessfulToolResult), new Date().toISOString());
1031
1037
  const signature = buildPlanStateSignature(reconciledPlanState);
1032
1038
  if (signature !== lastPlanStateSignature) {
1033
1039
  const previousPlanState = currentPlanState;
@@ -1053,6 +1059,9 @@ export async function* streamHarnessRun(options) {
1053
1059
  currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
1054
1060
  const explicitTerminalAssistantStatus = readTerminalExecutionStatus(assistantOutput);
1055
1061
  let terminalAssistantPlanItemStatus = inferPlanItemStatusFromTerminalAssistantOutput(assistantOutput);
1062
+ if (explicitTerminalAssistantStatus === "blocked" && sawSuccessfulToolResult) {
1063
+ terminalAssistantPlanItemStatus = "completed";
1064
+ }
1056
1065
  if (terminalAssistantPlanItemStatus === "failed"
1057
1066
  && sawSuccessfulToolResult
1058
1067
  && !explicitTerminalAssistantStatus
@@ -1135,7 +1144,7 @@ export async function* streamHarnessRun(options) {
1135
1144
  const canUseDeterministicToolEvidenceOutput = !currentPlanState || !planStateHasActiveItems(currentPlanState) || Boolean(terminalStructuredStatus);
1136
1145
  if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput && canUseDeterministicToolEvidenceOutput) {
1137
1146
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1138
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1147
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToObservedPlanItemStatus(terminalStructuredStatus, sawSuccessfulToolResult), new Date().toISOString());
1139
1148
  const signature = buildPlanStateSignature(reconciledPlanState);
1140
1149
  if (signature !== lastPlanStateSignature) {
1141
1150
  const previousPlanState = currentPlanState;
@@ -309,6 +309,89 @@ function normalizePythonLikeJson(value) {
309
309
  }
310
310
  return output;
311
311
  }
312
+ function repairMissingArrayObjectOpenBraces(value) {
313
+ let output = "";
314
+ let changed = false;
315
+ let inString = false;
316
+ let escaping = false;
317
+ const stack = [];
318
+ for (let index = 0; index < value.length; index += 1) {
319
+ const char = value[index];
320
+ if (inString) {
321
+ output += char;
322
+ if (escaping) {
323
+ escaping = false;
324
+ continue;
325
+ }
326
+ if (char === "\\") {
327
+ escaping = true;
328
+ continue;
329
+ }
330
+ if (char === "\"") {
331
+ inString = false;
332
+ }
333
+ continue;
334
+ }
335
+ if (char === "\"") {
336
+ output += char;
337
+ inString = true;
338
+ continue;
339
+ }
340
+ if (char === "{" || char === "[") {
341
+ stack.push(char);
342
+ output += char;
343
+ continue;
344
+ }
345
+ if (char === "}" || char === "]") {
346
+ const expectedOpen = char === "}" ? "{" : "[";
347
+ if (stack.at(-1) === expectedOpen) {
348
+ stack.pop();
349
+ }
350
+ output += char;
351
+ continue;
352
+ }
353
+ if (char !== "," || stack.at(-1) !== "[") {
354
+ output += char;
355
+ continue;
356
+ }
357
+ output += char;
358
+ let lookahead = index + 1;
359
+ while (lookahead < value.length && /\s/u.test(value[lookahead] ?? "")) {
360
+ output += value[lookahead];
361
+ lookahead += 1;
362
+ }
363
+ if (value[lookahead] !== "\"") {
364
+ index = lookahead - 1;
365
+ continue;
366
+ }
367
+ let cursor = lookahead + 1;
368
+ let keyEscaping = false;
369
+ while (cursor < value.length) {
370
+ const next = value[cursor];
371
+ if (keyEscaping) {
372
+ keyEscaping = false;
373
+ }
374
+ else if (next === "\\") {
375
+ keyEscaping = true;
376
+ }
377
+ else if (next === "\"") {
378
+ break;
379
+ }
380
+ cursor += 1;
381
+ }
382
+ let colonCursor = cursor + 1;
383
+ while (colonCursor < value.length && /\s/u.test(value[colonCursor] ?? "")) {
384
+ colonCursor += 1;
385
+ }
386
+ if (value[colonCursor] === ":") {
387
+ output += "{";
388
+ stack.push("{");
389
+ changed = true;
390
+ }
391
+ index = lookahead - 1;
392
+ }
393
+ return changed ? output : null;
394
+ }
312
395
  export function salvageToolArgs(value) {
313
396
  if (typeof value === "object" && value && !Array.isArray(value)) {
314
397
  return value;
@@ -359,6 +442,13 @@ export function salvageJsonToolCalls(value) {
359
442
  if (direct) {
360
443
  return direct;
361
444
  }
445
+ const repairedArrayObjects = repairMissingArrayObjectOpenBraces(trimmed);
446
+ if (repairedArrayObjects) {
447
+ const parsed = tryParseJson(repairedArrayObjects);
448
+ if (parsed) {
449
+ return parsed;
450
+ }
451
+ }
362
452
  const pythonLike = normalizePythonLikeJson(trimmed);
363
453
  if (pythonLike) {
364
454
  const parsed = tryParseJson(pythonLike);
@@ -366,6 +456,15 @@ export function salvageJsonToolCalls(value) {
366
456
  return parsed;
367
457
  }
368
458
  }
459
+ if (pythonLike) {
460
+ const repairedPythonLike = repairMissingArrayObjectOpenBraces(pythonLike);
461
+ if (repairedPythonLike) {
462
+ const parsed = tryParseJson(repairedPythonLike);
463
+ if (parsed) {
464
+ return parsed;
465
+ }
466
+ }
467
+ }
369
468
  const closed = closeJsonContainerSuffix(trimmed);
370
469
  if (closed) {
371
470
  const parsed = tryParseJson(closed);
@@ -373,6 +472,15 @@ export function salvageJsonToolCalls(value) {
373
472
  return parsed;
374
473
  }
375
474
  }
475
+ if (repairedArrayObjects) {
476
+ const closedRepaired = closeJsonContainerSuffix(repairedArrayObjects);
477
+ if (closedRepaired) {
478
+ const parsed = tryParseJson(closedRepaired);
479
+ if (parsed) {
480
+ return parsed;
481
+ }
482
+ }
483
+ }
376
484
  const embeddedObject = extractBalancedJsonObject(trimmed);
377
485
  if (embeddedObject) {
378
486
  const parsed = tryParseJson(embeddedObject);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.419",
3
+ "version": "0.0.420",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",