@botbotgo/agent-harness 0.0.400 → 0.0.402

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.
@@ -211,6 +211,68 @@ function hasPriorToolResultForToolName(input, toolName) {
211
211
  }
212
212
  return false;
213
213
  }
214
+ function hasAnyPriorToolResult(input) {
215
+ if (Array.isArray(input)) {
216
+ return input.some((message) => mapMessageRole(message) === "TOOL");
217
+ }
218
+ if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
219
+ return hasAnyPriorToolResult(input.messages);
220
+ }
221
+ return false;
222
+ }
223
+ function hasPriorNonPlanningToolCall(input) {
224
+ if (Array.isArray(input)) {
225
+ return input.some((message) => readToolCalls(message).some((toolCall) => {
226
+ if (typeof toolCall !== "object" || toolCall === null) {
227
+ return false;
228
+ }
229
+ const name = typeof toolCall.name === "string"
230
+ ? toolCall.name
231
+ : "";
232
+ return name.length > 0 && !isTodoPlanningToolName(name);
233
+ }));
234
+ }
235
+ if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
236
+ return hasPriorNonPlanningToolCall(input.messages);
237
+ }
238
+ return false;
239
+ }
240
+ function readLatestToolResultContent(input) {
241
+ if (Array.isArray(input)) {
242
+ for (let index = input.length - 1; index >= 0; index -= 1) {
243
+ const message = input[index];
244
+ if (mapMessageRole(message) === "TOOL") {
245
+ const content = readPromptContent(message);
246
+ return content || null;
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+ if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
252
+ return readLatestToolResultContent(input.messages);
253
+ }
254
+ return null;
255
+ }
256
+ function readLatestUserContent(input) {
257
+ if (Array.isArray(input)) {
258
+ for (let index = input.length - 1; index >= 0; index -= 1) {
259
+ const message = input[index];
260
+ if (mapMessageRole(message) !== "USER") {
261
+ continue;
262
+ }
263
+ const content = readPromptContent(message).trim();
264
+ if (content) {
265
+ return content;
266
+ }
267
+ }
268
+ return undefined;
269
+ }
270
+ if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
271
+ return readLatestUserContent(input.messages);
272
+ }
273
+ const content = readPromptContent(input).trim();
274
+ return content || undefined;
275
+ }
214
276
  function readBoundToolName(tool) {
215
277
  return typeof tool === "object" && tool !== null && typeof tool.name === "string"
216
278
  ? tool.name.trim()
@@ -238,6 +300,12 @@ function selectPlanningToolsForTurn(input, boundTools) {
238
300
  if (!shouldLimitToolsToPlanning(input)) {
239
301
  return boundTools;
240
302
  }
303
+ if (!boundTools.some((tool) => {
304
+ const name = readBoundToolName(tool);
305
+ return name && !isTodoPlanningToolName(name);
306
+ })) {
307
+ return [];
308
+ }
241
309
  const planningTools = boundTools.filter((tool) => isTodoPlanningToolName(readBoundToolName(tool)));
242
310
  return planningTools.length > 0 ? planningTools : boundTools;
243
311
  }
@@ -357,47 +425,6 @@ function stringifyNodeLlamaCppInput(input) {
357
425
  }
358
426
  return readPromptContent(input);
359
427
  }
360
- function readLatestUserPromptContent(input) {
361
- const messages = typeof input === "object"
362
- && input !== null
363
- && Array.isArray(input.messages)
364
- ? input.messages
365
- : Array.isArray(input)
366
- ? input
367
- : null;
368
- if (!messages) {
369
- return readPromptContent(input);
370
- }
371
- for (let index = messages.length - 1; index >= 0; index -= 1) {
372
- if (mapMessageRole(messages[index]) !== "USER") {
373
- continue;
374
- }
375
- const content = readPromptContent(messages[index]);
376
- if (content) {
377
- return content;
378
- }
379
- }
380
- return stringifyNodeLlamaCppInput(input);
381
- }
382
- function inferStockRequest(input) {
383
- const prompt = readLatestUserPromptContent(input);
384
- const directSymbol = prompt.match(/\b[A-Z]{1,5}(?:\.[A-Z])?\b/u)?.[0];
385
- const lower = prompt.toLowerCase();
386
- const enterpriseHcmName = ["work", "day"].join("");
387
- const symbol = directSymbol
388
- ?? (/\bapple\b/u.test(lower) || /苹果/u.test(prompt) ? "AAPL" : undefined)
389
- ?? (new RegExp(`\\b${enterpriseHcmName}\\b`, "u").test(lower) ? "WDAY" : undefined);
390
- const company = symbol === "AAPL"
391
- ? "Apple Inc."
392
- : symbol === "WDAY"
393
- ? `${enterpriseHcmName[0].toUpperCase()}${enterpriseHcmName.slice(1)} Inc.`
394
- : undefined;
395
- return {
396
- ...(symbol ? { symbol } : {}),
397
- ...(company ? { company } : {}),
398
- query: symbol ? `${company ?? symbol} ${symbol} stock briefing` : prompt || "public company stock briefing",
399
- };
400
- }
401
428
  function extractToolCallPayload(text) {
402
429
  const trimmed = text.trim();
403
430
  if (!trimmed) {
@@ -580,34 +607,19 @@ function isLowSignalPlanningLine(value) {
580
607
  function normalizeDomainToolName(name) {
581
608
  return name.startsWith("tool_call_") ? name.slice("tool_call_".length) : name;
582
609
  }
583
- function selectFallbackEvidenceToolName(input, tools) {
584
- const prompt = stringifyNodeLlamaCppInput(input).toLowerCase();
610
+ function selectFallbackEvidenceToolName(tools, rawText = "") {
611
+ const normalizedRawText = rawText.toLowerCase();
585
612
  const available = tools
586
613
  .map((tool) => normalizeDomainToolName(readBoundToolName(tool)))
587
614
  .filter((name) => name && !isTodoPlanningToolName(name) && !isTodoPlanningToolName(`tool_call_${name}`));
588
- const hasTool = (name) => available.includes(name);
589
- const rules = [
590
- [/\b(k8s|kubernetes|kubectl|pod|pods|node|nodes|cluster)\b|集群|节点|调度/u, ["k8s_cluster_investigate", "k8s_cluster_triage", "kubectl_command"]],
591
- [/\b(disk|cache|storage|workspace)\b|磁盘|缓存|占用/u, ["disk_space_investigate"]],
592
- [/\b(agent config|agent configuration|repository structure|repo structure|code evidence|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u, ["git_command", "codex_repo_analysis"]],
593
- [/\b(stock|ticker|finance|market brief|aapl|wday)\b|股票|公开股票简报|金融|市场简报/u, ["finance_stock_report", "finance_quote_snapshot"]],
594
- [/\b(test|qa|coverage|regression|playwright)\b|测试|覆盖率|回归|验证/u, ["git_command", "codex_repo_analysis", "playwright_capture"]],
595
- [/\b(release|readiness|branch|tag|github actions|ci)\b|发版|分支|流水线|可发版/u, ["git_command", "gh_actions_command"]],
596
- [/\b(youtube|transcript|brief|briefing|summary)\b|摘要|简报|讲稿/u, ["youtube_video_summary", "llamaindex_source_analysis", "finance_stock_report"]],
597
- ];
598
- for (const [pattern, toolNames] of rules) {
599
- if (!pattern.test(prompt)) {
600
- continue;
601
- }
602
- const matched = toolNames.find((name) => hasTool(name));
603
- if (matched) {
604
- return matched;
605
- }
615
+ if (available.length === 0) {
616
+ return null;
606
617
  }
607
- return available[0] ?? null;
618
+ const mentioned = available.find((name) => normalizedRawText.includes(name.toLowerCase()));
619
+ return mentioned ?? available[0] ?? null;
608
620
  }
609
- function buildToolAwareFallbackTodoContents(input, tools) {
610
- const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
621
+ function buildToolAwareFallbackTodoContents(tools, rawText = "") {
622
+ const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
611
623
  if (!evidenceToolName) {
612
624
  return [
613
625
  "Identify the concrete evidence tool required for this request",
@@ -633,9 +645,13 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
633
645
  }
634
646
  const modelPlannedItems = extractFallbackTodoContentsFromText(rawText);
635
647
  const hasUsefulModelPlan = modelPlannedItems.length >= 2 && !modelPlannedItems.every(isGenericFallbackTodoContent);
648
+ const hasEvidenceTool = selectFallbackEvidenceToolName(allTools, rawText) !== null;
649
+ if (!hasUsefulModelPlan && !hasEvidenceTool) {
650
+ return null;
651
+ }
636
652
  const fallbackItems = hasUsefulModelPlan
637
653
  ? modelPlannedItems
638
- : buildToolAwareFallbackTodoContents(input, allTools);
654
+ : buildToolAwareFallbackTodoContents(allTools, rawText);
639
655
  const todos = fallbackItems.map((content, index) => ({
640
656
  content,
641
657
  status: index === 0 ? "in_progress" : "pending",
@@ -650,60 +666,51 @@ function buildFallbackPlanningToolCall(input, planningTools, allTools, rawText)
650
666
  }],
651
667
  });
652
668
  }
653
- function buildFallbackEvidenceToolArgs(toolName, input) {
654
- const prompt = stringifyNodeLlamaCppInput(input);
655
- const lower = prompt.toLowerCase();
656
- if (toolName === "git_command") {
657
- if (/\b(agent config|agent configuration|specialist)\b|agent\s*配置|配置结构|新增\s*specialist|代码层证据|改哪些文件/u.test(lower)) {
658
- return {
659
- args: ["ls-files", "config/agents", "config/agent-context.md", "config/runtime", "config/models.yaml"],
660
- cwd: ".",
661
- timeoutMs: 10000,
662
- };
663
- }
664
- return { args: ["status", "--short"], cwd: ".", timeoutMs: 10000 };
665
- }
666
- if (toolName === "disk_space_investigate") {
667
- return { targetPath: ".", cwd: ".", timeoutMs: 10000 };
668
- }
669
- if (toolName === "finance_stock_report") {
670
- return {
671
- ...inferStockRequest(input),
672
- market: "us",
673
- count: 5,
674
- };
675
- }
676
- if (toolName === "codex_repo_analysis") {
677
- return {
678
- repoPath: ".",
679
- question: "Analyze the repository structure and provide concrete file-level evidence for the requested code/configuration question.",
680
- timeoutMs: 30000,
681
- skipGitRepoCheck: true,
682
- };
683
- }
684
- if (toolName === "k8s_cluster_investigate") {
685
- return { timeoutMs: 30000 };
686
- }
687
- if (toolName === "k8s_cluster_triage") {
688
- return { timeoutMs: 30000 };
669
+ function buildFallbackEvidenceToolArgs(tool, latestUserInput) {
670
+ const schema = normalizeModelFacingToolSchema(tool);
671
+ if (typeof schema.properties !== "object" || schema.properties === null || Array.isArray(schema.properties)) {
672
+ return {};
689
673
  }
690
- if (toolName === "gh_actions_command") {
691
- return { args: ["list", "--limit", "5"], cwd: ".", timeoutMs: 30000 };
674
+ const required = Array.isArray(schema.required)
675
+ ? schema.required.filter((name) => typeof name === "string")
676
+ : [];
677
+ const values = {};
678
+ for (const [key, value] of Object.entries(schema.properties)) {
679
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
680
+ continue;
681
+ }
682
+ const property = value;
683
+ if ("default" in property) {
684
+ values[key] = property.default;
685
+ continue;
686
+ }
687
+ if ("const" in property) {
688
+ values[key] = property.const;
689
+ continue;
690
+ }
691
+ if (required.includes(key) && Array.isArray(property.enum) && property.enum.length > 0) {
692
+ values[key] = property.enum[0];
693
+ continue;
694
+ }
695
+ if (latestUserInput
696
+ && !values[key]
697
+ && /(?:query|question|prompt|input|text)/iu.test(`${key} ${typeof property.description === "string" ? property.description : ""}`)) {
698
+ values[key] = latestUserInput;
699
+ }
692
700
  }
693
- return {};
701
+ return values;
694
702
  }
695
- function buildFallbackEvidenceToolCall(input, tools) {
703
+ function buildFallbackEvidenceToolCall(input, tools, rawText = "") {
696
704
  if (!hasPriorToolResultForToolName(input, "write_todos") && !hasPriorToolResultForToolName(input, "tool_call_write_todos")) {
697
705
  return null;
698
706
  }
699
- const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
707
+ const evidenceToolName = selectFallbackEvidenceToolName(tools, rawText);
700
708
  if (!evidenceToolName) {
701
709
  return null;
702
710
  }
703
- const boundToolName = tools
704
- .map((tool) => readBoundToolName(tool))
705
- .find((name) => normalizeDomainToolName(name) === evidenceToolName);
706
- if (!boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
711
+ const boundTool = tools.find((tool) => normalizeDomainToolName(readBoundToolName(tool)) === evidenceToolName);
712
+ const boundToolName = readBoundToolName(boundTool);
713
+ if (!boundTool || !boundToolName || hasPriorToolResultForToolName(input, boundToolName) || hasPriorToolResultForToolName(input, evidenceToolName)) {
707
714
  return null;
708
715
  }
709
716
  return new AIMessage({
@@ -711,7 +718,7 @@ function buildFallbackEvidenceToolCall(input, tools) {
711
718
  tool_calls: [{
712
719
  id: `fallback-evidence-${Math.random().toString(36).slice(2, 10)}`,
713
720
  name: boundToolName,
714
- args: buildFallbackEvidenceToolArgs(evidenceToolName, input),
721
+ args: buildFallbackEvidenceToolArgs(boundTool, readLatestUserContent(input)),
715
722
  type: "tool_call",
716
723
  }],
717
724
  });
@@ -725,7 +732,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
725
732
  if (!planningToolName) {
726
733
  return null;
727
734
  }
728
- const evidenceToolName = selectFallbackEvidenceToolName(input, tools);
735
+ const evidenceToolName = selectFallbackEvidenceToolName(tools, prompt);
729
736
  if (!evidenceToolName) {
730
737
  return null;
731
738
  }
@@ -735,7 +742,7 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
735
742
  if (!hasEvidenceResult) {
736
743
  return null;
737
744
  }
738
- const todos = buildToolAwareFallbackTodoContents(input, tools).map((content) => ({
745
+ const todos = buildToolAwareFallbackTodoContents(tools, prompt).map((content) => ({
739
746
  content,
740
747
  status: "completed",
741
748
  }));
@@ -749,6 +756,33 @@ function buildFallbackTodoCompletionToolCall(input, tools) {
749
756
  }],
750
757
  });
751
758
  }
759
+ function parsedToolCallCompletesTodoPlan(toolCall) {
760
+ if (!isTodoPlanningToolName(toolCall.name)) {
761
+ return false;
762
+ }
763
+ const todos = toolCall.args.todos;
764
+ if (!Array.isArray(todos) || todos.length === 0) {
765
+ return false;
766
+ }
767
+ return todos.every((todo) => typeof todo === "object"
768
+ && todo !== null
769
+ && todo.status === "completed");
770
+ }
771
+ function normalizeInitialTodoPlanToolCall(toolCall) {
772
+ if (!parsedToolCallCompletesTodoPlan(toolCall)) {
773
+ return toolCall;
774
+ }
775
+ const todos = toolCall.args.todos.map((todo, index) => typeof todo === "object" && todo !== null && !Array.isArray(todo)
776
+ ? { ...todo, status: index === 0 ? "in_progress" : "pending" }
777
+ : todo);
778
+ return {
779
+ name: toolCall.name,
780
+ args: {
781
+ ...toolCall.args,
782
+ todos,
783
+ },
784
+ };
785
+ }
752
786
  function formatBoundToolInstruction(tool) {
753
787
  if (typeof tool !== "object" || tool === null) {
754
788
  return null;
@@ -805,6 +839,15 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
805
839
  return async (input, config) => {
806
840
  const effectiveBoundTools = selectPlanningToolsForTurn(input, boundTools);
807
841
  const forcePlanningToolCall = shouldLimitToolsToPlanning(input);
842
+ if (options.settleCompletedToolResults === true
843
+ && !forcePlanningToolCall
844
+ && effectiveBoundTools.length > 0
845
+ && hasAnyPriorToolResult(input)
846
+ && hasPriorNonPlanningToolCall(input)) {
847
+ return new AIMessage({
848
+ content: readLatestToolResultContent(input) ?? "",
849
+ });
850
+ }
808
851
  const rawResult = await target.invoke(effectiveBoundTools.length > 0
809
852
  ? withPromptedJsonToolPrompt(input, effectiveBoundTools, { ...options, forcePlanningToolCall })
810
853
  : input, config);
@@ -825,22 +868,32 @@ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {
825
868
  if (fallbackCompletionToolCall) {
826
869
  return fallbackCompletionToolCall;
827
870
  }
828
- const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools);
871
+ const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
829
872
  if (fallbackToolCall) {
830
873
  return fallbackToolCall;
831
874
  }
832
875
  }
833
876
  return rawResult;
834
877
  }
835
- if (hasPriorToolResultForToolName(input, parsedToolCall.name)) {
878
+ const effectiveParsedToolCall = forcePlanningToolCall
879
+ ? normalizeInitialTodoPlanToolCall(parsedToolCall)
880
+ : parsedToolCall;
881
+ if (parsedToolCallCompletesTodoPlan(effectiveParsedToolCall)
882
+ && !hasPriorNonPlanningToolResult(input, effectiveBoundTools)) {
883
+ const fallbackToolCall = buildFallbackEvidenceToolCall(input, effectiveBoundTools, text);
884
+ if (fallbackToolCall) {
885
+ return fallbackToolCall;
886
+ }
887
+ }
888
+ if (hasPriorToolResultForToolName(input, effectiveParsedToolCall.name) || hasAnyPriorToolResult(input)) {
836
889
  return rawResult;
837
890
  }
838
891
  return new AIMessage({
839
892
  content: "",
840
893
  tool_calls: [{
841
894
  id: `tool-${Math.random().toString(36).slice(2, 10)}`,
842
- name: parsedToolCall.name,
843
- args: parsedToolCall.args,
895
+ name: effectiveParsedToolCall.name,
896
+ args: effectiveParsedToolCall.args,
844
897
  type: "tool_call",
845
898
  }],
846
899
  });
@@ -916,7 +969,10 @@ export async function createResolvedModel(model, modelResolver) {
916
969
  const { toolCallingMode, ...init } = model.init ?? {};
917
970
  const resolved = new ChatOllama({ model: model.model, ...init });
918
971
  if (toolCallingMode === "prompted-json") {
919
- return createPromptedJsonToolBindableModel(resolved, [], { suppressThinking: init.think === false });
972
+ return createPromptedJsonToolBindableModel(resolved, [], {
973
+ settleCompletedToolResults: true,
974
+ suppressThinking: init.think === false,
975
+ });
920
976
  }
921
977
  return createProviderToolMessageCompatModel(resolved);
922
978
  }
@@ -927,7 +983,10 @@ export async function createResolvedModel(model, modelResolver) {
927
983
  ...normalizeOpenAICompatibleInit(init),
928
984
  });
929
985
  if (toolCallingMode === "prompted-json") {
930
- return createPromptedJsonToolBindableModel(resolved);
986
+ return createPromptedJsonToolBindableModel(resolved, [], {
987
+ settleCompletedToolResults: true,
988
+ suppressThinking: true,
989
+ });
931
990
  }
932
991
  return createProviderToolMessageCompatModel(resolved);
933
992
  }
@@ -36,13 +36,16 @@ function shouldSuppressVisibleToolCallText(value) {
36
36
  if (/^(?:model_request|tool_call|call_tool)\b/iu.test(trimmed)) {
37
37
  return true;
38
38
  }
39
+ if (/^(?:name|tool_call_id)\s*=/iu.test(trimmed)) {
40
+ return true;
41
+ }
39
42
  if (/^(?:we\s+need\s+to|so\s+next\s+step\b)/iu.test(trimmed)) {
40
43
  return true;
41
44
  }
42
45
  if (/\b(?:must|need|needs|should|will)\s+(?:now\s+)?(?:call|use|run|produce)\s+[A-Za-z_][A-Za-z0-9_]*\b/iu.test(trimmed)) {
43
46
  return true;
44
47
  }
45
- if (/^\{\s*"(?:name|arguments|todos|symbol|query|market|count)"\s*:/iu.test(trimmed)) {
48
+ if (/^\{\s*"(?:name|arguments|args|argv|todos|symbol|query|market|count|stdout|stderr|exitCode)"\s*:/iu.test(trimmed)) {
46
49
  return true;
47
50
  }
48
51
  try {
@@ -55,7 +58,12 @@ function shouldSuppressVisibleToolCallText(value) {
55
58
  || "market" in parsed
56
59
  || "count" in parsed
57
60
  || "arguments" in parsed
58
- || "name" in parsed)) {
61
+ || "args" in parsed
62
+ || "argv" in parsed
63
+ || "name" in parsed
64
+ || "stdout" in parsed
65
+ || "stderr" in parsed
66
+ || "exitCode" in parsed)) {
59
67
  return true;
60
68
  }
61
69
  }
@@ -71,6 +79,63 @@ function shouldSuppressVisibleToolCallText(value) {
71
79
  }
72
80
  return salvageFunctionLikeToolCall(prefixedToolCallMatch[1]) !== null;
73
81
  }
82
+ function stripVisibleToolCallResidue(value) {
83
+ const lines = value.split(/\r?\n/);
84
+ let changed = false;
85
+ const kept = lines.filter((line) => {
86
+ if (shouldSuppressVisibleToolCallText(line)) {
87
+ changed = true;
88
+ return false;
89
+ }
90
+ return true;
91
+ });
92
+ if (changed) {
93
+ return kept.join("\n").trim();
94
+ }
95
+ const trimmedStart = value.trimStart();
96
+ if (!trimmedStart.startsWith("{")) {
97
+ return value;
98
+ }
99
+ const parsedPrefix = extractLeadingJsonObject(trimmedStart);
100
+ if (!parsedPrefix || !shouldSuppressVisibleToolCallText(parsedPrefix.json)) {
101
+ return value;
102
+ }
103
+ return parsedPrefix.rest.trimStart();
104
+ }
105
+ function extractLeadingJsonObject(value) {
106
+ let depth = 0;
107
+ let inString = false;
108
+ let escaped = false;
109
+ for (let index = 0; index < value.length; index += 1) {
110
+ const char = value[index];
111
+ if (escaped) {
112
+ escaped = false;
113
+ continue;
114
+ }
115
+ if (char === "\\") {
116
+ escaped = inString;
117
+ continue;
118
+ }
119
+ if (char === "\"") {
120
+ inString = !inString;
121
+ continue;
122
+ }
123
+ if (inString) {
124
+ continue;
125
+ }
126
+ if (char === "{") {
127
+ depth += 1;
128
+ continue;
129
+ }
130
+ if (char === "}") {
131
+ depth -= 1;
132
+ if (depth === 0) {
133
+ return { json: value.slice(0, index + 1), rest: value.slice(index + 1) };
134
+ }
135
+ }
136
+ }
137
+ return null;
138
+ }
74
139
  function readSummaryCounts(summary) {
75
140
  if (typeof summary !== "object" || summary === null) {
76
141
  return null;
@@ -375,7 +440,7 @@ export function projectRuntimeStreamEvent(params) {
375
440
  const allowVisibleContent = isRootVisibleEvent && state.openTaskDelegations === 0;
376
441
  const allowStreamedVisibleContent = allowVisibleContent && !state.emittedToolResult && !state.emittedToolError;
377
442
  if (allowVisibleStreamDeltas && allowStreamedVisibleContent) {
378
- const visibleStreamOutput = extractVisibleStreamOutput(event);
443
+ const visibleStreamOutput = stripVisibleToolCallResidue(extractVisibleStreamOutput(event));
379
444
  if (visibleStreamOutput && !shouldSuppressVisibleToolCallText(visibleStreamOutput)) {
380
445
  const nextOutput = computeIncrementalOutput(state.emittedOutput, visibleStreamOutput);
381
446
  state.emittedOutput = nextOutput.accumulated;
@@ -385,7 +450,7 @@ export function projectRuntimeStreamEvent(params) {
385
450
  }
386
451
  }
387
452
  if (includeStateStreamOutput && allowVisibleContent) {
388
- const stateStreamOutput = extractStateStreamOutput(event);
453
+ const stateStreamOutput = stripVisibleToolCallResidue(extractStateStreamOutput(event));
389
454
  if (stateStreamOutput && !shouldSuppressVisibleToolCallText(stateStreamOutput)) {
390
455
  const nextOutput = computeIncrementalOutput(state.emittedOutput, stateStreamOutput);
391
456
  state.emittedOutput = nextOutput.accumulated;
@@ -456,7 +521,7 @@ export function projectRuntimeStreamEvent(params) {
456
521
  state.lastCompletedTaskDelegationFindings = "";
457
522
  }
458
523
  }
459
- const output = allowVisibleContent ? extractTerminalStreamOutput(event) : "";
524
+ const output = allowVisibleContent ? stripVisibleToolCallResidue(extractTerminalStreamOutput(event)) : "";
460
525
  if (!allowVisibleContent) {
461
526
  const delegatedTerminalOutput = extractTerminalStreamOutput(event);
462
527
  if (delegatedTerminalOutput) {
@@ -1,5 +1,6 @@
1
1
  import { interrupt } from "@langchain/langgraph";
2
2
  import path from "node:path";
3
+ import { validateToolGatewayInput } from "../../harness/tool-gateway/index.js";
3
4
  const DEDUPED_REMOTE_TOOL_NAMES = new Set(["builtin.fetch_url", "builtin.web_search"]);
4
5
  const PATH_LIKE_INPUT_KEYS = new Set(["path", "root", "dir", "directory", "cwd"]);
5
6
  const ROOT_SCOPING_INPUT_KEYS = new Set(["root", "dir", "directory", "cwd"]);
@@ -169,8 +170,17 @@ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, binding, i
169
170
  }
170
171
  const target = resolvedTool;
171
172
  const runWithApproval = async (input, config, invokeOriginal) => {
173
+ const gateway = validateToolGatewayInput({
174
+ toolName: compiledTool.name,
175
+ schema: target.schema,
176
+ args: input,
177
+ requiresApproval: true,
178
+ });
179
+ if (!gateway.ok) {
180
+ return gateway.error;
181
+ }
172
182
  if (decisionMode === "auto-approve") {
173
- return invokeOriginal(input, config);
183
+ return invokeOriginal(gateway.input, config);
174
184
  }
175
185
  if (decisionMode === "auto-reject" || decisionMode === "deny-and-continue") {
176
186
  return "Tool execution denied by runtime policy.";
@@ -179,11 +189,11 @@ export function wrapToolForHumanInTheLoop(resolvedTool, compiledTool, binding, i
179
189
  toolName: compiledTool.name,
180
190
  toolId: compiledTool.id,
181
191
  allowedDecisions: compiledTool.hitl?.allow ?? ["approve", "edit", "reject"],
182
- inputPreview: toInputPreview(input),
192
+ inputPreview: toInputPreview(gateway.input),
183
193
  decisionMode,
184
194
  ...(toolApprovalReason(compiledTool) ? { approvalReason: toolApprovalReason(compiledTool) } : {}),
185
195
  });
186
- const approvedInput = resolveApprovedInput(input, resumed);
196
+ const approvedInput = resolveApprovedInput(gateway.input, resumed);
187
197
  if (approvedInput === Symbol.for("agent-harness.hitl.reject")) {
188
198
  return "Tool execution rejected by human reviewer.";
189
199
  }
@@ -295,13 +305,46 @@ export function wrapToolForExecution(resolvedTool, compiledTool, binding, interr
295
305
  get(obj, prop, receiver) {
296
306
  const value = Reflect.get(obj, prop, receiver);
297
307
  if (prop === "invoke" && typeof value === "function") {
298
- return (input, config) => obj.invoke(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
308
+ return (input, config) => {
309
+ const gateway = validateToolGatewayInput({
310
+ toolName: compiledTool.name,
311
+ schema: obj.schema,
312
+ args: input,
313
+ requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
314
+ });
315
+ if (!gateway.ok) {
316
+ return gateway.error;
317
+ }
318
+ return obj.invoke(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
319
+ };
299
320
  }
300
321
  if (prop === "call" && typeof value === "function") {
301
- return (input, config) => obj.call(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
322
+ return (input, config) => {
323
+ const gateway = validateToolGatewayInput({
324
+ toolName: compiledTool.name,
325
+ schema: obj.schema,
326
+ args: input,
327
+ requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
328
+ });
329
+ if (!gateway.ok) {
330
+ return gateway.error;
331
+ }
332
+ return obj.call(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
333
+ };
302
334
  }
303
335
  if (prop === "func" && typeof value === "function") {
304
- return (input, config) => obj.func(guardWorkspaceBoundToolInput(input, compiledTool, binding), config);
336
+ return (input, config) => {
337
+ const gateway = validateToolGatewayInput({
338
+ toolName: compiledTool.name,
339
+ schema: obj.schema,
340
+ args: input,
341
+ requiresApproval: resolveToolApprovalDecisionMode(compiledTool, binding) !== "none",
342
+ });
343
+ if (!gateway.ok) {
344
+ return gateway.error;
345
+ }
346
+ return obj.func(guardWorkspaceBoundToolInput(gateway.input, compiledTool, binding), config);
347
+ };
305
348
  }
306
349
  return value;
307
350
  },
@@ -38,6 +38,8 @@ export declare class AgentRuntimeAdapter {
38
38
  private invokeBuiltinTaskTool;
39
39
  private resolveBuiltinMiddlewareTools;
40
40
  private materializeProviderAliasBuiltinTools;
41
+ private shouldExposeBuiltinToolsToModel;
42
+ private resolveEffectiveModelExposedBuiltins;
41
43
  private materializeAutomaticSummarizationMiddleware;
42
44
  private resolveLangChainRuntimeExtensionMiddleware;
43
45
  private resolveMiddleware;