chainlesschain 0.45.11 → 0.45.19

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.
Files changed (81) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/AppLayout-B00RARl2.js +1 -0
  3. package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +1 -0
  4. package/src/assets/web-panel/assets/{Chat-5f__rMCR.js → Chat-DXtvKoM0.js} +1 -1
  5. package/src/assets/web-panel/assets/{Cron-C4mrNC4c.js → Cron-BJ4ODHOy.js} +1 -1
  6. package/src/assets/web-panel/assets/Dashboard-3iIpp3zd.js +3 -0
  7. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +1 -0
  8. package/src/assets/web-panel/assets/{Logs-CC_Zuh66.js → Logs-CSeKZEG_.js} +1 -1
  9. package/src/assets/web-panel/assets/{McpTools-B15GiN3u.js → McpTools-BYQAK11r.js} +2 -2
  10. package/src/assets/web-panel/assets/{Memory-Dbd7oLOH.js → Memory-gkUAPyuZ.js} +2 -2
  11. package/src/assets/web-panel/assets/{Notes-CEkc49fY.js → Notes-bjNrQgAo.js} +1 -1
  12. package/src/assets/web-panel/assets/{Providers-CjyPHW00.js → Providers-Dbf57Tbv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Services-XFzHMRRd.js → Services-CS0oMdxh.js} +1 -1
  14. package/src/assets/web-panel/assets/{Skills-D8oxmB3U.js → Skills-B2fgruv8.js} +1 -1
  15. package/src/assets/web-panel/assets/Tasks-BJjN_YEm.css +1 -0
  16. package/src/assets/web-panel/assets/Tasks-qULws8pc.js +1 -0
  17. package/src/assets/web-panel/assets/{antd-ChLPLhSn.js → antd-CJSBocer.js} +1 -1
  18. package/src/assets/web-panel/assets/chat-DnH09sSR.js +1 -0
  19. package/src/assets/web-panel/assets/{index-DQ5xXK7O.js → index-CF2CqPYX.js} +2 -2
  20. package/src/assets/web-panel/assets/{markdown-DtbPhnFe.js → markdown-Bo5cVN4u.js} +1 -1
  21. package/src/assets/web-panel/assets/ws-DjelKkD6.js +1 -0
  22. package/src/assets/web-panel/index.html +2 -2
  23. package/src/commands/agent.js +7 -8
  24. package/src/commands/chat.js +9 -11
  25. package/src/commands/serve.js +11 -106
  26. package/src/commands/session.js +185 -18
  27. package/src/commands/ui.js +10 -151
  28. package/src/gateways/repl/agent-repl.js +1 -0
  29. package/src/gateways/repl/chat-repl.js +1 -0
  30. package/src/gateways/ui/web-ui-server.js +1 -0
  31. package/src/gateways/ws/action-protocol.js +83 -0
  32. package/src/gateways/ws/message-dispatcher.js +73 -0
  33. package/src/gateways/ws/session-protocol.js +396 -0
  34. package/src/gateways/ws/task-protocol.js +55 -0
  35. package/src/gateways/ws/worktree-protocol.js +315 -0
  36. package/src/gateways/ws/ws-server.js +4 -0
  37. package/src/gateways/ws/ws-session-gateway.js +1 -0
  38. package/src/harness/background-task-manager.js +506 -0
  39. package/src/harness/background-task-worker.js +48 -0
  40. package/src/harness/compression-telemetry.js +214 -0
  41. package/src/harness/feature-flags.js +157 -0
  42. package/src/harness/jsonl-session-store.js +452 -0
  43. package/src/harness/prompt-compressor.js +416 -0
  44. package/src/harness/worktree-isolator.js +845 -0
  45. package/src/lib/agent-core.js +246 -45
  46. package/src/lib/background-task-manager.js +1 -305
  47. package/src/lib/background-task-worker.js +1 -50
  48. package/src/lib/compression-telemetry.js +5 -0
  49. package/src/lib/feature-flags.js +7 -182
  50. package/src/lib/interaction-adapter.js +32 -6
  51. package/src/lib/jsonl-session-store.js +21 -237
  52. package/src/lib/prompt-compressor.js +10 -351
  53. package/src/lib/sub-agent-context.js +91 -0
  54. package/src/lib/worktree-isolator.js +13 -231
  55. package/src/lib/ws-agent-handler.js +1 -0
  56. package/src/lib/ws-server.js +155 -359
  57. package/src/lib/ws-session-manager.js +82 -1
  58. package/src/repl/agent-repl.js +114 -32
  59. package/src/runtime/agent-runtime.js +417 -0
  60. package/src/runtime/contracts/agent-turn.js +11 -0
  61. package/src/runtime/contracts/session-record.js +31 -0
  62. package/src/runtime/contracts/task-record.js +18 -0
  63. package/src/runtime/contracts/telemetry-record.js +23 -0
  64. package/src/runtime/contracts/worktree-record.js +14 -0
  65. package/src/runtime/index.js +13 -0
  66. package/src/runtime/policies/agent-policy.js +45 -0
  67. package/src/runtime/runtime-context.js +14 -0
  68. package/src/runtime/runtime-events.js +37 -0
  69. package/src/runtime/runtime-factory.js +50 -0
  70. package/src/tools/index.js +22 -0
  71. package/src/tools/legacy-agent-tools.js +171 -0
  72. package/src/tools/registry.js +141 -0
  73. package/src/tools/tool-context.js +28 -0
  74. package/src/tools/tool-permissions.js +28 -0
  75. package/src/tools/tool-telemetry.js +39 -0
  76. package/src/assets/web-panel/assets/AppLayout-19ZC8w11.js +0 -1
  77. package/src/assets/web-panel/assets/AppLayout-CjgO-ML6.css +0 -1
  78. package/src/assets/web-panel/assets/Dashboard-CRFnDUFh.css +0 -1
  79. package/src/assets/web-panel/assets/Dashboard-DsjXpZor.js +0 -3
  80. package/src/assets/web-panel/assets/chat-C_hu-qNs.js +0 -1
  81. package/src/assets/web-panel/assets/ws-DwluTqT5.js +0 -1
@@ -24,6 +24,13 @@ import { executeHooks, HookEvents } from "./hook-manager.js";
24
24
  import { detectPython } from "./cli-anything-bridge.js";
25
25
  import { findProjectRoot, loadProjectConfig } from "./project-detector.js";
26
26
  import { SubAgentContext } from "./sub-agent-context.js";
27
+ import {
28
+ createLegacyAgentToolRegistry,
29
+ getRuntimeToolDescriptor,
30
+ } from "../tools/legacy-agent-tools.js";
31
+ import { createToolContext } from "../tools/tool-context.js";
32
+ import { createToolTelemetryRecord } from "../tools/tool-telemetry.js";
33
+ import { DEFAULT_TOOL_DESCRIPTORS } from "../tools/registry.js";
27
34
 
28
35
  // ─── Tool definitions ────────────────────────────────────────────────────
29
36
 
@@ -249,6 +256,60 @@ export const AGENT_TOOLS = [
249
256
  },
250
257
  ];
251
258
 
259
+ const STATIC_AGENT_TOOL_NAMES = new Set(
260
+ AGENT_TOOLS.map((tool) => tool.function.name),
261
+ );
262
+
263
+ export const AGENT_TOOL_REGISTRY = createLegacyAgentToolRegistry(AGENT_TOOLS);
264
+ const DEFAULT_TOOL_DESCRIPTOR_MAP = new Map(
265
+ DEFAULT_TOOL_DESCRIPTORS.map((descriptor) => [descriptor.name, descriptor]),
266
+ );
267
+
268
+ function mergeToolDefinitions(baseTools = [], extraTools = []) {
269
+ const merged = new Map();
270
+
271
+ for (const tool of [...baseTools, ...extraTools]) {
272
+ const name = tool?.function?.name;
273
+ if (!name) continue;
274
+ merged.set(name, tool);
275
+ }
276
+
277
+ return Array.from(merged.values());
278
+ }
279
+
280
+ export function getAgentToolDefinitions({
281
+ names = null,
282
+ disabledTools = [],
283
+ extraTools = [],
284
+ } = {}) {
285
+ const allowedNames =
286
+ Array.isArray(names) && names.length > 0 ? new Set(names) : null;
287
+ const disabledNames = new Set(
288
+ Array.isArray(disabledTools) ? disabledTools : [],
289
+ );
290
+ const allTools = mergeToolDefinitions(
291
+ AGENT_TOOLS,
292
+ Array.isArray(extraTools) ? extraTools : [],
293
+ );
294
+
295
+ return allTools.filter((tool) => {
296
+ const name = tool?.function?.name;
297
+ if (!name) return false;
298
+ if (allowedNames && !allowedNames.has(name)) return false;
299
+ if (disabledNames.has(name)) return false;
300
+ return true;
301
+ });
302
+ }
303
+
304
+ export function getAgentToolDescriptors(options = {}) {
305
+ const allowedNames = new Set(
306
+ getAgentToolDefinitions(options).map((tool) => tool.function.name),
307
+ );
308
+ return AGENT_TOOL_REGISTRY.list({ enabledOnly: options.enabledOnly }).filter(
309
+ (descriptor) => allowedNames.has(descriptor.name),
310
+ );
311
+ }
312
+
252
313
  // ─── Shared skill loader ──────────────────────────────────────────────────
253
314
 
254
315
  const _defaultSkillLoader = new CLISkillLoader();
@@ -514,6 +575,12 @@ export async function executeTool(name, args, context = {}) {
514
575
  const hookDb = context.hookDb || null;
515
576
  const skillLoader = context.skillLoader || _defaultSkillLoader;
516
577
  const cwd = context.cwd || process.cwd();
578
+ const runtimeDescriptor = getRuntimeToolDescriptor(name);
579
+ const toolContext = createToolContext({
580
+ toolName: runtimeDescriptor?.name || name,
581
+ cwd,
582
+ metadata: { descriptor: runtimeDescriptor },
583
+ });
517
584
 
518
585
  // Persona toolsDisabled guard
519
586
  const persona = _loadProjectPersona(cwd);
@@ -523,9 +590,35 @@ export async function executeTool(name, args, context = {}) {
523
590
  };
524
591
  }
525
592
 
593
+ const toolPolicies =
594
+ context.hostManagedToolPolicy?.tools ||
595
+ context.hostManagedToolPolicy?.toolPolicies ||
596
+ null;
597
+ const hostToolPolicy =
598
+ toolPolicies && typeof toolPolicies === "object"
599
+ ? toolPolicies[name]
600
+ : null;
601
+ const isExternalHostTool =
602
+ hostToolPolicy && !STATIC_AGENT_TOOL_NAMES.has(name);
603
+ if (hostToolPolicy && hostToolPolicy.allowed === false) {
604
+ return {
605
+ error: `[Host Policy] Tool "${name}" is blocked by desktop host policy. ${hostToolPolicy.reason || "Desktop approval has not been synchronized yet."}`,
606
+ policy: {
607
+ decision: hostToolPolicy.decision || "blocked",
608
+ requiresPlanApproval: hostToolPolicy.requiresPlanApproval === true,
609
+ requiresConfirmation: hostToolPolicy.requiresConfirmation === true,
610
+ riskLevel: hostToolPolicy.riskLevel || null,
611
+ },
612
+ };
613
+ }
614
+
526
615
  // Plan mode: check if tool is allowed
527
616
  const planManager = getPlanModeManager();
528
- if (planManager.isActive() && !planManager.isToolAllowed(name)) {
617
+ if (
618
+ planManager.isActive() &&
619
+ !planManager.isToolAllowed(name) &&
620
+ !(isExternalHostTool && hostToolPolicy?.allowed === true)
621
+ ) {
529
622
  planManager.addPlanItem({
530
623
  title: `${name}: ${formatToolArgs(name, args)}`,
531
624
  tool: name,
@@ -549,18 +642,23 @@ export async function executeTool(name, args, context = {}) {
549
642
  tool: name,
550
643
  args,
551
644
  timestamp: new Date().toISOString(),
645
+ descriptor: runtimeDescriptor,
646
+ context: toolContext,
552
647
  });
553
648
  } catch (_err) {
554
649
  // Hook failure should not block tool execution
555
650
  }
556
651
  }
557
652
 
653
+ const startTime = Date.now();
558
654
  let toolResult;
559
655
  try {
560
656
  toolResult = await executeToolInner(name, args, {
561
657
  skillLoader,
562
658
  cwd,
563
659
  parentMessages: context.parentMessages,
660
+ interaction: context.interaction,
661
+ hostManagedToolPolicy: context.hostManagedToolPolicy || null,
564
662
  });
565
663
  } catch (err) {
566
664
  if (hookDb) {
@@ -577,6 +675,19 @@ export async function executeTool(name, args, context = {}) {
577
675
  throw err;
578
676
  }
579
677
 
678
+ const durationMs = Date.now() - startTime;
679
+ const status = toolResult?.error ? "error" : "completed";
680
+ const telemetryRecord = createToolTelemetryRecord({
681
+ descriptor: runtimeDescriptor,
682
+ status,
683
+ durationMs,
684
+ sessionId: context.sessionId || null,
685
+ metadata: { args },
686
+ });
687
+ if (toolResult && typeof toolResult === "object") {
688
+ toolResult.toolTelemetryRecord = telemetryRecord;
689
+ }
690
+
580
691
  // PostToolUse hook
581
692
  if (hookDb) {
582
693
  try {
@@ -587,6 +698,8 @@ export async function executeTool(name, args, context = {}) {
587
698
  typeof toolResult === "object"
588
699
  ? JSON.stringify(toolResult).substring(0, 500)
589
700
  : String(toolResult).substring(0, 500),
701
+ descriptor: runtimeDescriptor,
702
+ context: toolContext,
590
703
  });
591
704
  } catch (_err) {
592
705
  // Non-critical
@@ -602,22 +715,59 @@ export async function executeTool(name, args, context = {}) {
602
715
  async function executeToolInner(
603
716
  name,
604
717
  args,
605
- { skillLoader, cwd, parentMessages },
718
+ { skillLoader, cwd, parentMessages, interaction, hostManagedToolPolicy },
606
719
  ) {
720
+ const runtimeDescriptor = getRuntimeToolDescriptor(name);
721
+ const hostToolPolicies =
722
+ hostManagedToolPolicy?.tools || hostManagedToolPolicy?.toolPolicies || null;
723
+ const hostToolPolicy =
724
+ hostToolPolicies && typeof hostToolPolicies === "object"
725
+ ? hostToolPolicies[name]
726
+ : null;
727
+ const hostToolDefinition = Array.isArray(
728
+ hostManagedToolPolicy?.toolDefinitions,
729
+ )
730
+ ? hostManagedToolPolicy.toolDefinitions.find(
731
+ (tool) => tool?.function?.name === name,
732
+ ) || null
733
+ : null;
734
+ const buildPayload = (descriptor) =>
735
+ descriptor
736
+ ? {
737
+ name: descriptor.name,
738
+ kind: descriptor.kind || descriptor.category || descriptor.source,
739
+ category: descriptor.category,
740
+ }
741
+ : null;
742
+ const descriptorPayload = buildPayload(runtimeDescriptor);
743
+ const attachDescriptor = (payload, overrideDescriptor = null) => {
744
+ const descriptor = buildPayload(overrideDescriptor || runtimeDescriptor);
745
+ return descriptor ? { ...payload, toolDescriptor: descriptor } : payload;
746
+ };
747
+ const resolveShellDescriptor = (command) => {
748
+ const trimmed = (command || "").trim();
749
+ if (!trimmed) return null;
750
+ const parts = trimmed.split(/\s+/);
751
+ if (parts[0] === "git") return DEFAULT_TOOL_DESCRIPTOR_MAP.get("git");
752
+ if (parts[0] === "mcp" || parts.includes("mcp")) {
753
+ return DEFAULT_TOOL_DESCRIPTOR_MAP.get("mcp");
754
+ }
755
+ return DEFAULT_TOOL_DESCRIPTOR_MAP.get("shell");
756
+ };
607
757
  switch (name) {
608
758
  case "read_file": {
609
759
  const filePath = path.resolve(cwd, args.path);
610
760
  if (!fs.existsSync(filePath)) {
611
- return { error: `File not found: ${filePath}` };
761
+ return attachDescriptor({ error: `File not found: ${filePath}` });
612
762
  }
613
763
  const content = fs.readFileSync(filePath, "utf8");
614
764
  if (content.length > 50000) {
615
- return {
765
+ return attachDescriptor({
616
766
  content: content.substring(0, 50000) + "\n...(truncated)",
617
767
  size: content.length,
618
- };
768
+ });
619
769
  }
620
- return { content };
770
+ return attachDescriptor({ content });
621
771
  }
622
772
 
623
773
  case "write_file": {
@@ -627,21 +777,25 @@ async function executeToolInner(
627
777
  fs.mkdirSync(dir, { recursive: true });
628
778
  }
629
779
  fs.writeFileSync(filePath, args.content, "utf8");
630
- return { success: true, path: filePath, size: args.content.length };
780
+ return attachDescriptor({
781
+ success: true,
782
+ path: filePath,
783
+ size: args.content.length,
784
+ });
631
785
  }
632
786
 
633
787
  case "edit_file": {
634
788
  const filePath = path.resolve(cwd, args.path);
635
789
  if (!fs.existsSync(filePath)) {
636
- return { error: `File not found: ${filePath}` };
790
+ return attachDescriptor({ error: `File not found: ${filePath}` });
637
791
  }
638
792
  const content = fs.readFileSync(filePath, "utf8");
639
793
  if (!content.includes(args.old_string)) {
640
- return { error: "old_string not found in file" };
794
+ return attachDescriptor({ error: "old_string not found in file" });
641
795
  }
642
796
  const newContent = content.replace(args.old_string, args.new_string);
643
797
  fs.writeFileSync(filePath, newContent, "utf8");
644
- return { success: true, path: filePath };
798
+ return attachDescriptor({ success: true, path: filePath });
645
799
  }
646
800
 
647
801
  case "run_shell": {
@@ -652,22 +806,34 @@ async function executeToolInner(
652
806
  timeout: 60000,
653
807
  maxBuffer: 1024 * 1024,
654
808
  });
655
- return { stdout: output.substring(0, 30000) };
809
+ const override = resolveShellDescriptor(args.command);
810
+ return attachDescriptor(
811
+ {
812
+ stdout: output.substring(0, 30000),
813
+ },
814
+ override || runtimeDescriptor,
815
+ );
656
816
  } catch (err) {
657
- return {
658
- error: err.message.substring(0, 2000),
659
- stderr: (err.stderr || "").substring(0, 2000),
660
- exitCode: err.status,
661
- };
817
+ const override = resolveShellDescriptor(args.command);
818
+ return attachDescriptor(
819
+ {
820
+ error: err.message.substring(0, 2000),
821
+ stderr: (err.stderr || "").substring(0, 2000),
822
+ exitCode: err.status,
823
+ },
824
+ override || runtimeDescriptor,
825
+ );
662
826
  }
663
827
  }
664
828
 
665
829
  case "run_code": {
666
- return _executeRunCode(args, cwd);
830
+ return attachDescriptor(await _executeRunCode(args, cwd));
667
831
  }
668
832
 
669
833
  case "spawn_sub_agent": {
670
- return _executeSpawnSubAgent(args, { skillLoader, cwd, parentMessages });
834
+ return attachDescriptor(
835
+ await _executeSpawnSubAgent(args, { skillLoader, cwd, parentMessages }),
836
+ );
671
837
  }
672
838
 
673
839
  case "search_files": {
@@ -683,7 +849,9 @@ async function executeToolInner(
683
849
  encoding: "utf8",
684
850
  timeout: 10000,
685
851
  });
686
- return { matches: output.trim().split("\n").slice(0, 20) };
852
+ return attachDescriptor({
853
+ matches: output.trim().split("\n").slice(0, 20),
854
+ });
687
855
  } else {
688
856
  const cmd =
689
857
  process.platform === "win32"
@@ -694,44 +862,47 @@ async function executeToolInner(
694
862
  encoding: "utf8",
695
863
  timeout: 10000,
696
864
  });
697
- return {
865
+ return attachDescriptor({
698
866
  files: output.trim().split("\n").filter(Boolean).slice(0, 20),
699
- };
867
+ });
700
868
  }
701
869
  } catch {
702
- return { files: [], message: "No matches found" };
870
+ return attachDescriptor({
871
+ files: [],
872
+ message: "No matches found",
873
+ });
703
874
  }
704
875
  }
705
876
 
706
877
  case "list_dir": {
707
878
  const dirPath = args.path ? path.resolve(cwd, args.path) : cwd;
708
879
  if (!fs.existsSync(dirPath)) {
709
- return { error: `Directory not found: ${dirPath}` };
880
+ return attachDescriptor({ error: `Directory not found: ${dirPath}` });
710
881
  }
711
882
  const entries = fs.readdirSync(dirPath, { withFileTypes: true });
712
- return {
883
+ return attachDescriptor({
713
884
  entries: entries.map((e) => ({
714
885
  name: e.name,
715
886
  type: e.isDirectory() ? "dir" : "file",
716
887
  })),
717
- };
888
+ });
718
889
  }
719
890
 
720
891
  case "run_skill": {
721
892
  const allSkills = skillLoader.getResolvedSkills();
722
893
  if (allSkills.length === 0) {
723
- return {
894
+ return attachDescriptor({
724
895
  error:
725
896
  "No skills found. Make sure you're in the ChainlessChain project root or have skills installed.",
726
- };
897
+ });
727
898
  }
728
899
  const match = allSkills.find(
729
900
  (s) => s.id === args.skill_name || s.dirName === args.skill_name,
730
901
  );
731
902
  if (!match || !match.hasHandler) {
732
- return {
903
+ return attachDescriptor({
733
904
  error: `Skill "${args.skill_name}" not found or has no handler. Use list_skills to see available skills.`,
734
- };
905
+ });
735
906
  }
736
907
 
737
908
  // Check if skill requests isolation (via SKILL.md frontmatter)
@@ -746,15 +917,17 @@ async function executeToolInner(
746
917
  });
747
918
  try {
748
919
  const result = await subCtx.run(args.input);
749
- return {
920
+ return attachDescriptor({
750
921
  success: true,
751
922
  isolated: true,
752
923
  skill: args.skill_name,
753
924
  summary: result.summary,
754
925
  toolsUsed: result.toolsUsed,
755
- };
926
+ });
756
927
  } catch (err) {
757
- return { error: `Isolated skill execution failed: ${err.message}` };
928
+ return attachDescriptor({
929
+ error: `Isolated skill execution failed: ${err.message}`,
930
+ });
758
931
  }
759
932
  }
760
933
 
@@ -775,16 +948,18 @@ async function executeToolInner(
775
948
  workspacePath: cwd,
776
949
  };
777
950
  const result = await handler.execute(task, taskContext, match);
778
- return result;
951
+ return attachDescriptor(result);
779
952
  } catch (err) {
780
- return { error: `Skill execution failed: ${err.message}` };
953
+ return attachDescriptor({
954
+ error: `Skill execution failed: ${err.message}`,
955
+ });
781
956
  }
782
957
  }
783
958
 
784
959
  case "list_skills": {
785
960
  let skills = skillLoader.getResolvedSkills();
786
961
  if (skills.length === 0) {
787
- return { error: "No skills found." };
962
+ return attachDescriptor({ error: "No skills found." });
788
963
  }
789
964
  if (args.category) {
790
965
  skills = skills.filter(
@@ -800,7 +975,7 @@ async function executeToolInner(
800
975
  s.category.toLowerCase().includes(q),
801
976
  );
802
977
  }
803
- return {
978
+ return attachDescriptor({
804
979
  count: skills.length,
805
980
  skills: skills.map((s) => ({
806
981
  id: s.id,
@@ -809,11 +984,38 @@ async function executeToolInner(
809
984
  hasHandler: s.hasHandler,
810
985
  description: (s.description || "").substring(0, 80),
811
986
  })),
812
- };
987
+ });
813
988
  }
814
989
 
815
990
  default:
816
- return { error: `Unknown tool: ${name}` };
991
+ if (
992
+ hostToolDefinition &&
993
+ interaction &&
994
+ typeof interaction.requestHostTool === "function"
995
+ ) {
996
+ const hostedResult = await interaction.requestHostTool(name, args);
997
+ if (hostedResult?.success === false) {
998
+ return attachDescriptor({
999
+ error:
1000
+ hostedResult.error || `Hosted tool execution failed: ${name}`,
1001
+ policy: hostToolPolicy || null,
1002
+ });
1003
+ }
1004
+
1005
+ if (hostedResult?.result && typeof hostedResult.result === "object") {
1006
+ return hostedResult.result;
1007
+ }
1008
+
1009
+ return attachDescriptor({
1010
+ result:
1011
+ hostedResult &&
1012
+ Object.prototype.hasOwnProperty.call(hostedResult, "result")
1013
+ ? hostedResult.result
1014
+ : hostedResult,
1015
+ });
1016
+ }
1017
+
1018
+ return attachDescriptor({ error: `Unknown tool: ${name}` });
817
1019
  }
818
1020
  }
819
1021
 
@@ -1128,14 +1330,11 @@ async function _executeSpawnSubAgent(args, ctx) {
1128
1330
  export async function chatWithTools(rawMessages, options) {
1129
1331
  const { provider, model, baseUrl, apiKey, contextEngine: ce } = options;
1130
1332
 
1131
- // Filter tools based on persona.toolsDisabled
1132
- let tools = AGENT_TOOLS;
1133
1333
  const persona = _loadProjectPersona(options.cwd);
1134
- if (persona?.toolsDisabled?.length > 0) {
1135
- tools = AGENT_TOOLS.filter(
1136
- (t) => !persona.toolsDisabled.includes(t.function.name),
1137
- );
1138
- }
1334
+ const tools = getAgentToolDefinitions({
1335
+ disabledTools: persona?.toolsDisabled,
1336
+ extraTools: options.hostManagedToolPolicy?.toolDefinitions || [],
1337
+ });
1139
1338
 
1140
1339
  const lastUserMsg = [...rawMessages].reverse().find((m) => m.role === "user");
1141
1340
  const messages = ce
@@ -1319,7 +1518,9 @@ export async function* agentLoop(messages, options) {
1319
1518
  hookDb: options.hookDb || null,
1320
1519
  skillLoader: options.skillLoader || _defaultSkillLoader,
1321
1520
  cwd: options.cwd || process.cwd(),
1521
+ hostManagedToolPolicy: options.hostManagedToolPolicy || null,
1322
1522
  parentMessages: messages, // pass parent messages for sub-agent auto-condensation
1523
+ interaction: options.interaction || null,
1323
1524
  };
1324
1525
 
1325
1526
  // ── Slot-filling phase ──────────────────────────────────────────────