@sammorrowdrums/mcpi-ext 0.2.1 → 1.0.1

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 +426 -151
  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,10 +1,35 @@
1
1
  import type { McpClientManager, McpTool } from "../mcp/index.js";
2
+ export declare const SYNTHESIZED_OUTPUT_SCHEMA: NonNullable<McpTool["outputSchema"]>;
3
+ export type OutputSchemaProvenance = "declared" | "synthesized" | "unavailable";
4
+ export type CodeModeRefusalReason = "destructive_hint" | "read_only_hint_required";
2
5
  /**
3
- * A tool is eligible for code mode when:
4
- * 1. annotations.readOnlyHint === true does not modify its environment
5
- * 2. outputSchema is defined — results are typed and parseable
6
+ * Client-internal Code Mode metadata. The source MCP tool is retained separately
7
+ * so provenance never becomes part of an MCP request or response.
8
+ */
9
+ export interface CodeModeTool {
10
+ readonly tool: McpTool;
11
+ readonly callable: boolean;
12
+ readonly refusalReasons: readonly CodeModeRefusalReason[];
13
+ readonly outputSchema?: NonNullable<McpTool["outputSchema"]>;
14
+ readonly outputSchemaProvenance: OutputSchemaProvenance;
15
+ }
16
+ export interface CodeModeDiagnostics {
17
+ readonly totalTools: number;
18
+ readonly callableTools: number;
19
+ readonly refusedTools: number;
20
+ readonly declaredOutputSchemas: number;
21
+ readonly synthesizedOutputSchemas: number;
22
+ readonly unavailableOutputSchemas: number;
23
+ }
24
+ /**
25
+ * Code Mode dispatch is allowed only when a tool explicitly declares itself
26
+ * read-only and does not also declare destructive behavior.
6
27
  */
7
28
  export declare function isEligibleForCodeMode(tool: McpTool): boolean;
8
29
  /** Get all tools eligible for code mode across all connected MCP servers. */
9
30
  export declare function getEligibleTools(mcpManager: McpClientManager): McpTool[];
10
- //# sourceMappingURL=eligibility.d.ts.map
31
+ /** Build the internal Code Mode catalog without mutating source MCP tool definitions. */
32
+ export declare function toCodeModeTool(tool: McpTool): CodeModeTool;
33
+ /** Get every discovered MCP tool with Code Mode permission and schema metadata. */
34
+ export declare function getCodeModeTools(mcpManager: McpClientManager): CodeModeTool[];
35
+ export declare function getCodeModeDiagnostics(tools: readonly CodeModeTool[]): CodeModeDiagnostics;
@@ -1,14 +1,64 @@
1
+ export const SYNTHESIZED_OUTPUT_SCHEMA = Object.freeze({});
1
2
  /**
2
- * A tool is eligible for code mode when:
3
- * 1. annotations.readOnlyHint === true — does not modify its environment
4
- * 2. outputSchema is defined — results are typed and parseable
3
+ * Code Mode dispatch is allowed only when a tool explicitly declares itself
4
+ * read-only and does not also declare destructive behavior.
5
5
  */
6
6
  export function isEligibleForCodeMode(tool) {
7
- const annotations = tool.annotations;
8
- return annotations?.readOnlyHint === true && tool.outputSchema != null;
7
+ return tool.annotations?.readOnlyHint === true && tool.annotations.destructiveHint !== true;
9
8
  }
10
9
  /** Get all tools eligible for code mode across all connected MCP servers. */
11
10
  export function getEligibleTools(mcpManager) {
12
11
  return mcpManager.getTools().filter(isEligibleForCodeMode);
13
12
  }
14
- //# sourceMappingURL=eligibility.js.map
13
+ /** Build the internal Code Mode catalog without mutating source MCP tool definitions. */
14
+ export function toCodeModeTool(tool) {
15
+ const refusalReasons = [];
16
+ if (tool.annotations?.readOnlyHint !== true) {
17
+ refusalReasons.push("read_only_hint_required");
18
+ }
19
+ if (tool.annotations?.destructiveHint === true) {
20
+ refusalReasons.push("destructive_hint");
21
+ }
22
+ const callable = refusalReasons.length === 0;
23
+ const declaredOutputSchema = tool.outputSchema !== undefined && tool.outputSchema !== null ? tool.outputSchema : undefined;
24
+ if (declaredOutputSchema) {
25
+ return {
26
+ tool,
27
+ callable,
28
+ refusalReasons,
29
+ outputSchema: declaredOutputSchema,
30
+ outputSchemaProvenance: "declared",
31
+ };
32
+ }
33
+ if (callable) {
34
+ return {
35
+ tool,
36
+ callable,
37
+ refusalReasons,
38
+ outputSchema: SYNTHESIZED_OUTPUT_SCHEMA,
39
+ outputSchemaProvenance: "synthesized",
40
+ };
41
+ }
42
+ return {
43
+ tool,
44
+ callable,
45
+ refusalReasons,
46
+ outputSchemaProvenance: "unavailable",
47
+ };
48
+ }
49
+ /** Get every discovered MCP tool with Code Mode permission and schema metadata. */
50
+ export function getCodeModeTools(mcpManager) {
51
+ return mcpManager.getTools().map(toCodeModeTool);
52
+ }
53
+ export function getCodeModeDiagnostics(tools) {
54
+ const callableTools = tools.filter((tool) => tool.callable).length;
55
+ const countProvenance = (provenance) => tools.filter((tool) => tool.outputSchemaProvenance === provenance).length;
56
+ return {
57
+ totalTools: tools.length,
58
+ callableTools,
59
+ refusedTools: tools.length - callableTools,
60
+ declaredOutputSchemas: countProvenance("declared"),
61
+ synthesizedOutputSchemas: countProvenance("synthesized"),
62
+ unavailableOutputSchemas: countProvenance("unavailable"),
63
+ };
64
+ }
@@ -2,8 +2,20 @@
2
2
  export interface ExecuteResult {
3
3
  result: unknown;
4
4
  error?: string;
5
+ errorDetails?: CodeModeErrorDetails;
5
6
  logs: string[];
6
7
  }
8
+ export interface CodeModeErrorDetails {
9
+ error: string;
10
+ message: string;
11
+ alternatives?: string[];
12
+ toolName?: string;
13
+ reason?: string;
14
+ }
15
+ export declare class CodeModeDispatchError extends Error {
16
+ readonly details: CodeModeErrorDetails;
17
+ constructor(details: CodeModeErrorDetails);
18
+ }
7
19
  /** A function the sandbox can call to invoke an MCP tool. */
8
20
  export type ToolDispatchFn = (toolName: string, args: Record<string, unknown>) => Promise<unknown>;
9
21
  export interface ExecutorOptions {
@@ -12,6 +24,8 @@ export interface ExecutorOptions {
12
24
  /** Execution timeout in milliseconds. Default: 30000. */
13
25
  timeoutMs?: number;
14
26
  }
27
+ /** Error code surfaced when the V8 isolate backend is not installed. */
28
+ export declare const SANDBOX_UNAVAILABLE_ERROR = "sandbox_unavailable";
15
29
  /**
16
30
  * Execute model-generated JavaScript code in an isolated V8 sandbox.
17
31
  *
@@ -23,6 +37,11 @@ export interface ExecutorOptions {
23
37
  *
24
38
  * Tool calls are dispatched to the host via `Reference` callbacks —
25
39
  * actual MCP tool execution happens outside the sandbox.
40
+ *
41
+ * The `isolated-vm` addon is optional and loaded lazily. If it is unavailable
42
+ * this returns a structured `sandbox_unavailable` error rather than falling
43
+ * back to Node's `vm` module: `node:vm` shares the host realm and heap, so
44
+ * using it here would silently void the isolation guarantee this API makes.
26
45
  */
27
46
  export declare function executeInSandbox(code: string, toolNames: string[], dispatch: ToolDispatchFn, options?: ExecutorOptions): Promise<ExecuteResult>;
28
47
  /**
@@ -32,4 +51,3 @@ export declare function executeInSandbox(code: string, toolNames: string[], disp
32
51
  * - Otherwise leave code as a statement block; callers must explicitly `return` a value
33
52
  */
34
53
  export declare function normalizeCode(code: string): string;
35
- //# sourceMappingURL=executor.d.ts.map
@@ -1,7 +1,18 @@
1
- import ivm from "isolated-vm";
1
+ import { loadIsolatedVm } from "./isolated-vm.js";
2
2
  import { sanitizeToolName } from "./type-hints.js";
3
+ export class CodeModeDispatchError extends Error {
4
+ details;
5
+ constructor(details) {
6
+ super(details.message);
7
+ this.details = details;
8
+ this.name = "CodeModeDispatchError";
9
+ }
10
+ }
3
11
  const DEFAULT_MEMORY_LIMIT = 128;
4
12
  const DEFAULT_TIMEOUT_MS = 30_000;
13
+ const STRUCTURED_ERROR_PREFIX = "__CODE_MODE_ERROR__";
14
+ /** Error code surfaced when the V8 isolate backend is not installed. */
15
+ export const SANDBOX_UNAVAILABLE_ERROR = "sandbox_unavailable";
5
16
  /**
6
17
  * Execute model-generated JavaScript code in an isolated V8 sandbox.
7
18
  *
@@ -13,19 +24,40 @@ const DEFAULT_TIMEOUT_MS = 30_000;
13
24
  *
14
25
  * Tool calls are dispatched to the host via `Reference` callbacks —
15
26
  * actual MCP tool execution happens outside the sandbox.
27
+ *
28
+ * The `isolated-vm` addon is optional and loaded lazily. If it is unavailable
29
+ * this returns a structured `sandbox_unavailable` error rather than falling
30
+ * back to Node's `vm` module: `node:vm` shares the host realm and heap, so
31
+ * using it here would silently void the isolation guarantee this API makes.
16
32
  */
17
33
  export async function executeInSandbox(code, toolNames, dispatch, options = {}) {
18
34
  const memoryLimit = options.memoryLimit ?? DEFAULT_MEMORY_LIMIT;
19
35
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
36
+ const load = await loadIsolatedVm();
37
+ if (!load.available) {
38
+ const message = `Code Mode is unavailable: ${load.reason}. Use tool-cli or the MCP tool proxies instead.`;
39
+ return {
40
+ result: undefined,
41
+ error: message,
42
+ errorDetails: {
43
+ error: SANDBOX_UNAVAILABLE_ERROR,
44
+ message,
45
+ reason: load.reason,
46
+ alternatives: ["tool-cli", "MCP tool proxies"],
47
+ },
48
+ logs: [],
49
+ };
50
+ }
51
+ const ivm = load.module;
20
52
  const isolate = new ivm.Isolate({ memoryLimit });
21
53
  try {
22
- return await runInIsolate(isolate, code, toolNames, dispatch, timeoutMs);
54
+ return await runInIsolate(ivm, isolate, code, toolNames, dispatch, timeoutMs);
23
55
  }
24
56
  finally {
25
57
  isolate.dispose();
26
58
  }
27
59
  }
28
- async function runInIsolate(isolate, code, toolNames, dispatch, timeoutMs) {
60
+ async function runInIsolate(ivm, isolate, code, toolNames, dispatch, timeoutMs) {
29
61
  const ctx = await isolate.createContext();
30
62
  const jail = ctx.global;
31
63
  const logs = [];
@@ -37,8 +69,16 @@ async function runInIsolate(isolate, code, toolNames, dispatch, timeoutMs) {
37
69
  // Inject tool dispatcher Reference (async callback)
38
70
  const dispatchRef = new ivm.Reference(async (toolName, argsJson) => {
39
71
  const args = JSON.parse(argsJson);
40
- const result = await dispatch(toolName, args);
41
- return JSON.stringify(result === undefined ? null : result);
72
+ try {
73
+ const result = await dispatch(toolName, args);
74
+ return JSON.stringify({ ok: true, value: result === undefined ? null : result });
75
+ }
76
+ catch (error) {
77
+ if (error instanceof CodeModeDispatchError) {
78
+ return JSON.stringify({ ok: false, error: error.details });
79
+ }
80
+ throw error;
81
+ }
42
82
  });
43
83
  await jail.set("__dispatch", dispatchRef);
44
84
  // Build the tool call helper and codemode proxy as setup code
@@ -57,7 +97,11 @@ async function runInIsolate(isolate, code, toolNames, dispatch, timeoutMs) {
57
97
  const console = { log: (...args) => __log(args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ')) };
58
98
  const __callTool = async (name, args) => {
59
99
  const r = await __dispatch.apply(undefined, [name, JSON.stringify(args ?? {})], { arguments: { copy: true }, result: { promise: true, copy: true } });
60
- return JSON.parse(r);
100
+ const response = JSON.parse(r);
101
+ if (!response.ok) {
102
+ throw new Error(${JSON.stringify(STRUCTURED_ERROR_PREFIX)} + JSON.stringify(response.error));
103
+ }
104
+ return response.value;
61
105
  };
62
106
  const codemode = {
63
107
  listTools: async () => ${JSON.stringify(toolNames)},
@@ -83,7 +127,13 @@ ${toolProxyEntries}
83
127
  }
84
128
  catch (err) {
85
129
  const message = err instanceof Error ? err.message : String(err);
86
- return { result: undefined, error: message, logs };
130
+ const errorDetails = parseStructuredError(message);
131
+ return {
132
+ result: undefined,
133
+ error: errorDetails?.message ?? message,
134
+ errorDetails,
135
+ logs,
136
+ };
87
137
  }
88
138
  }
89
139
  /**
@@ -117,4 +167,19 @@ export function normalizeCode(code) {
117
167
  // Otherwise, treat as a code block — return the last expression
118
168
  return normalized;
119
169
  }
120
- //# sourceMappingURL=executor.js.map
170
+ function parseStructuredError(message) {
171
+ const markerIndex = message.indexOf(STRUCTURED_ERROR_PREFIX);
172
+ if (markerIndex === -1)
173
+ return undefined;
174
+ const serialized = message.slice(markerIndex + STRUCTURED_ERROR_PREFIX.length);
175
+ try {
176
+ const details = JSON.parse(serialized);
177
+ if (typeof details.error !== "string" || typeof details.message !== "string") {
178
+ return undefined;
179
+ }
180
+ return details;
181
+ }
182
+ catch {
183
+ return undefined;
184
+ }
185
+ }
@@ -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