@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.
- package/dist/cli/chat-interactive.js +1 -1
- package/dist/cli/chat-stream.js +9 -1
- package/dist/package-version.d.ts +2 -2
- package/dist/package-version.js +2 -2
- package/dist/runtime/adapter/local-tool-invocation.js +32 -5
- package/dist/runtime/adapter/model/model-providers.js +8 -0
- package/dist/runtime/harness/run/stream-run.js +11 -2
- package/dist/runtime/parsing/output-tool-args.js +108 -0
- package/package.json +1 -1
|
@@ -166,7 +166,7 @@ export async function runInteractiveChatLoop(input) {
|
|
|
166
166
|
}
|
|
167
167
|
activeSessionId = chatCommand.arg;
|
|
168
168
|
latestRequestId = session.latestRequestId;
|
|
169
|
-
activeAgentId = session.
|
|
169
|
+
activeAgentId = session.entryAgentId ?? session.currentAgentId ?? activeAgentId;
|
|
170
170
|
input.stdout(`session=${activeSessionId}\n`);
|
|
171
171
|
continue;
|
|
172
172
|
}
|
package/dist/cli/chat-stream.js
CHANGED
|
@@ -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.
|
|
2
|
-
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.420";
|
|
2
|
+
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-03";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
2
|
-
export const AGENT_HARNESS_RELEASE_DATE = "2026-05-
|
|
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
|
|
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
|
-
.
|
|
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,
|
|
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,
|
|
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);
|