@botbotgo/agent-harness 0.0.379 → 0.0.381
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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.381";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.381";
|
|
2
2
|
export const AGENT_HARNESS_RELEASE_DATE = "2026-04-30";
|
|
@@ -212,14 +212,14 @@ function isTodoPlanningToolName(name) {
|
|
|
212
212
|
|| name === "tool_call_write_todos"
|
|
213
213
|
|| name === "tool_call_read_todos";
|
|
214
214
|
}
|
|
215
|
-
function
|
|
215
|
+
function shouldLimitToolsToPlanning(input) {
|
|
216
216
|
const text = stringifyNodeLlamaCppInput(input);
|
|
217
217
|
return text.includes("required visible planning contract")
|
|
218
218
|
&& !hasPriorToolResultForToolName(input, "write_todos")
|
|
219
219
|
&& !hasPriorToolResultForToolName(input, "tool_call_write_todos");
|
|
220
220
|
}
|
|
221
|
-
function
|
|
222
|
-
if (!
|
|
221
|
+
function selectPlanningToolsForTurn(input, boundTools) {
|
|
222
|
+
if (!shouldLimitToolsToPlanning(input)) {
|
|
223
223
|
return boundTools;
|
|
224
224
|
}
|
|
225
225
|
const planningTools = boundTools.filter((tool) => isTodoPlanningToolName(readBoundToolName(tool)));
|
|
@@ -270,26 +270,52 @@ function normalizeProviderFacingInput(input) {
|
|
|
270
270
|
}
|
|
271
271
|
return input;
|
|
272
272
|
}
|
|
273
|
-
function createProviderToolMessageCompatModel(model) {
|
|
273
|
+
function createProviderToolMessageCompatModel(model, boundTools = []) {
|
|
274
|
+
const resolveTargetForTurn = (input) => {
|
|
275
|
+
if (boundTools.length === 0 || typeof model.bindTools !== "function") {
|
|
276
|
+
return model;
|
|
277
|
+
}
|
|
278
|
+
const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
|
|
279
|
+
return model.bindTools.call(model, effectiveBoundTools);
|
|
280
|
+
};
|
|
274
281
|
return new Proxy(model, {
|
|
275
282
|
has(target, prop) {
|
|
276
|
-
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
|
|
283
|
+
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
|
|
277
284
|
return true;
|
|
278
285
|
}
|
|
279
286
|
return prop in target;
|
|
280
287
|
},
|
|
281
288
|
get(target, prop, receiver) {
|
|
282
289
|
if (prop === "bindTools") {
|
|
283
|
-
return (tools) =>
|
|
284
|
-
const bound = target.bindTools.call(target, tools);
|
|
285
|
-
return createProviderToolMessageCompatModel(bound);
|
|
286
|
-
};
|
|
290
|
+
return (tools) => createProviderToolMessageCompatModel(target, tools);
|
|
287
291
|
}
|
|
288
292
|
if (prop === "invoke") {
|
|
289
|
-
return (input, config) =>
|
|
293
|
+
return (input, config) => {
|
|
294
|
+
const effectiveTarget = resolveTargetForTurn(input);
|
|
295
|
+
return effectiveTarget.invoke.call(effectiveTarget, normalizeProviderFacingInput(input), config);
|
|
296
|
+
};
|
|
290
297
|
}
|
|
291
298
|
if (prop === "stream") {
|
|
292
|
-
return (input, config) =>
|
|
299
|
+
return (input, config) => {
|
|
300
|
+
const effectiveTarget = resolveTargetForTurn(input);
|
|
301
|
+
return effectiveTarget.stream.call(effectiveTarget, normalizeProviderFacingInput(input), config);
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
if (prop === "streamEvents") {
|
|
305
|
+
return (input, config) => {
|
|
306
|
+
const effectiveTarget = resolveTargetForTurn(input);
|
|
307
|
+
if (typeof effectiveTarget.streamEvents === "function") {
|
|
308
|
+
return effectiveTarget.streamEvents.call(effectiveTarget, normalizeProviderFacingInput(input), config);
|
|
309
|
+
}
|
|
310
|
+
return (async function* () {
|
|
311
|
+
const output = await effectiveTarget.invoke.call(effectiveTarget, normalizeProviderFacingInput(input), config);
|
|
312
|
+
yield {
|
|
313
|
+
event: "on_chat_model_end",
|
|
314
|
+
name: typeof effectiveTarget.constructor?.name === "string" ? effectiveTarget.constructor.name : "ChatModel",
|
|
315
|
+
data: { output },
|
|
316
|
+
};
|
|
317
|
+
})();
|
|
318
|
+
};
|
|
293
319
|
}
|
|
294
320
|
if (prop === "withConfig" && typeof target.withConfig === "function") {
|
|
295
321
|
return (config) => createProviderToolMessageCompatModel(target.withConfig.call(target, config));
|
|
@@ -463,6 +489,65 @@ function normalizeParsedToolCall(payload) {
|
|
|
463
489
|
: salvageToolArgs(argsCandidate) ?? {};
|
|
464
490
|
return { name, args };
|
|
465
491
|
}
|
|
492
|
+
function extractFallbackTodoContentsFromText(text) {
|
|
493
|
+
const normalized = text.trim();
|
|
494
|
+
if (!normalized) {
|
|
495
|
+
return [];
|
|
496
|
+
}
|
|
497
|
+
const candidates = normalized
|
|
498
|
+
.split(/\r?\n/)
|
|
499
|
+
.map((line) => line
|
|
500
|
+
.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+|\[[ x~!-]\]\s+)/i, "")
|
|
501
|
+
.replace(/^\s*(?:todo|step|task)\s*\d*\s*[:.-]\s*/i, "")
|
|
502
|
+
.trim())
|
|
503
|
+
.filter((line) => line.length >= 12
|
|
504
|
+
&& !/^(?:status|summary|findings|blockers|next actions)\s*[::]?$/i.test(line)
|
|
505
|
+
&& !/\b(?:plan|todo|steps?)\s*[::]\s*$/i.test(line)
|
|
506
|
+
&& !isLowSignalPlanningLine(line));
|
|
507
|
+
const seen = new Set();
|
|
508
|
+
return candidates.filter((line) => {
|
|
509
|
+
const key = line.toLowerCase();
|
|
510
|
+
if (seen.has(key)) {
|
|
511
|
+
return false;
|
|
512
|
+
}
|
|
513
|
+
seen.add(key);
|
|
514
|
+
return true;
|
|
515
|
+
}).slice(0, 6);
|
|
516
|
+
}
|
|
517
|
+
function isLowSignalPlanningLine(value) {
|
|
518
|
+
const normalized = value.trim().toLowerCase();
|
|
519
|
+
return (normalized.length < 12
|
|
520
|
+
|| /^#+\s*/.test(normalized)
|
|
521
|
+
|| /^(?:ok|okay|sure|understood|got it|plan|todo|steps?)\.?$/.test(normalized));
|
|
522
|
+
}
|
|
523
|
+
function buildFallbackPlanningToolCall(input, tools, rawText) {
|
|
524
|
+
const toolName = tools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
|
|
525
|
+
if (!toolName) {
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
const modelPlannedItems = extractFallbackTodoContentsFromText(rawText);
|
|
529
|
+
const fallbackItems = modelPlannedItems.length >= 2
|
|
530
|
+
? modelPlannedItems
|
|
531
|
+
: [
|
|
532
|
+
"Gather concrete evidence for the requested investigation",
|
|
533
|
+
"Inspect the most relevant runtime signals and tool outputs",
|
|
534
|
+
"Analyze the evidence to identify root cause and impact",
|
|
535
|
+
"Produce the final RCA report with blockers and next actions",
|
|
536
|
+
];
|
|
537
|
+
const todos = fallbackItems.map((content, index) => ({
|
|
538
|
+
content,
|
|
539
|
+
status: index === 0 ? "in_progress" : "pending",
|
|
540
|
+
}));
|
|
541
|
+
return new AIMessage({
|
|
542
|
+
content: "",
|
|
543
|
+
tool_calls: [{
|
|
544
|
+
id: `tool-${Math.random().toString(36).slice(2, 10)}`,
|
|
545
|
+
name: toolName,
|
|
546
|
+
args: { todos },
|
|
547
|
+
type: "tool_call",
|
|
548
|
+
}],
|
|
549
|
+
});
|
|
550
|
+
}
|
|
466
551
|
function formatBoundToolInstruction(tool) {
|
|
467
552
|
if (typeof tool !== "object" || tool === null) {
|
|
468
553
|
return null;
|
|
@@ -485,11 +570,20 @@ function withPromptedJsonToolPrompt(input, tools, options = {}) {
|
|
|
485
570
|
if (toolInstructions.length === 0) {
|
|
486
571
|
return stringifyNodeLlamaCppInput(input);
|
|
487
572
|
}
|
|
573
|
+
const forcedPlanningInstruction = options.forcePlanningToolCall
|
|
574
|
+
? [
|
|
575
|
+
"Required planning tool call:",
|
|
576
|
+
"You must call write_todos now before any domain tool or final answer.",
|
|
577
|
+
"Return exactly one JSON object for write_todos with concrete todo items and statuses.",
|
|
578
|
+
"Do not write prose, markdown, analysis, or a plain-text plan.",
|
|
579
|
+
].join("\n")
|
|
580
|
+
: "";
|
|
488
581
|
const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
|
|
489
582
|
const prompt = stringifyNodeLlamaCppInput(input);
|
|
490
583
|
return [
|
|
491
584
|
options.suppressThinking ? NO_THINK_CONTROL_TOKEN : "",
|
|
492
585
|
systemContent,
|
|
586
|
+
forcedPlanningInstruction,
|
|
493
587
|
prompt,
|
|
494
588
|
PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER,
|
|
495
589
|
].filter(Boolean).join("\n\n");
|
|
@@ -497,7 +591,7 @@ function withPromptedJsonToolPrompt(input, tools, options = {}) {
|
|
|
497
591
|
function createPromptedJsonToolBindableModel(model, boundTools = [], options = {}) {
|
|
498
592
|
return new Proxy(model, {
|
|
499
593
|
has(target, prop) {
|
|
500
|
-
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
|
|
594
|
+
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
|
|
501
595
|
return true;
|
|
502
596
|
}
|
|
503
597
|
return prop in target;
|
|
@@ -508,14 +602,23 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
508
602
|
}
|
|
509
603
|
if (prop === "invoke") {
|
|
510
604
|
return async (input, config) => {
|
|
511
|
-
const effectiveBoundTools =
|
|
512
|
-
const
|
|
605
|
+
const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
|
|
606
|
+
const forcePlanningToolCall = shouldLimitToolsToPlanning(input);
|
|
607
|
+
const rawResult = await target.invoke(effectiveBoundTools.length > 0
|
|
608
|
+
? withPromptedJsonToolPrompt(input, effectiveBoundTools, { ...options, forcePlanningToolCall })
|
|
609
|
+
: input, config);
|
|
513
610
|
if (effectiveBoundTools.length === 0) {
|
|
514
611
|
return rawResult;
|
|
515
612
|
}
|
|
516
613
|
const text = readModelText(rawResult);
|
|
517
614
|
const parsedToolCall = normalizeParsedToolCall(extractToolCallPayload(text));
|
|
518
615
|
if (!parsedToolCall) {
|
|
616
|
+
if (forcePlanningToolCall) {
|
|
617
|
+
const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, text);
|
|
618
|
+
if (fallbackToolCall) {
|
|
619
|
+
return fallbackToolCall;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
519
622
|
return rawResult;
|
|
520
623
|
}
|
|
521
624
|
if (hasPriorToolResultForToolName(input, parsedToolCall.name)) {
|
|
@@ -540,6 +643,18 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
540
643
|
})();
|
|
541
644
|
};
|
|
542
645
|
}
|
|
646
|
+
if (prop === "streamEvents") {
|
|
647
|
+
return async (input, config) => {
|
|
648
|
+
const value = await receiver.invoke(input, config);
|
|
649
|
+
return (async function* () {
|
|
650
|
+
yield {
|
|
651
|
+
event: "on_chat_model_end",
|
|
652
|
+
name: typeof target.constructor?.name === "string" ? target.constructor.name : "ChatModel",
|
|
653
|
+
data: { output: value },
|
|
654
|
+
};
|
|
655
|
+
})();
|
|
656
|
+
};
|
|
657
|
+
}
|
|
543
658
|
if (prop === "withConfig" && typeof target.withConfig === "function") {
|
|
544
659
|
return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools, options);
|
|
545
660
|
}
|
|
@@ -547,7 +662,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
|
|
|
547
662
|
return typeof member === "function" ? member.bind(target) : member;
|
|
548
663
|
},
|
|
549
664
|
getOwnPropertyDescriptor(target, prop) {
|
|
550
|
-
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
|
|
665
|
+
if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "streamEvents" || prop === "withConfig") {
|
|
551
666
|
return {
|
|
552
667
|
configurable: true,
|
|
553
668
|
enumerable: false,
|
|
@@ -48,6 +48,23 @@ function isSubstantiveTerminalAssistantOutput(value) {
|
|
|
48
48
|
}
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
|
+
function inferPlanItemStatusFromTerminalAssistantOutput(value) {
|
|
52
|
+
const terminalStatus = readTerminalExecutionStatus(value);
|
|
53
|
+
if (terminalStatus) {
|
|
54
|
+
return mapTerminalStatusToPlanItemStatus(terminalStatus);
|
|
55
|
+
}
|
|
56
|
+
const normalized = sanitizeVisibleText(value).trim().toLowerCase();
|
|
57
|
+
if (!normalized) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (normalized.startsWith("runtime_error=")
|
|
61
|
+
|| /\bterminated\b/i.test(normalized)
|
|
62
|
+
|| /\b(?:blocked|blocker|failed|failure|refused|unable to complete|could not complete)\b/i.test(normalized)
|
|
63
|
+
|| /(?:执行失败|未能完成|无法完成|阻塞|失败)/u.test(normalized)) {
|
|
64
|
+
return "failed";
|
|
65
|
+
}
|
|
66
|
+
return isSubstantiveTerminalAssistantOutput(value) ? "completed" : null;
|
|
67
|
+
}
|
|
51
68
|
function reconcilePlanStateToTerminalStatus(planState, status, updatedAt) {
|
|
52
69
|
const items = planState.items.map((item) => ({
|
|
53
70
|
...item,
|
|
@@ -236,7 +253,10 @@ function getPlanStateFromUpstreamEvent(input) {
|
|
|
236
253
|
return null;
|
|
237
254
|
}
|
|
238
255
|
const typed = input.event;
|
|
239
|
-
const
|
|
256
|
+
const isWriteTodosStart = typed.event === "on_tool_start"
|
|
257
|
+
&& typed.name === "write_todos";
|
|
258
|
+
const todos = (isWriteTodosStart ? extractTodosArray(typed.data?.input) : null)
|
|
259
|
+
?? extractTodosArray(typed.data?.output)
|
|
240
260
|
?? extractTodosArray(typed.data?.chunk);
|
|
241
261
|
if (!todos) {
|
|
242
262
|
return null;
|
|
@@ -1023,8 +1043,9 @@ export async function* streamHarnessRun(options) {
|
|
|
1023
1043
|
}
|
|
1024
1044
|
}
|
|
1025
1045
|
currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
|
|
1026
|
-
|
|
1027
|
-
|
|
1046
|
+
const terminalAssistantPlanItemStatus = inferPlanItemStatusFromTerminalAssistantOutput(assistantOutput);
|
|
1047
|
+
if (terminalAssistantPlanItemStatus && planStateHasActiveItems(currentPlanState)) {
|
|
1048
|
+
const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalAssistantPlanItemStatus, new Date().toISOString());
|
|
1028
1049
|
const signature = buildPlanStateSignature(reconciledPlanState);
|
|
1029
1050
|
if (signature !== lastPlanStateSignature) {
|
|
1030
1051
|
const previousPlanState = currentPlanState;
|