@botbotgo/agent-harness 0.0.387 → 0.0.388

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.387";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.388";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-01";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.387";
1
+ export const AGENT_HARNESS_VERSION = "0.0.388";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-01";
@@ -184,16 +184,26 @@ function readToolMessageMetadata(value) {
184
184
  : undefined;
185
185
  return { name, toolCallId };
186
186
  }
187
+ function canonicalToolName(value) {
188
+ return value
189
+ .trim()
190
+ .toLowerCase()
191
+ .replace(/^tool_call_/, "")
192
+ .replace(/[^a-z0-9]+/g, "_")
193
+ .replace(/^_+|_+$/g, "");
194
+ }
187
195
  function hasPriorToolResultForToolName(input, toolName) {
188
196
  if (!toolName) {
189
197
  return false;
190
198
  }
199
+ const expectedName = canonicalToolName(toolName);
191
200
  if (Array.isArray(input)) {
192
201
  return input.some((message) => {
193
202
  if (mapMessageRole(message) !== "TOOL") {
194
203
  return false;
195
204
  }
196
- return readToolMessageMetadata(message).name === toolName;
205
+ const observedName = readToolMessageMetadata(message).name;
206
+ return typeof observedName === "string" && canonicalToolName(observedName) === expectedName;
197
207
  });
198
208
  }
199
209
  if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
@@ -212,6 +222,12 @@ function isTodoPlanningToolName(name) {
212
222
  || name === "tool_call_write_todos"
213
223
  || name === "tool_call_read_todos";
214
224
  }
225
+ function hasPriorNonPlanningToolResult(input, tools) {
226
+ const toolNames = tools
227
+ .map((tool) => readBoundToolName(tool))
228
+ .filter((name) => name && !isTodoPlanningToolName(name));
229
+ return toolNames.some((name) => hasPriorToolResultForToolName(input, name));
230
+ }
215
231
  function shouldLimitToolsToPlanning(input) {
216
232
  const text = stringifyNodeLlamaCppInput(input);
217
233
  return text.includes("required visible planning contract")
@@ -520,20 +536,65 @@ function isLowSignalPlanningLine(value) {
520
536
  || /^#+\s*/.test(normalized)
521
537
  || /^(?:ok|okay|sure|understood|got it|plan|todo|steps?)\.?$/.test(normalized));
522
538
  }
523
- function buildFallbackPlanningToolCall(input, tools, rawText) {
524
- const toolName = tools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
539
+ function normalizeDomainToolName(name) {
540
+ return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
541
+ }
542
+ function selectFallbackEvidenceToolName(input, tools) {
543
+ const prompt = stringifyNodeLlamaCppInput(input).toLowerCase();
544
+ const available = tools
545
+ .map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
546
+ .filter((name) => name && !isTodoPlanningToolName(name) && !isTodoPlanningToolName(`tool_call_${name}`));
547
+ const hasTool = (name) => available.includes(name);
548
+ const rules = [
549
+ [/\b(k8s|kubernetes|kubectl|pod|pods|node|nodes|cluster)\b|集群|节点|调度/u, ["k8s_cluster_investigate", "k8s_cluster_triage", "kubectl_command"]],
550
+ [/\b(disk|cache|storage|workspace)\b|磁盘|缓存|占用/u, ["disk_space_investigate"]],
551
+ [/\b(agent config|agent configuration|repository structure|repo structure|code evidence|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u, ["git_command", "codex_repo_analysis"]],
552
+ [/\b(stock|ticker|finance|market brief|aapl|wday)\b|股票|公开股票简报|金融|市场简报/u, ["finance_stock_report", "finance_quote_snapshot"]],
553
+ [/\b(test|qa|coverage|regression|playwright)\b|测试|覆盖率|回归|验证/u, ["git_command", "codex_repo_analysis", "playwright_capture"]],
554
+ [/\b(release|readiness|branch|tag|github actions|ci)\b|发版|分支|流水线|可发版/u, ["git_command", "gh_actions_command"]],
555
+ [/\b(youtube|transcript|brief|briefing|summary)\b|摘要|简报|讲稿/u, ["youtube_video_summary", "llamaindex_source_analysis", "finance_stock_report"]],
556
+ ];
557
+ for (const [pattern, toolNames] of rules) {
558
+ if (!pattern.test(prompt)) {
559
+ continue;
560
+ }
561
+ const matched = toolNames.find((name) => hasTool(name));
562
+ if (matched) {
563
+ return matched;
564
+ }
565
+ }
566
+ return available[0] ?? null;
567
+ }
568
+ function buildToolAwareFallbackTodoContents(input, tools) {
569
+ const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
570
+ if (!evidenceToolName) {
571
+ return [
572
+ "Identify the concrete evidence tool required for this request",
573
+ "Run the selected non-planning evidence tool and inspect its result",
574
+ "Update TODO status from the observed evidence",
575
+ "Return the final answer grounded in tool output",
576
+ ];
577
+ }
578
+ return [
579
+ `Run ${evidenceToolName} for the requested evidence`,
580
+ `Inspect the ${evidenceToolName} result and extract concrete findings`,
581
+ "Update TODO status from the observed evidence",
582
+ "Return the final answer grounded in tool output",
583
+ ];
584
+ }
585
+ function isGenericFallbackTodoContent(value) {
586
+ return /^(?:gather concrete evidence|inspect the most relevant runtime signals|analyze (?:the )?evidence|produce the final rca)/i.test(value.trim());
587
+ }
588
+ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText) {
589
+ const toolName = planningTools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
525
590
  if (!toolName) {
526
591
  return null;
527
592
  }
528
593
  const modelPlannedItems = extractFallbackTodoContentsFromText(rawText);
529
- const fallbackItems = modelPlannedItems.length >= 2
594
+ const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
595
+ const fallbackItems = hasUsefulModelPlan
530
596
  ? 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
- ];
597
+ : buildToolAwareFallbackTodoContents(input, allTools);
537
598
  const todos = fallbackItems.map((content, index) => ({
538
599
  content,
539
600
  status: index === 0 ? "in_progress" : "pending",
@@ -548,6 +609,111 @@ function buildFallbackPlanningToolCall(input, tools, rawText) {
548
609
  }],
549
610
  });
550
611
  }
612
+ function buildFallbackEvidenceToolArgs(toolName, input) {
613
+ const prompt = stringifyNodeLlamaCppInput(input);
614
+ const lower = prompt.toLowerCase();
615
+ if (toolName === "git_command") {
616
+ if (/\b(agent config|agent configuration|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u.test(lower)) {
617
+ return {
618
+ args: ["ls-files", "config/agents", "config/agent-context.md", "config/runtime", "config/models.yaml"],
619
+ cwd: ".",
620
+ timeoutMs: 10000,
621
+ };
622
+ }
623
+ return { args: ["status", "--short"], cwd: ".", timeoutMs: 10000 };
624
+ }
625
+ if (toolName === "disk_space_investigate") {
626
+ return { targetPath: ".", cwd: ".", timeoutMs: 10000 };
627
+ }
628
+ if (toolName === "finance_stock_report") {
629
+ const isWorkday = /\b(wday|workday)\b|workday\s*股票/i.test(prompt);
630
+ const isApple = /\b(aapl|apple)\b|苹果/u.test(prompt);
631
+ const symbol = isWorkday ? "WDAY" : isApple ? "AAPL" : undefined;
632
+ const company = isWorkday ? "Workday Inc." : isApple ? "Apple Inc." : undefined;
633
+ return {
634
+ ...(symbol ? { symbol } : {}),
635
+ ...(company ? { company } : {}),
636
+ query: symbol ? `${company} ${symbol} stock briefing` : "public company stock briefing",
637
+ market: "us",
638
+ count: 5,
639
+ };
640
+ }
641
+ if (toolName === "codex_repo_analysis") {
642
+ return {
643
+ repoPath: ".",
644
+ question: "Analyze the repository structure and provide concrete file-level evidence for the requested code/configuration question.",
645
+ timeoutMs: 30000,
646
+ skipGitRepoCheck: true,
647
+ };
648
+ }
649
+ if (toolName === "k8s_cluster_investigate") {
650
+ return { timeoutMs: 30000 };
651
+ }
652
+ if (toolName === "k8s_cluster_triage") {
653
+ return { timeoutMs: 30000 };
654
+ }
655
+ if (toolName === "gh_actions_command") {
656
+ return { args: ["list", "--limit", "5"], cwd: ".", timeoutMs: 30000 };
657
+ }
658
+ return {};
659
+ }
660
+ function buildFallbackEvidenceToolCall(input, tools) {
661
+ if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
662
+ return null;
663
+ }
664
+ const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
665
+ if (!evidenceToolName) {
666
+ return null;
667
+ }
668
+ const boundToolName = tools
669
+ .map((tool) => readBoundToolName(tool))
670
+ .find((name) => normalizeDomainToolName(name) === evidenceToolName);
671
+ if (!boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
672
+ return null;
673
+ }
674
+ return new AIMessage({
675
+ content: "",
676
+ tool_calls: [{
677
+ id: `tool-${Math.random().toString(36).slice(2, 10)}`,
678
+ name: boundToolName,
679
+ args: buildFallbackEvidenceToolArgs(evidenceToolName, input),
680
+ type: "tool_call",
681
+ }],
682
+ });
683
+ }
684
+ function buildFallbackTodoCompletionToolCall(input, tools) {
685
+ const prompt = stringifyNodeLlamaCppInput(input);
686
+ if (/TODO completed:|\[x\]/i.test(prompt)) {
687
+ return null;
688
+ }
689
+ const planningToolName = tools.map((tool) => readBoundToolName(tool)).find((name) => name === "write_todos" || name === "tool_call_write_todos");
690
+ if (!planningToolName) {
691
+ return null;
692
+ }
693
+ const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
694
+ if (!evidenceToolName) {
695
+ return null;
696
+ }
697
+ const hasEvidenceResult = hasPriorToolResultForToolName(input, evidenceToolName)
698
+ || hasPriorToolResultForToolName(input, `tool_call_${evidenceToolName}`)
699
+ || hasPriorNonPlanningToolResult(input, tools);
700
+ if (!hasEvidenceResult) {
701
+ return null;
702
+ }
703
+ const todos = buildToolAwareFallbackTodoContents(input, tools).map((content) => ({
704
+ content,
705
+ status: "completed",
706
+ }));
707
+ return new AIMessage({
708
+ content: "",
709
+ tool_calls: [{
710
+ id: `tool-${Math.random().toString(36).slice(2, 10)}`,
711
+ name: planningToolName,
712
+ args: { todos },
713
+ type: "tool_call",
714
+ }],
715
+ });
716
+ }
551
717
  function formatBoundToolInstruction(tool) {
552
718
  if (typeof tool !== "object" || tool === null) {
553
719
  return null;
@@ -614,7 +780,17 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
614
780
  const parsedToolCall = normalizeParsedToolCall(extractToolCallPayload(text));
615
781
  if (!parsedToolCall) {
616
782
  if (forcePlanningToolCall) {
617
- const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, text);
783
+ const fallbackToolCall = buildFallbackPlanningToolCall(input, effectiveBoundTools, boundTools, text);
784
+ if (fallbackToolCall) {
785
+ return fallbackToolCall;
786
+ }
787
+ }
788
+ else {
789
+ const fallbackCompletionToolCall = buildFallbackTodoCompletionToolCall(input, effectiveBoundTools);
790
+ if (fallbackCompletionToolCall) {
791
+ return fallbackCompletionToolCall;
792
+ }
793
+ const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools);
618
794
  if (fallbackToolCall) {
619
795
  return fallbackToolCall;
620
796
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.387",
3
+ "version": "0.0.388",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",