@sammorrowdrums/mcpi-ext 0.2.1 → 1.0.0

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 (199) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +137 -47
  3. package/dist/code-mode/eligibility.d.ts +29 -4
  4. package/dist/code-mode/eligibility.js +56 -6
  5. package/dist/code-mode/executor.d.ts +19 -1
  6. package/dist/code-mode/executor.js +73 -8
  7. package/dist/code-mode/index.d.ts +54 -12
  8. package/dist/code-mode/index.js +157 -65
  9. package/dist/code-mode/isolated-vm.d.ts +65 -0
  10. package/dist/code-mode/isolated-vm.js +116 -0
  11. package/dist/code-mode/tools.d.ts +4 -1
  12. package/dist/code-mode/tools.js +7 -4
  13. package/dist/code-mode/type-hints.d.ts +2 -2
  14. package/dist/code-mode/type-hints.js +24 -8
  15. package/dist/docker-e2e.d.ts +0 -1
  16. package/dist/docker-e2e.js +0 -1
  17. package/dist/index.d.ts +9 -1
  18. package/dist/index.js +146 -67
  19. package/dist/mcp/call-tool-result.d.ts +10 -0
  20. package/dist/mcp/call-tool-result.js +70 -0
  21. package/dist/mcp/client-factory.d.ts +59 -0
  22. package/dist/mcp/client-factory.js +76 -0
  23. package/dist/mcp/client-manager.d.ts +61 -19
  24. package/dist/mcp/client-manager.js +223 -64
  25. package/dist/mcp/config-loader.d.ts +0 -1
  26. package/dist/mcp/config-loader.js +0 -1
  27. package/dist/mcp/config.d.ts +11 -1
  28. package/dist/mcp/config.js +15 -1
  29. package/dist/mcp/gateway-defaults.d.ts +14 -0
  30. package/dist/mcp/gateway-defaults.js +21 -0
  31. package/dist/mcp/host-approval.d.ts +16 -0
  32. package/dist/mcp/host-approval.js +19 -0
  33. package/dist/mcp/host-elicitation.d.ts +19 -0
  34. package/dist/mcp/host-elicitation.js +94 -0
  35. package/dist/mcp/index.d.ts +4 -1
  36. package/dist/mcp/index.js +4 -1
  37. package/dist/mcp/policy.d.ts +314 -0
  38. package/dist/mcp/policy.js +848 -0
  39. package/dist/routing/facilities.d.ts +109 -0
  40. package/dist/routing/facilities.js +175 -0
  41. package/dist/routing/format.d.ts +24 -0
  42. package/dist/routing/format.js +64 -0
  43. package/dist/routing/index.d.ts +4 -0
  44. package/dist/routing/index.js +4 -0
  45. package/dist/routing/seam.d.ts +35 -0
  46. package/dist/routing/seam.js +28 -0
  47. package/dist/routing/tripwire.d.ts +42 -0
  48. package/dist/routing/tripwire.js +80 -0
  49. package/dist/skills/discover.d.ts +5 -3
  50. package/dist/skills/discover.js +37 -12
  51. package/dist/skills/format.d.ts +4 -1
  52. package/dist/skills/format.js +8 -4
  53. package/dist/skills/index.d.ts +2 -2
  54. package/dist/skills/index.js +1 -1
  55. package/dist/skills/load-skill-tool.d.ts +23 -7
  56. package/dist/skills/load-skill-tool.js +112 -33
  57. package/dist/skills/mcp-tool-proxy.d.ts +4 -8
  58. package/dist/skills/mcp-tool-proxy.js +22 -97
  59. package/dist/skills/sep2640/client.d.ts +91 -0
  60. package/dist/skills/sep2640/client.js +160 -0
  61. package/dist/skills/sep2640/discover.d.ts +33 -0
  62. package/dist/skills/sep2640/discover.js +82 -0
  63. package/dist/skills/sep2640/index.d.ts +13 -0
  64. package/dist/skills/sep2640/index.js +13 -0
  65. package/dist/skills/sep2640/integrity.d.ts +58 -0
  66. package/dist/skills/sep2640/integrity.js +185 -0
  67. package/dist/skills/sep2640/load.d.ts +61 -0
  68. package/dist/skills/sep2640/load.js +90 -0
  69. package/dist/skills/sep2640/protocol.d.ts +146 -0
  70. package/dist/skills/sep2640/protocol.js +214 -0
  71. package/dist/skills/sep2640/spec.d.ts +76 -0
  72. package/dist/skills/sep2640/spec.js +88 -0
  73. package/dist/skills/skill-registry.d.ts +48 -2
  74. package/dist/skills/skill-registry.js +40 -3
  75. package/dist/tool-cli/bridge.d.ts +14 -0
  76. package/dist/tool-cli/bridge.js +196 -0
  77. package/dist/tool-cli/format.d.ts +17 -5
  78. package/dist/tool-cli/format.js +36 -20
  79. package/dist/tool-cli/index.d.ts +5 -2
  80. package/dist/tool-cli/index.js +3 -1
  81. package/dist/tool-cli/provider.d.ts +21 -0
  82. package/dist/tool-cli/provider.js +114 -0
  83. package/dist/tool-cli/startup.d.ts +22 -0
  84. package/dist/tool-cli/startup.js +47 -0
  85. package/package.json +78 -30
  86. package/dist/code-mode/code-mode-integration.test.d.ts +0 -2
  87. package/dist/code-mode/code-mode-integration.test.d.ts.map +0 -1
  88. package/dist/code-mode/code-mode-integration.test.js +0 -116
  89. package/dist/code-mode/code-mode-integration.test.js.map +0 -1
  90. package/dist/code-mode/eligibility.d.ts.map +0 -1
  91. package/dist/code-mode/eligibility.js.map +0 -1
  92. package/dist/code-mode/eligibility.test.d.ts +0 -2
  93. package/dist/code-mode/eligibility.test.d.ts.map +0 -1
  94. package/dist/code-mode/eligibility.test.js +0 -53
  95. package/dist/code-mode/eligibility.test.js.map +0 -1
  96. package/dist/code-mode/executor.d.ts.map +0 -1
  97. package/dist/code-mode/executor.js.map +0 -1
  98. package/dist/code-mode/executor.test.d.ts +0 -2
  99. package/dist/code-mode/executor.test.d.ts.map +0 -1
  100. package/dist/code-mode/executor.test.js +0 -130
  101. package/dist/code-mode/executor.test.js.map +0 -1
  102. package/dist/code-mode/index.d.ts.map +0 -1
  103. package/dist/code-mode/index.js.map +0 -1
  104. package/dist/code-mode/tools.d.ts.map +0 -1
  105. package/dist/code-mode/tools.js.map +0 -1
  106. package/dist/code-mode/type-hints.d.ts.map +0 -1
  107. package/dist/code-mode/type-hints.js.map +0 -1
  108. package/dist/code-mode/type-hints.test.d.ts +0 -2
  109. package/dist/code-mode/type-hints.test.d.ts.map +0 -1
  110. package/dist/code-mode/type-hints.test.js +0 -157
  111. package/dist/code-mode/type-hints.test.js.map +0 -1
  112. package/dist/docker-e2e.d.ts.map +0 -1
  113. package/dist/docker-e2e.js.map +0 -1
  114. package/dist/docker-e2e.test.d.ts +0 -2
  115. package/dist/docker-e2e.test.d.ts.map +0 -1
  116. package/dist/docker-e2e.test.js +0 -176
  117. package/dist/docker-e2e.test.js.map +0 -1
  118. package/dist/index.d.ts.map +0 -1
  119. package/dist/index.js.map +0 -1
  120. package/dist/index.test.d.ts +0 -2
  121. package/dist/index.test.d.ts.map +0 -1
  122. package/dist/index.test.js +0 -8
  123. package/dist/index.test.js.map +0 -1
  124. package/dist/mcp/client-manager.d.ts.map +0 -1
  125. package/dist/mcp/client-manager.js.map +0 -1
  126. package/dist/mcp/client-manager.test.d.ts +0 -2
  127. package/dist/mcp/client-manager.test.d.ts.map +0 -1
  128. package/dist/mcp/client-manager.test.js +0 -204
  129. package/dist/mcp/client-manager.test.js.map +0 -1
  130. package/dist/mcp/config-loader.d.ts.map +0 -1
  131. package/dist/mcp/config-loader.js.map +0 -1
  132. package/dist/mcp/config-loader.test.d.ts +0 -2
  133. package/dist/mcp/config-loader.test.d.ts.map +0 -1
  134. package/dist/mcp/config-loader.test.js +0 -93
  135. package/dist/mcp/config-loader.test.js.map +0 -1
  136. package/dist/mcp/config.d.ts.map +0 -1
  137. package/dist/mcp/config.js.map +0 -1
  138. package/dist/mcp/index.d.ts.map +0 -1
  139. package/dist/mcp/index.js.map +0 -1
  140. package/dist/skills/discover.d.ts.map +0 -1
  141. package/dist/skills/discover.js.map +0 -1
  142. package/dist/skills/format.d.ts.map +0 -1
  143. package/dist/skills/format.js.map +0 -1
  144. package/dist/skills/format.test.d.ts +0 -2
  145. package/dist/skills/format.test.d.ts.map +0 -1
  146. package/dist/skills/format.test.js +0 -63
  147. package/dist/skills/format.test.js.map +0 -1
  148. package/dist/skills/index.d.ts.map +0 -1
  149. package/dist/skills/index.js.map +0 -1
  150. package/dist/skills/load-skill-tool.d.ts.map +0 -1
  151. package/dist/skills/load-skill-tool.js.map +0 -1
  152. package/dist/skills/mcp-tool-proxy.d.ts.map +0 -1
  153. package/dist/skills/mcp-tool-proxy.js.map +0 -1
  154. package/dist/skills/skill-integration.test.d.ts +0 -2
  155. package/dist/skills/skill-integration.test.d.ts.map +0 -1
  156. package/dist/skills/skill-integration.test.js +0 -119
  157. package/dist/skills/skill-integration.test.js.map +0 -1
  158. package/dist/skills/skill-registry.d.ts.map +0 -1
  159. package/dist/skills/skill-registry.js.map +0 -1
  160. package/dist/skills/skill-registry.test.d.ts +0 -2
  161. package/dist/skills/skill-registry.test.d.ts.map +0 -1
  162. package/dist/skills/skill-registry.test.js +0 -67
  163. package/dist/skills/skill-registry.test.js.map +0 -1
  164. package/dist/test-servers/weather-server.d.ts +0 -12
  165. package/dist/test-servers/weather-server.d.ts.map +0 -1
  166. package/dist/test-servers/weather-server.js +0 -139
  167. package/dist/test-servers/weather-server.js.map +0 -1
  168. package/dist/test-servers/weather-stdio.d.ts +0 -2
  169. package/dist/test-servers/weather-stdio.d.ts.map +0 -1
  170. package/dist/test-servers/weather-stdio.js +0 -11
  171. package/dist/test-servers/weather-stdio.js.map +0 -1
  172. package/dist/tool-cli/cli.d.ts +0 -3
  173. package/dist/tool-cli/cli.d.ts.map +0 -1
  174. package/dist/tool-cli/cli.js +0 -179
  175. package/dist/tool-cli/cli.js.map +0 -1
  176. package/dist/tool-cli/constants.d.ts +0 -7
  177. package/dist/tool-cli/constants.d.ts.map +0 -1
  178. package/dist/tool-cli/constants.js +0 -15
  179. package/dist/tool-cli/constants.js.map +0 -1
  180. package/dist/tool-cli/format.d.ts.map +0 -1
  181. package/dist/tool-cli/format.js.map +0 -1
  182. package/dist/tool-cli/format.test.d.ts +0 -2
  183. package/dist/tool-cli/format.test.d.ts.map +0 -1
  184. package/dist/tool-cli/format.test.js +0 -30
  185. package/dist/tool-cli/format.test.js.map +0 -1
  186. package/dist/tool-cli/index.d.ts.map +0 -1
  187. package/dist/tool-cli/index.js.map +0 -1
  188. package/dist/tool-cli/rpc-client.d.ts +0 -6
  189. package/dist/tool-cli/rpc-client.d.ts.map +0 -1
  190. package/dist/tool-cli/rpc-client.js +0 -25
  191. package/dist/tool-cli/rpc-client.js.map +0 -1
  192. package/dist/tool-cli/rpc-server.d.ts +0 -48
  193. package/dist/tool-cli/rpc-server.d.ts.map +0 -1
  194. package/dist/tool-cli/rpc-server.js +0 -220
  195. package/dist/tool-cli/rpc-server.js.map +0 -1
  196. package/dist/tool-cli/rpc-server.test.d.ts +0 -2
  197. package/dist/tool-cli/rpc-server.test.d.ts.map +0 -1
  198. package/dist/tool-cli/rpc-server.test.js +0 -154
  199. package/dist/tool-cli/rpc-server.test.js.map +0 -1
@@ -1,34 +1,77 @@
1
1
  import type { McpClientManager, McpTool } from "../mcp/index.js";
2
+ import { type McpPolicy } from "../mcp/policy.js";
3
+ import { type CodeModeDiagnostics, type CodeModeTool } from "./eligibility.js";
2
4
  import type { ExecuteResult, ExecutorOptions } from "./executor.js";
3
- export { getEligibleTools, isEligibleForCodeMode } from "./eligibility.js";
4
- export type { ExecuteResult, ExecutorOptions } from "./executor.js";
5
- export { executeInSandbox, normalizeCode } from "./executor.js";
5
+ import { executeInSandbox } from "./executor.js";
6
+ export { SYNTHESIZED_OUTPUT_SCHEMA, getCodeModeDiagnostics, getCodeModeTools, getEligibleTools, isEligibleForCodeMode, toCodeModeTool, } from "./eligibility.js";
7
+ export type { CodeModeDiagnostics, CodeModeRefusalReason, CodeModeTool, OutputSchemaProvenance, } from "./eligibility.js";
8
+ export type { CodeModeErrorDetails, ExecuteResult, ExecutorOptions } from "./executor.js";
9
+ export { SANDBOX_UNAVAILABLE_ERROR, executeInSandbox, normalizeCode } from "./executor.js";
10
+ export { loadIsolatedVm, peekIsolatedVm, resetIsolatedVmCacheForTests, setIsolatedVmForTests, type IsolatedVmLoad, type IsolatedVmModule, } from "./isolated-vm.js";
6
11
  export { createCodeExecuteTool, createCodeSearchTool } from "./tools.js";
7
12
  export { generateTypeHints, jsonSchemaToTypeString, sanitizeToolName } from "./type-hints.js";
8
13
  export interface CodeModeManagerOptions extends ExecutorOptions {
9
14
  /** Log function for status messages. */
10
15
  log?: (msg: string) => void;
16
+ /** Test seam for proving pre-isolate refusals. */
17
+ sandboxExecutor?: typeof executeInSandbox;
11
18
  }
12
19
  /**
13
- * Orchestrates code mode: discovers eligible tools, generates type hints,
20
+ * Whether the sandbox backend can run code.
21
+ *
22
+ * `unknown` is a real state, not a synonym for unavailable: before the optional
23
+ * native addon has been probed we have not established anything, and reporting
24
+ * that honestly is better than guessing in either direction.
25
+ */
26
+ export interface SandboxAvailability {
27
+ readonly state: "available" | "unavailable" | "unknown";
28
+ readonly reason: string;
29
+ }
30
+ /**
31
+ * Orchestrates code mode: catalogs tools, generates type hints,
14
32
  * and executes model-generated code in a sandbox with tool dispatch.
15
33
  */
16
34
  export declare class CodeModeManager {
17
35
  private mcpManager;
18
- private eligibleTools;
36
+ private policy;
37
+ private codeModeTools;
38
+ private diagnostics;
19
39
  private typeHints;
20
- private options;
40
+ private readonly options;
41
+ private readonly sandboxExecutor;
42
+ private log;
43
+ private lastDiagnosticSummary;
44
+ private sandbox;
45
+ private sandboxProbe;
21
46
  constructor(options?: CodeModeManagerOptions);
22
- /** Initialize with MCP manager, discover eligible tools, generate type hints. */
23
- initialize(mcpManager: McpClientManager): void;
24
- /** Refresh eligible tools and type hints (call on tools/list_changed). */
47
+ /**
48
+ * Whether code mode should be advertised to the model.
49
+ *
50
+ * Only a *proven* unavailable sandbox switches this off. An unprobed backend
51
+ * stays active because `code_execute` is registered synchronously at load and
52
+ * returns a structured `sandbox_unavailable` error if it turns out it cannot
53
+ * run — a truthful refusal at call time beats hiding a facility that works.
54
+ */
55
+ get isActive(): boolean;
56
+ /** Current sandbox backend availability, without triggering a probe. */
57
+ getSandboxAvailability(): SandboxAvailability;
58
+ /**
59
+ * Load the optional native addon once and cache the verdict.
60
+ *
61
+ * Safe to call from any lifecycle hook; concurrent callers share one probe.
62
+ */
63
+ probeSandbox(): Promise<SandboxAvailability>;
64
+ /** Initialize with MCP manager and policy, catalog tools, and generate type hints. */
65
+ initialize(mcpManager: McpClientManager, policy: McpPolicy, log?: (msg: string) => void): void;
66
+ /** Refresh the complete tool catalog and type hints (call on tools/list_changed). */
25
67
  refresh(): void;
26
68
  /** Get the type hints string for injection into system prompt. */
27
69
  getTypeHints(): string;
28
70
  /** Get eligible tools. */
29
71
  getEligibleTools(): McpTool[];
30
- /** Whether code mode has any eligible tools. */
31
- get isActive(): boolean;
72
+ /** Get the complete client-internal catalog, including permission and schema provenance. */
73
+ getCatalogTools(): readonly CodeModeTool[];
74
+ getDiagnostics(): CodeModeDiagnostics;
32
75
  /** Execute code in search mode (tool catalog queries). */
33
76
  searchTools(code: string): Promise<ExecuteResult>;
34
77
  /** Execute code that chains MCP tool calls. */
@@ -62,4 +105,3 @@ export declare class CodeModeManager {
62
105
  formatSystemPromptSection(): string;
63
106
  private execute;
64
107
  }
65
- //# sourceMappingURL=index.d.ts.map
@@ -1,35 +1,111 @@
1
- import { getEligibleTools } from "./eligibility.js";
2
- import { executeInSandbox } from "./executor.js";
1
+ import { McpPolicyError } from "../mcp/policy.js";
2
+ import { getCodeModeDiagnostics, getCodeModeTools, } from "./eligibility.js";
3
+ import { CodeModeDispatchError, executeInSandbox } from "./executor.js";
4
+ import { loadIsolatedVm } from "./isolated-vm.js";
3
5
  import { createCodeExecuteTool, createCodeSearchTool } from "./tools.js";
4
6
  import { generateTypeHints } from "./type-hints.js";
5
- export { getEligibleTools, isEligibleForCodeMode } from "./eligibility.js";
6
- export { executeInSandbox, normalizeCode } from "./executor.js";
7
+ export { SYNTHESIZED_OUTPUT_SCHEMA, getCodeModeDiagnostics, getCodeModeTools, getEligibleTools, isEligibleForCodeMode, toCodeModeTool, } from "./eligibility.js";
8
+ export { SANDBOX_UNAVAILABLE_ERROR, executeInSandbox, normalizeCode } from "./executor.js";
9
+ export { loadIsolatedVm, peekIsolatedVm, resetIsolatedVmCacheForTests, setIsolatedVmForTests, } from "./isolated-vm.js";
7
10
  export { createCodeExecuteTool, createCodeSearchTool } from "./tools.js";
8
11
  export { generateTypeHints, jsonSchemaToTypeString, sanitizeToolName } from "./type-hints.js";
12
+ const NO_ELIGIBLE_TOOLS_ERROR = {
13
+ error: "no_eligible_tools",
14
+ message: "code_search has no callable read-only MCP tools to search.",
15
+ alternatives: ["code_execute", "tool-cli"],
16
+ };
17
+ const SANDBOX_UNPROBED = {
18
+ state: "unknown",
19
+ reason: "the isolated-vm native addon has not been probed yet",
20
+ };
21
+ const SANDBOX_INJECTED = {
22
+ state: "available",
23
+ reason: "a sandbox executor was supplied directly, bypassing the isolated-vm addon",
24
+ };
25
+ const SANDBOX_NATIVE = {
26
+ state: "available",
27
+ reason: "the isolated-vm native addon loaded",
28
+ };
9
29
  /**
10
- * Orchestrates code mode: discovers eligible tools, generates type hints,
30
+ * Orchestrates code mode: catalogs tools, generates type hints,
11
31
  * and executes model-generated code in a sandbox with tool dispatch.
12
32
  */
13
33
  export class CodeModeManager {
14
34
  mcpManager = null;
15
- eligibleTools = [];
16
- typeHints = "";
35
+ policy = null;
36
+ codeModeTools = [];
37
+ diagnostics = getCodeModeDiagnostics([]);
38
+ typeHints = generateTypeHints([]);
17
39
  options;
40
+ sandboxExecutor;
41
+ log;
42
+ lastDiagnosticSummary = "";
43
+ sandbox;
44
+ sandboxProbe;
18
45
  constructor(options = {}) {
19
46
  this.options = options;
47
+ this.sandboxExecutor = options.sandboxExecutor ?? executeInSandbox;
48
+ this.log = options.log;
49
+ // An injected executor is the sandbox. Probing the native addon in that
50
+ // case would report on a backend this manager will never call.
51
+ this.sandbox = options.sandboxExecutor ? SANDBOX_INJECTED : SANDBOX_UNPROBED;
20
52
  }
21
- /** Initialize with MCP manager, discover eligible tools, generate type hints. */
22
- initialize(mcpManager) {
53
+ /**
54
+ * Whether code mode should be advertised to the model.
55
+ *
56
+ * Only a *proven* unavailable sandbox switches this off. An unprobed backend
57
+ * stays active because `code_execute` is registered synchronously at load and
58
+ * returns a structured `sandbox_unavailable` error if it turns out it cannot
59
+ * run — a truthful refusal at call time beats hiding a facility that works.
60
+ */
61
+ get isActive() {
62
+ return this.sandbox.state !== "unavailable";
63
+ }
64
+ /** Current sandbox backend availability, without triggering a probe. */
65
+ getSandboxAvailability() {
66
+ return this.sandbox;
67
+ }
68
+ /**
69
+ * Load the optional native addon once and cache the verdict.
70
+ *
71
+ * Safe to call from any lifecycle hook; concurrent callers share one probe.
72
+ */
73
+ async probeSandbox() {
74
+ if (this.sandbox.state !== "unknown")
75
+ return this.sandbox;
76
+ this.sandboxProbe ??= loadIsolatedVm().then((load) => {
77
+ this.sandbox = load.available
78
+ ? SANDBOX_NATIVE
79
+ : { state: "unavailable", reason: load.reason };
80
+ if (!load.available) {
81
+ this.log?.(`[code-mode] disabled: ${load.reason}. Skills, tool-cli, and routing are unaffected.`);
82
+ }
83
+ return this.sandbox;
84
+ });
85
+ return this.sandboxProbe;
86
+ }
87
+ /** Initialize with MCP manager and policy, catalog tools, and generate type hints. */
88
+ initialize(mcpManager, policy, log) {
23
89
  this.mcpManager = mcpManager;
90
+ this.policy = policy;
91
+ this.log = log ?? this.log;
24
92
  this.refresh();
25
93
  }
26
- /** Refresh eligible tools and type hints (call on tools/list_changed). */
94
+ /** Refresh the complete tool catalog and type hints (call on tools/list_changed). */
27
95
  refresh() {
28
- if (!this.mcpManager)
29
- return;
30
- this.eligibleTools = getEligibleTools(this.mcpManager);
31
- this.typeHints = generateTypeHints(this.eligibleTools);
32
- this.options.log?.(`[code-mode] ${this.eligibleTools.length} eligible tool(s), ${this.typeHints.length} chars of type hints`);
96
+ this.codeModeTools = this.mcpManager ? getCodeModeTools(this.mcpManager) : [];
97
+ this.diagnostics = getCodeModeDiagnostics(this.codeModeTools);
98
+ this.typeHints = generateTypeHints(this.codeModeTools);
99
+ const summary = `[code-mode] ${this.diagnostics.totalTools} tool(s): ` +
100
+ `${this.diagnostics.callableTools} callable, ${this.diagnostics.refusedTools} dispatch-refused; ` +
101
+ `output schemas: ${this.diagnostics.declaredOutputSchemas} declared, ` +
102
+ `${this.diagnostics.synthesizedOutputSchemas} synthesized, ` +
103
+ `${this.diagnostics.unavailableOutputSchemas} unavailable; ` +
104
+ `${this.typeHints.length} chars of type hints`;
105
+ if (summary !== this.lastDiagnosticSummary) {
106
+ this.log?.(summary);
107
+ this.lastDiagnosticSummary = summary;
108
+ }
33
109
  }
34
110
  /** Get the type hints string for injection into system prompt. */
35
111
  getTypeHints() {
@@ -37,18 +113,34 @@ export class CodeModeManager {
37
113
  }
38
114
  /** Get eligible tools. */
39
115
  getEligibleTools() {
40
- return this.eligibleTools;
116
+ return this.codeModeTools.filter((entry) => entry.callable).map((entry) => entry.tool);
41
117
  }
42
- /** Whether code mode has any eligible tools. */
43
- get isActive() {
44
- return this.eligibleTools.length > 0;
118
+ /** Get the complete client-internal catalog, including permission and schema provenance. */
119
+ getCatalogTools() {
120
+ return this.codeModeTools;
121
+ }
122
+ getDiagnostics() {
123
+ return this.diagnostics;
45
124
  }
46
125
  /** Execute code in search mode (tool catalog queries). */
47
126
  async searchTools(code) {
127
+ this.refresh();
128
+ if (this.diagnostics.callableTools === 0) {
129
+ return {
130
+ result: undefined,
131
+ error: NO_ELIGIBLE_TOOLS_ERROR.message,
132
+ errorDetails: {
133
+ ...NO_ELIGIBLE_TOOLS_ERROR,
134
+ alternatives: [...(NO_ELIGIBLE_TOOLS_ERROR.alternatives ?? [])],
135
+ },
136
+ logs: [],
137
+ };
138
+ }
48
139
  return this.execute(code);
49
140
  }
50
141
  /** Execute code that chains MCP tool calls. */
51
142
  async executeCode(code) {
143
+ this.refresh();
52
144
  return this.execute(code);
53
145
  }
54
146
  /** Create the Pi tool definitions for code_search and code_execute. */
@@ -60,14 +152,15 @@ export class CodeModeManager {
60
152
  }
61
153
  /** Format a system prompt section for code mode. */
62
154
  formatSystemPromptSection() {
63
- if (!this.isActive)
64
- return "";
65
155
  return [
66
156
  "",
67
157
  "<code_mode>",
68
158
  "## Code mode",
69
159
  "",
70
- "`code_execute` runs vanilla JavaScript in a sandboxed V8 isolate. Use it for:",
160
+ "Use when a task needs exact computation or control flow: math, aggregation, looping over",
161
+ "results, data transformation, or chaining several MCP tool calls with logic in between.",
162
+ "",
163
+ "`code_execute` runs vanilla JavaScript in a sandboxed V8 isolate. Concretely, that covers:",
71
164
  "",
72
165
  "1. **Arbitrary computation** — math, string manipulation, date arithmetic, data transformation,",
73
166
  " or any calculation the user asks for. No MCP tools needed; plain JS works.",
@@ -75,21 +168,10 @@ export class CodeModeManager {
75
168
  " many tool calls. Write a loop inside one `code_execute` instead of making many separate tool calls.",
76
169
  "3. **Pagination** — fetch batches in a loop until exhausted, then compute over the full dataset.",
77
170
  "",
78
- "Use `code_search` first to discover what tools are available before writing execution code.",
79
- "",
80
- "**Code mode tools are always available — you do not need to call `load_skill` first.**",
171
+ "Use `code_search` first to discover which MCP tools are reachable from inside the sandbox.",
81
172
  "",
82
- "### Choosing the right approach",
173
+ "`code_search` and `code_execute` are always registered and never gated.",
83
174
  "",
84
- "- **Skill** (`load_skill`) — a curated workflow exists for this domain task (e.g., a GitHub skill for PR management).",
85
- "- **tool-cli** — you need to discover what tools exist, or make a quick ad-hoc tool call.",
86
- "- **Code mode** (`code_execute`) — you need computation: math, aggregation, looping over results,",
87
- " data transformation, or chaining multiple tool calls with logic in between.",
88
- "",
89
- "Pick based on what the task needs, not a fixed order. A calculation goes straight to code mode;",
90
- "a single lookup goes to a skill or tool-cli; exploration starts with tool-cli or `code_search`.",
91
- "",
92
- "**If unsure what's available**, start with `code_search` or `tool-cli --help` to see what you have.",
93
175
  "After producing a result, verify it makes sense — run a quick sanity check or spot-check values.",
94
176
  "",
95
177
  "### How to write code",
@@ -131,45 +213,55 @@ export class CodeModeManager {
131
213
  ].join("\n");
132
214
  }
133
215
  async execute(code) {
134
- if (!this.mcpManager) {
135
- return { result: undefined, error: "Code mode not initialized", logs: [] };
136
- }
137
- // Refresh eligible tools in case MCP servers changed since initialization
138
- this.refresh();
139
- const toolNames = this.eligibleTools.map((t) => t.name);
216
+ const policy = this.policy;
217
+ const toolNames = this.codeModeTools.map((entry) => entry.tool.name);
140
218
  const dispatch = async (toolName, args) => {
141
- const tool = this.eligibleTools.find((t) => t.name === toolName);
142
- if (!tool) {
219
+ const codeModeTool = this.codeModeTools.find((entry) => entry.tool.name === toolName);
220
+ if (!codeModeTool) {
143
221
  throw new Error(`Tool "${toolName}" not found in code mode eligible tools`);
144
222
  }
145
- const client = this.mcpManager?.getClient(tool.serverName);
146
- if (!client) {
147
- throw new Error(`MCP server "${tool.serverName}" is not connected`);
223
+ if (!policy) {
224
+ throw new Error("Code mode MCP policy is not initialized");
148
225
  }
149
- const result = await client.callTool({ name: toolName, arguments: args });
150
- // Prefer structuredContent (typed output) over raw content
151
- if (result.structuredContent) {
152
- return result.structuredContent;
226
+ try {
227
+ const terminal = await policy.callTool({
228
+ source: "code-mode",
229
+ serverName: codeModeTool.tool.serverName,
230
+ toolName: codeModeTool.tool.name,
231
+ args,
232
+ });
233
+ return terminal.result;
153
234
  }
154
- // Fall back to parsing text content
155
- if (Array.isArray(result.content)) {
156
- const textParts = result.content
157
- .filter((c) => typeof c === "object" && c !== null && "text" in c)
158
- .map((c) => c.text);
159
- const combined = textParts.join("\n");
160
- try {
161
- return JSON.parse(combined);
162
- }
163
- catch {
164
- return combined;
165
- }
235
+ catch (error) {
236
+ throw toCodeModeDispatchError(error, codeModeTool);
166
237
  }
167
- return result;
168
238
  };
169
- return executeInSandbox(code, toolNames, dispatch, {
239
+ return this.sandboxExecutor(code, toolNames, dispatch, {
170
240
  memoryLimit: this.options.memoryLimit,
171
241
  timeoutMs: this.options.timeoutMs,
172
242
  });
173
243
  }
174
244
  }
175
- //# sourceMappingURL=index.js.map
245
+ /**
246
+ * Translate a policy denial into Code Mode's structured dispatch error, keeping
247
+ * the catalog's refusal detail so the model learns why a tool was refused.
248
+ */
249
+ function toCodeModeDispatchError(error, codeModeTool) {
250
+ if (!(error instanceof McpPolicyError))
251
+ return error;
252
+ const isPermission = error.reason === "permission_denied";
253
+ return new CodeModeDispatchError({
254
+ error: isPermission ? "permission_denied" : error.reason,
255
+ message: error.message,
256
+ alternatives: [...error.alternatives],
257
+ toolName: codeModeTool.tool.name,
258
+ ...(isPermission ? { reason: formatRefusalReasons(codeModeTool) } : {}),
259
+ });
260
+ }
261
+ function formatRefusalReasons(codeModeTool) {
262
+ return codeModeTool.refusalReasons
263
+ .map((reason) => reason === "destructive_hint"
264
+ ? "annotations.destructiveHint is true"
265
+ : "annotations.readOnlyHint is not true")
266
+ .join("; ");
267
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Lazy adapter for the optional `isolated-vm` native addon.
3
+ *
4
+ * Code Mode is the only feature that needs a real V8 isolate. `isolated-vm` is a
5
+ * native addon, so it can legitimately be absent: an unsupported platform, a
6
+ * missing prebuild with no toolchain to compile one, or a deliberate
7
+ * `--omit=optional` install. When that happens Code Mode must report itself
8
+ * unavailable and the rest of the extension — skills, tool-cli, routing — must
9
+ * keep working.
10
+ *
11
+ * Two rules govern this module:
12
+ *
13
+ * 1. **No top-level import.** The specifier is resolved through a dynamic
14
+ * `import()` behind a variable so neither Node nor TypeScript binds to the
15
+ * addon at module-evaluation time. Loading the extension never throws
16
+ * because a native addon is missing.
17
+ * 2. **Never fall back to Node's `vm`.** `node:vm` shares the host heap and
18
+ * realm; it is a code-organisation tool, not a security boundary. Silently
19
+ * downgrading to it would turn "sandboxed execution" into a false claim.
20
+ * If the addon is absent, Code Mode is unavailable — full stop.
21
+ */
22
+ /** The subset of the `isolated-vm` surface Code Mode actually uses. */
23
+ export interface IsolatedVmContext {
24
+ readonly global: {
25
+ set(name: string, value: unknown): Promise<void>;
26
+ };
27
+ eval(code: string, options: {
28
+ promise: true;
29
+ copy: true;
30
+ timeout: number;
31
+ }): Promise<unknown>;
32
+ }
33
+ export interface IsolatedVmIsolate {
34
+ createContext(): Promise<IsolatedVmContext>;
35
+ dispose(): void;
36
+ }
37
+ export interface IsolatedVmModule {
38
+ Isolate: new (options: {
39
+ memoryLimit: number;
40
+ }) => IsolatedVmIsolate;
41
+ Callback: new (fn: (...args: never[]) => unknown) => unknown;
42
+ Reference: new (fn: (...args: never[]) => unknown) => unknown;
43
+ }
44
+ /** Outcome of attempting to load the native addon. */
45
+ export type IsolatedVmLoad = {
46
+ readonly available: true;
47
+ readonly module: IsolatedVmModule;
48
+ } | {
49
+ readonly available: false;
50
+ readonly reason: string;
51
+ };
52
+ /**
53
+ * Load the native addon at most once per process.
54
+ *
55
+ * The result — success *or* failure — is cached. A failed load is not retried:
56
+ * a missing or broken native addon does not become present later in the same
57
+ * process, and retrying would repeat the cost on every Code Mode call.
58
+ */
59
+ export declare function loadIsolatedVm(): Promise<IsolatedVmLoad>;
60
+ /** Synchronously report a previously resolved load, if one has happened. */
61
+ export declare function peekIsolatedVm(): IsolatedVmLoad | undefined;
62
+ /** Reset the memoised state. Test-only. */
63
+ export declare function resetIsolatedVmCacheForTests(): void;
64
+ /** Seed the memoised state. Test-only — lets tests simulate an absent addon. */
65
+ export declare function setIsolatedVmForTests(load: IsolatedVmLoad): void;
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Lazy adapter for the optional `isolated-vm` native addon.
3
+ *
4
+ * Code Mode is the only feature that needs a real V8 isolate. `isolated-vm` is a
5
+ * native addon, so it can legitimately be absent: an unsupported platform, a
6
+ * missing prebuild with no toolchain to compile one, or a deliberate
7
+ * `--omit=optional` install. When that happens Code Mode must report itself
8
+ * unavailable and the rest of the extension — skills, tool-cli, routing — must
9
+ * keep working.
10
+ *
11
+ * Two rules govern this module:
12
+ *
13
+ * 1. **No top-level import.** The specifier is resolved through a dynamic
14
+ * `import()` behind a variable so neither Node nor TypeScript binds to the
15
+ * addon at module-evaluation time. Loading the extension never throws
16
+ * because a native addon is missing.
17
+ * 2. **Never fall back to Node's `vm`.** `node:vm` shares the host heap and
18
+ * realm; it is a code-organisation tool, not a security boundary. Silently
19
+ * downgrading to it would turn "sandboxed execution" into a false claim.
20
+ * If the addon is absent, Code Mode is unavailable — full stop.
21
+ */
22
+ /**
23
+ * Resolved through a variable rather than a string literal so that TypeScript
24
+ * does not require `isolated-vm` types to be present to build, and so no
25
+ * bundler or loader statically links the addon into the module graph.
26
+ */
27
+ const ISOLATED_VM_SPECIFIER = "isolated-vm";
28
+ const MISSING_MODULE_CODES = new Set([
29
+ "ERR_MODULE_NOT_FOUND",
30
+ "MODULE_NOT_FOUND",
31
+ "ERR_DLOPEN_FAILED",
32
+ ]);
33
+ let cached;
34
+ let inFlight;
35
+ /**
36
+ * Load the native addon at most once per process.
37
+ *
38
+ * The result — success *or* failure — is cached. A failed load is not retried:
39
+ * a missing or broken native addon does not become present later in the same
40
+ * process, and retrying would repeat the cost on every Code Mode call.
41
+ */
42
+ export async function loadIsolatedVm() {
43
+ if (cached)
44
+ return cached;
45
+ inFlight ??= attemptLoad().then((result) => {
46
+ cached = result;
47
+ inFlight = undefined;
48
+ return result;
49
+ });
50
+ return inFlight;
51
+ }
52
+ /** Synchronously report a previously resolved load, if one has happened. */
53
+ export function peekIsolatedVm() {
54
+ return cached;
55
+ }
56
+ /** Reset the memoised state. Test-only. */
57
+ export function resetIsolatedVmCacheForTests() {
58
+ cached = undefined;
59
+ inFlight = undefined;
60
+ }
61
+ /** Seed the memoised state. Test-only — lets tests simulate an absent addon. */
62
+ export function setIsolatedVmForTests(load) {
63
+ cached = load;
64
+ inFlight = undefined;
65
+ }
66
+ async function attemptLoad() {
67
+ try {
68
+ const imported = await import(ISOLATED_VM_SPECIFIER);
69
+ const candidate = unwrapDefault(imported);
70
+ if (!isIsolatedVmModule(candidate)) {
71
+ return {
72
+ available: false,
73
+ reason: "the optional isolated-vm addon loaded but did not expose the expected Isolate/Callback/Reference API",
74
+ };
75
+ }
76
+ return { available: true, module: candidate };
77
+ }
78
+ catch (error) {
79
+ return { available: false, reason: describeLoadFailure(error) };
80
+ }
81
+ }
82
+ function unwrapDefault(imported) {
83
+ if (typeof imported !== "object" || imported === null)
84
+ return imported;
85
+ const namespace = imported;
86
+ return namespace.default ?? imported;
87
+ }
88
+ function isIsolatedVmModule(value) {
89
+ if (typeof value !== "object" || value === null)
90
+ return false;
91
+ const candidate = value;
92
+ return (typeof candidate.Isolate === "function" &&
93
+ typeof candidate.Callback === "function" &&
94
+ typeof candidate.Reference === "function");
95
+ }
96
+ /**
97
+ * Turn a load failure into a reason a human can act on.
98
+ *
99
+ * The two failure shapes are meaningfully different: "not installed" is fixed
100
+ * by installing it, while "installed but failed to load" points at an ABI or
101
+ * platform mismatch and needs a rebuild.
102
+ */
103
+ function describeLoadFailure(error) {
104
+ const code = getErrorCode(error);
105
+ const detail = error instanceof Error ? error.message : String(error);
106
+ if (code && MISSING_MODULE_CODES.has(code)) {
107
+ return `the optional isolated-vm native addon is not installed or failed to load (${code}: ${detail})`;
108
+ }
109
+ return `the optional isolated-vm native addon could not be loaded (${detail})`;
110
+ }
111
+ function getErrorCode(error) {
112
+ if (typeof error !== "object" || error === null)
113
+ return undefined;
114
+ const code = error.code;
115
+ return typeof code === "string" ? code : undefined;
116
+ }
@@ -9,6 +9,10 @@ export interface CodeModeToolDetails {
9
9
  executionMs: number;
10
10
  logs: string[];
11
11
  error?: string;
12
+ message?: string;
13
+ alternatives?: string[];
14
+ toolName?: string;
15
+ reason?: string;
12
16
  }
13
17
  /**
14
18
  * Create the `code_search` tool for querying the tool catalog.
@@ -42,4 +46,3 @@ export declare function createCodeExecuteTool(manager: CodeModeManager): {
42
46
  execute(_toolCallId: string, params: CodeInputType, _signal: AbortSignal | undefined, _onUpdate: undefined, _ctx: ExtensionContext): Promise<AgentToolResult<CodeModeToolDetails>>;
43
47
  };
44
48
  export {};
45
- //# sourceMappingURL=tools.d.ts.map
@@ -7,13 +7,17 @@ const CodeInput = Type.Object({
7
7
  /** Format an ExecuteResult into a tool response. Falls back to logs if result is undefined. */
8
8
  function formatResult(result, executionMs, errorPrefix) {
9
9
  if (result.error) {
10
+ const errorDetails = result.errorDetails ?? {
11
+ error: "execution_failed",
12
+ message: result.error,
13
+ };
10
14
  return {
11
15
  content: [{ type: "text", text: `${errorPrefix}: ${result.error}` }],
12
- details: { executionMs, logs: result.logs, error: result.error },
16
+ details: { executionMs, logs: result.logs, ...errorDetails },
13
17
  };
14
18
  }
15
19
  // If code didn't return a value, fall back to captured console output
16
- const output = result.result !== undefined && result.result !== null
20
+ const output = result.result !== undefined
17
21
  ? typeof result.result === "string"
18
22
  ? result.result
19
23
  : JSON.stringify(result.result, null, 2)
@@ -38,7 +42,7 @@ export function createCodeSearchTool(manager) {
38
42
  return {
39
43
  name: "code_search",
40
44
  label: "Code Search",
41
- description: "Discover available read-only MCP tools by writing JavaScript. Use `codemode.listTools()` to list tools and `codemode.describeTools(names)` for type info. Use this before code_execute to understand what data sources are available.",
45
+ description: "Discover all available MCP tools by writing JavaScript. Use `codemode.listTools()` to list tools and `codemode.describeTools(names)` for type info. Type hints identify which tools Code Mode can call; non-read-only tools remain discovery-only.",
42
46
  parameters: CodeInput,
43
47
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
44
48
  const start = performance.now();
@@ -67,4 +71,3 @@ export function createCodeExecuteTool(manager) {
67
71
  },
68
72
  };
69
73
  }
70
- //# sourceMappingURL=tools.js.map
@@ -1,4 +1,5 @@
1
1
  import type { McpTool } from "../mcp/index.js";
2
+ import { type CodeModeTool } from "./eligibility.js";
2
3
  type JsonSchema = Record<string, unknown>;
3
4
  /**
4
5
  * Convert a JSON Schema to a TypeScript type string.
@@ -15,6 +16,5 @@ export declare function sanitizeToolName(name: string): string;
15
16
  * Produces a `declare const codemode: { ... }` block with type-safe
16
17
  * method signatures the model can use when writing code.
17
18
  */
18
- export declare function generateTypeHints(tools: McpTool[]): string;
19
+ export declare function generateTypeHints(tools: readonly (McpTool | CodeModeTool)[]): string;
19
20
  export {};
20
- //# sourceMappingURL=type-hints.d.ts.map