@nitrostack/core 1.0.17 → 1.1.0-beta.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/dist/core/app-decorator.d.ts +5 -0
  2. package/dist/core/app-decorator.d.ts.map +1 -1
  3. package/dist/core/app-decorator.js +2 -1
  4. package/dist/core/app-decorator.js.map +1 -1
  5. package/dist/core/builders.d.ts.map +1 -1
  6. package/dist/core/builders.js +2 -0
  7. package/dist/core/builders.js.map +1 -1
  8. package/dist/core/decorators.d.ts +11 -0
  9. package/dist/core/decorators.d.ts.map +1 -1
  10. package/dist/core/decorators.js.map +1 -1
  11. package/dist/core/health/health-checks.resource.d.ts +4 -3
  12. package/dist/core/health/health-checks.resource.d.ts.map +1 -1
  13. package/dist/core/health/health-checks.resource.js +35 -8
  14. package/dist/core/health/health-checks.resource.js.map +1 -1
  15. package/dist/core/health/health.interface.d.ts +40 -0
  16. package/dist/core/health/health.interface.d.ts.map +1 -0
  17. package/dist/core/health/health.interface.js +2 -0
  18. package/dist/core/health/health.interface.js.map +1 -0
  19. package/dist/core/index.d.ts +11 -2
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +9 -1
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/core/interceptors/data-spillover.interceptor.d.ts +30 -0
  24. package/dist/core/interceptors/data-spillover.interceptor.d.ts.map +1 -0
  25. package/dist/core/interceptors/data-spillover.interceptor.js +174 -0
  26. package/dist/core/interceptors/data-spillover.interceptor.js.map +1 -0
  27. package/dist/core/interceptors/interceptor.decorator.d.ts +17 -8
  28. package/dist/core/interceptors/interceptor.decorator.d.ts.map +1 -1
  29. package/dist/core/interceptors/interceptor.decorator.js +36 -11
  30. package/dist/core/interceptors/interceptor.decorator.js.map +1 -1
  31. package/dist/core/interceptors/interceptor.interface.d.ts +4 -0
  32. package/dist/core/interceptors/interceptor.interface.d.ts.map +1 -1
  33. package/dist/core/interceptors/spillover/fs-spillover.store.d.ts +54 -0
  34. package/dist/core/interceptors/spillover/fs-spillover.store.d.ts.map +1 -0
  35. package/dist/core/interceptors/spillover/fs-spillover.store.js +302 -0
  36. package/dist/core/interceptors/spillover/fs-spillover.store.js.map +1 -0
  37. package/dist/core/interceptors/spillover/index.d.ts +6 -0
  38. package/dist/core/interceptors/spillover/index.d.ts.map +1 -0
  39. package/dist/core/interceptors/spillover/index.js +6 -0
  40. package/dist/core/interceptors/spillover/index.js.map +1 -0
  41. package/dist/core/interceptors/spillover/memory-spillover.store.d.ts +25 -0
  42. package/dist/core/interceptors/spillover/memory-spillover.store.d.ts.map +1 -0
  43. package/dist/core/interceptors/spillover/memory-spillover.store.js +115 -0
  44. package/dist/core/interceptors/spillover/memory-spillover.store.js.map +1 -0
  45. package/dist/core/interceptors/spillover/preview-generator.d.ts +7 -0
  46. package/dist/core/interceptors/spillover/preview-generator.d.ts.map +1 -0
  47. package/dist/core/interceptors/spillover/preview-generator.js +79 -0
  48. package/dist/core/interceptors/spillover/preview-generator.js.map +1 -0
  49. package/dist/core/interceptors/spillover/spillover-store.interface.d.ts +20 -0
  50. package/dist/core/interceptors/spillover/spillover-store.interface.d.ts.map +1 -0
  51. package/dist/core/interceptors/spillover/spillover-store.interface.js +2 -0
  52. package/dist/core/interceptors/spillover/spillover-store.interface.js.map +1 -0
  53. package/dist/core/interceptors/spillover/types.d.ts +71 -0
  54. package/dist/core/interceptors/spillover/types.d.ts.map +1 -0
  55. package/dist/core/interceptors/spillover/types.js +12 -0
  56. package/dist/core/interceptors/spillover/types.js.map +1 -0
  57. package/dist/core/protocol/adapter.d.ts +7 -1
  58. package/dist/core/protocol/adapter.d.ts.map +1 -1
  59. package/dist/core/protocol/modern-v2.adapter.d.ts +95 -2
  60. package/dist/core/protocol/modern-v2.adapter.d.ts.map +1 -1
  61. package/dist/core/protocol/modern-v2.adapter.js +703 -165
  62. package/dist/core/protocol/modern-v2.adapter.js.map +1 -1
  63. package/dist/core/server.d.ts +104 -4
  64. package/dist/core/server.d.ts.map +1 -1
  65. package/dist/core/server.js +465 -52
  66. package/dist/core/server.js.map +1 -1
  67. package/dist/core/task.d.ts.map +1 -1
  68. package/dist/core/task.js +8 -7
  69. package/dist/core/task.js.map +1 -1
  70. package/dist/core/tool.d.ts +12 -3
  71. package/dist/core/tool.d.ts.map +1 -1
  72. package/dist/core/tool.js +21 -3
  73. package/dist/core/tool.js.map +1 -1
  74. package/dist/core/transforms/catalog.transform.d.ts +40 -0
  75. package/dist/core/transforms/catalog.transform.d.ts.map +1 -0
  76. package/dist/core/transforms/catalog.transform.js +52 -0
  77. package/dist/core/transforms/catalog.transform.js.map +1 -0
  78. package/dist/core/transforms/code-mode/code-mode.transform.d.ts +75 -0
  79. package/dist/core/transforms/code-mode/code-mode.transform.d.ts.map +1 -0
  80. package/dist/core/transforms/code-mode/code-mode.transform.js +226 -0
  81. package/dist/core/transforms/code-mode/code-mode.transform.js.map +1 -0
  82. package/dist/core/transforms/code-mode/destructive-guard.d.ts +7 -0
  83. package/dist/core/transforms/code-mode/destructive-guard.d.ts.map +1 -0
  84. package/dist/core/transforms/code-mode/destructive-guard.js +18 -0
  85. package/dist/core/transforms/code-mode/destructive-guard.js.map +1 -0
  86. package/dist/core/transforms/code-mode/guest-evaluator.d.ts +17 -0
  87. package/dist/core/transforms/code-mode/guest-evaluator.d.ts.map +1 -0
  88. package/dist/core/transforms/code-mode/guest-evaluator.js +277 -0
  89. package/dist/core/transforms/code-mode/guest-evaluator.js.map +1 -0
  90. package/dist/core/transforms/code-mode/index.d.ts +11 -0
  91. package/dist/core/transforms/code-mode/index.d.ts.map +1 -0
  92. package/dist/core/transforms/code-mode/index.js +11 -0
  93. package/dist/core/transforms/code-mode/index.js.map +1 -0
  94. package/dist/core/transforms/code-mode/ipc-messages.d.ts +31 -0
  95. package/dist/core/transforms/code-mode/ipc-messages.d.ts.map +1 -0
  96. package/dist/core/transforms/code-mode/ipc-messages.js +2 -0
  97. package/dist/core/transforms/code-mode/ipc-messages.js.map +1 -0
  98. package/dist/core/transforms/code-mode/legible-error.d.ts +8 -0
  99. package/dist/core/transforms/code-mode/legible-error.d.ts.map +1 -0
  100. package/dist/core/transforms/code-mode/legible-error.js +17 -0
  101. package/dist/core/transforms/code-mode/legible-error.js.map +1 -0
  102. package/dist/core/transforms/code-mode/quickjs-wasm.provider.d.ts +25 -0
  103. package/dist/core/transforms/code-mode/quickjs-wasm.provider.d.ts.map +1 -0
  104. package/dist/core/transforms/code-mode/quickjs-wasm.provider.js +31 -0
  105. package/dist/core/transforms/code-mode/quickjs-wasm.provider.js.map +1 -0
  106. package/dist/core/transforms/code-mode/sandbox.interface.d.ts +10 -0
  107. package/dist/core/transforms/code-mode/sandbox.interface.d.ts.map +1 -0
  108. package/dist/core/transforms/code-mode/sandbox.interface.js +2 -0
  109. package/dist/core/transforms/code-mode/sandbox.interface.js.map +1 -0
  110. package/dist/core/transforms/code-mode/sandbox.worker.d.ts +2 -0
  111. package/dist/core/transforms/code-mode/sandbox.worker.d.ts.map +1 -0
  112. package/dist/core/transforms/code-mode/sandbox.worker.js +69 -0
  113. package/dist/core/transforms/code-mode/sandbox.worker.js.map +1 -0
  114. package/dist/core/transforms/code-mode/synthetic-tools.d.ts +23 -0
  115. package/dist/core/transforms/code-mode/synthetic-tools.d.ts.map +1 -0
  116. package/dist/core/transforms/code-mode/synthetic-tools.js +112 -0
  117. package/dist/core/transforms/code-mode/synthetic-tools.js.map +1 -0
  118. package/dist/core/transforms/code-mode/types.d.ts +32 -0
  119. package/dist/core/transforms/code-mode/types.d.ts.map +1 -0
  120. package/dist/core/transforms/code-mode/types.js +2 -0
  121. package/dist/core/transforms/code-mode/types.js.map +1 -0
  122. package/dist/core/transforms/code-mode/worker-pool.d.ts +71 -0
  123. package/dist/core/transforms/code-mode/worker-pool.d.ts.map +1 -0
  124. package/dist/core/transforms/code-mode/worker-pool.js +435 -0
  125. package/dist/core/transforms/code-mode/worker-pool.js.map +1 -0
  126. package/dist/core/transforms/index.d.ts +6 -0
  127. package/dist/core/transforms/index.d.ts.map +1 -0
  128. package/dist/core/transforms/index.js +6 -0
  129. package/dist/core/transforms/index.js.map +1 -0
  130. package/dist/core/transforms/search/base-search.transform.d.ts +59 -0
  131. package/dist/core/transforms/search/base-search.transform.d.ts.map +1 -0
  132. package/dist/core/transforms/search/base-search.transform.js +169 -0
  133. package/dist/core/transforms/search/base-search.transform.js.map +1 -0
  134. package/dist/core/transforms/search/bm25-search.transform.d.ts +27 -0
  135. package/dist/core/transforms/search/bm25-search.transform.d.ts.map +1 -0
  136. package/dist/core/transforms/search/bm25-search.transform.js +37 -0
  137. package/dist/core/transforms/search/bm25-search.transform.js.map +1 -0
  138. package/dist/core/transforms/search/bm25.engine.d.ts +46 -0
  139. package/dist/core/transforms/search/bm25.engine.d.ts.map +1 -0
  140. package/dist/core/transforms/search/bm25.engine.js +160 -0
  141. package/dist/core/transforms/search/bm25.engine.js.map +1 -0
  142. package/dist/core/transforms/search/index.d.ts +9 -0
  143. package/dist/core/transforms/search/index.d.ts.map +1 -0
  144. package/dist/core/transforms/search/index.js +9 -0
  145. package/dist/core/transforms/search/index.js.map +1 -0
  146. package/dist/core/transforms/search/regex-match.worker.d.ts +2 -0
  147. package/dist/core/transforms/search/regex-match.worker.d.ts.map +1 -0
  148. package/dist/core/transforms/search/regex-match.worker.js +19 -0
  149. package/dist/core/transforms/search/regex-match.worker.js.map +1 -0
  150. package/dist/core/transforms/search/regex-search.transform.d.ts +41 -0
  151. package/dist/core/transforms/search/regex-search.transform.d.ts.map +1 -0
  152. package/dist/core/transforms/search/regex-search.transform.js +237 -0
  153. package/dist/core/transforms/search/regex-search.transform.js.map +1 -0
  154. package/dist/core/transforms/search/synthetic-tools.d.ts +11 -0
  155. package/dist/core/transforms/search/synthetic-tools.d.ts.map +1 -0
  156. package/dist/core/transforms/search/synthetic-tools.js +76 -0
  157. package/dist/core/transforms/search/synthetic-tools.js.map +1 -0
  158. package/dist/core/transforms/search/tokenizer.d.ts +11 -0
  159. package/dist/core/transforms/search/tokenizer.d.ts.map +1 -0
  160. package/dist/core/transforms/search/tokenizer.js +38 -0
  161. package/dist/core/transforms/search/tokenizer.js.map +1 -0
  162. package/dist/core/transforms/search/tool-serializer.d.ts +6 -0
  163. package/dist/core/transforms/search/tool-serializer.d.ts.map +1 -0
  164. package/dist/core/transforms/search/tool-serializer.js +41 -0
  165. package/dist/core/transforms/search/tool-serializer.js.map +1 -0
  166. package/dist/core/transforms/search/types.d.ts +36 -0
  167. package/dist/core/transforms/search/types.d.ts.map +1 -0
  168. package/dist/core/transforms/search/types.js +2 -0
  169. package/dist/core/transforms/search/types.js.map +1 -0
  170. package/dist/core/transforms/tool-cache-key.d.ts +15 -0
  171. package/dist/core/transforms/tool-cache-key.d.ts.map +1 -0
  172. package/dist/core/transforms/tool-cache-key.js +61 -0
  173. package/dist/core/transforms/tool-cache-key.js.map +1 -0
  174. package/dist/core/transforms/transform.interface.d.ts +34 -0
  175. package/dist/core/transforms/transform.interface.d.ts.map +1 -0
  176. package/dist/core/transforms/transform.interface.js +2 -0
  177. package/dist/core/transforms/transform.interface.js.map +1 -0
  178. package/dist/core/transforms/validate-tool-arguments.d.ts +10 -0
  179. package/dist/core/transforms/validate-tool-arguments.d.ts.map +1 -0
  180. package/dist/core/transforms/validate-tool-arguments.js +62 -0
  181. package/dist/core/transforms/validate-tool-arguments.js.map +1 -0
  182. package/dist/core/transforms/visibility/index.d.ts +3 -0
  183. package/dist/core/transforms/visibility/index.d.ts.map +1 -0
  184. package/dist/core/transforms/visibility/index.js +3 -0
  185. package/dist/core/transforms/visibility/index.js.map +1 -0
  186. package/dist/core/transforms/visibility/session-store.d.ts +132 -0
  187. package/dist/core/transforms/visibility/session-store.d.ts.map +1 -0
  188. package/dist/core/transforms/visibility/session-store.js +330 -0
  189. package/dist/core/transforms/visibility/session-store.js.map +1 -0
  190. package/dist/core/transforms/visibility/visibility.transform.d.ts +42 -0
  191. package/dist/core/transforms/visibility/visibility.transform.d.ts.map +1 -0
  192. package/dist/core/transforms/visibility/visibility.transform.js +109 -0
  193. package/dist/core/transforms/visibility/visibility.transform.js.map +1 -0
  194. package/dist/core/types.d.ts +60 -0
  195. package/dist/core/types.d.ts.map +1 -1
  196. package/dist/testing/index.d.ts.map +1 -1
  197. package/dist/testing/index.js +3 -0
  198. package/dist/testing/index.js.map +1 -1
  199. package/package.json +8 -2
@@ -0,0 +1,112 @@
1
+ import { Tool } from '../../tool.js';
2
+ import { clampSearchLimit } from '../search/synthetic-tools.js';
3
+ /**
4
+ * Builds the 3 synthetic meta-tools (search, get_schema, execute) for Code Mode.
5
+ */
6
+ export function buildCodeModeTools(bm25Engine, rawTools, workerPool, limits, customNames = {}, filterAuthorized = async (names) => names.map((n) => rawTools.get(n)).filter((t) => t !== undefined)) {
7
+ const searchName = customNames.searchToolName || 'search';
8
+ const getSchemaName = customNames.getSchemaToolName || 'get_schema';
9
+ const executeName = customNames.executeToolName || 'execute';
10
+ // 1. search meta-tool
11
+ const searchTool = new Tool({
12
+ name: searchName,
13
+ description: 'Searches available tools using natural language query or keywords. Returns tool names and brief summaries.',
14
+ inputSchema: {
15
+ type: 'object',
16
+ properties: {
17
+ query: { type: 'string', description: 'Natural language search query or keywords' },
18
+ limit: { type: 'number', description: 'Maximum number of tools to return (default: 5)' },
19
+ },
20
+ required: ['query'],
21
+ },
22
+ handler: async (args, context) => {
23
+ const limit = clampSearchLimit(args.limit, 5);
24
+ // Rank the full index, then drop what this session may not see, so a page
25
+ // of hidden tools cannot shrink the requested limit.
26
+ const ranked = bm25Engine.search(args.query, Number.MAX_SAFE_INTEGER);
27
+ const authorized = await filterAuthorized(ranked.map((r) => r.item.name), context);
28
+ const text = authorized
29
+ .slice(0, limit)
30
+ .map((tool) => `- **${tool.name}**: ${tool.description || 'No description'}`)
31
+ .join('\n');
32
+ return { content: [{ type: 'text', text: text || 'No matching tools found.' }] };
33
+ },
34
+ });
35
+ // 2. get_schema meta-tool
36
+ const getSchemaTool = new Tool({
37
+ name: getSchemaName,
38
+ description: 'Returns parameter schemas, types, and required fields for specified tools, plus ES2020 scripting constraints.',
39
+ inputSchema: {
40
+ type: 'object',
41
+ properties: {
42
+ tools: {
43
+ type: 'array',
44
+ items: { type: 'string' },
45
+ description: 'Array of tool names to inspect',
46
+ },
47
+ },
48
+ required: ['tools'],
49
+ },
50
+ handler: async (args, context) => {
51
+ const toolNames = args.tools || [];
52
+ if (toolNames.length === 0) {
53
+ return {
54
+ content: [
55
+ {
56
+ type: 'text',
57
+ text: 'No tools specified. Pass an array of tool names to inspect.',
58
+ },
59
+ ],
60
+ };
61
+ }
62
+ // Schemas are as sensitive as the tools themselves, so unauthorized names are
63
+ // reported as not found rather than distinguished from nonexistent ones.
64
+ const authorized = await filterAuthorized(toolNames, context);
65
+ const byName = new Map(authorized.map((tool) => [tool.name, tool]));
66
+ const sections = [];
67
+ for (const name of toolNames) {
68
+ const tool = byName.get(name);
69
+ if (!tool) {
70
+ sections.push(`### ${name}\n*Tool not found.*`);
71
+ continue;
72
+ }
73
+ const mcpTool = await tool.toMcpTool();
74
+ sections.push(`### ${tool.name}\n${tool.description || ''}\n\`\`\`json\n${JSON.stringify(mcpTool.inputSchema, null, 2)}\n\`\`\``);
75
+ }
76
+ return { content: [{ type: 'text', text: sections.join('\n\n') }] };
77
+ },
78
+ });
79
+ // 3. execute meta-tool
80
+ const executeTool = new Tool({
81
+ name: executeName,
82
+ description: 'Executes a JavaScript (ES2020) script within an isolated QuickJS WebAssembly sandbox with async callTool() access.',
83
+ inputSchema: {
84
+ type: 'object',
85
+ properties: {
86
+ code: {
87
+ type: 'string',
88
+ description: 'JavaScript (ES2020) script to execute. Use await callTool(name, args).',
89
+ },
90
+ },
91
+ required: ['code'],
92
+ },
93
+ handler: async (args, context) => {
94
+ const result = await workerPool.executeScript(args.code, limits, context);
95
+ if (!result.success) {
96
+ throw new Error(`Script execution failed: ${result.error}`);
97
+ }
98
+ return {
99
+ content: [
100
+ {
101
+ type: 'text',
102
+ text: typeof result.value === 'string'
103
+ ? result.value
104
+ : JSON.stringify(result.value ?? null, null, 2),
105
+ },
106
+ ],
107
+ };
108
+ },
109
+ });
110
+ return { searchTool, getSchemaTool, executeTool };
111
+ }
112
+ //# sourceMappingURL=synthetic-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"synthetic-tools.js","sourceRoot":"","sources":["../../../../src/core/transforms/code-mode/synthetic-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ export interface ExecutionLimits {
2
+ timeoutMs: number;
3
+ memoryLimitMb: number;
4
+ maxToolCalls: number;
5
+ allowDestructive: boolean;
6
+ }
7
+ export interface SandboxLogEntry {
8
+ level: 'log' | 'warn' | 'error';
9
+ message: string;
10
+ timestamp: number;
11
+ }
12
+ export interface SandboxExecutionResult {
13
+ success: boolean;
14
+ value?: unknown;
15
+ error?: string;
16
+ logs: SandboxLogEntry[];
17
+ toolCallCount: number;
18
+ durationMs: number;
19
+ memoryUsedMb: number;
20
+ }
21
+ export interface CodeModeTransformOptions {
22
+ workerPoolSize?: number;
23
+ memoryLimitMb?: number;
24
+ timeoutMs?: number;
25
+ maxToolCalls?: number;
26
+ allowDestructive?: boolean;
27
+ alwaysVisible?: string[];
28
+ searchToolName?: string;
29
+ getSchemaToolName?: string;
30
+ executeToolName?: string;
31
+ }
32
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/core/transforms/code-mode/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/core/transforms/code-mode/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ import { Tool } from '../../tool.js';
2
+ import { ExecutionContext } from '../../types.js';
3
+ import { ExecutionLimits, SandboxExecutionResult } from './types.js';
4
+ /**
5
+ * Resolves a tool name for a guest `callTool`. Implementations must walk the full
6
+ * transform chain so authorization transforms (e.g. session visibility) still apply
7
+ * to tools invoked from inside the sandbox.
8
+ */
9
+ export type SandboxToolResolver = (name: string, context?: ExecutionContext) => Promise<Tool | undefined>;
10
+ export declare class WorkerPool {
11
+ private readonly poolSize;
12
+ private readonly toolResolver;
13
+ /** Respawn attempts tolerated before the pool disables itself. */
14
+ private static readonly MAX_CONSECUTIVE_CRASHES;
15
+ /** Guest scripts longer than this are rejected before they reach a worker. */
16
+ private static readonly MAX_SCRIPT_CHARS;
17
+ private workers;
18
+ private idleWorkers;
19
+ private activeTasks;
20
+ private taskQueue;
21
+ /** Host-side tool calls still running for a sandbox task. Aborted when that task ends. */
22
+ private inflightCalls;
23
+ /** Calls whose guest is already gone; abort the handler without posting TOOL_RESPONSE. */
24
+ private silentCalls;
25
+ private isDisposed;
26
+ private consecutiveCrashes;
27
+ /** Set when the crash ceiling is breached; the pool stops respawning permanently. */
28
+ private disabledReason;
29
+ /** Workers stopped by the watchdog. Their exit is not a crash and must not respawn again. */
30
+ private readonly intentionalStop;
31
+ private readonly resolvedScriptPath;
32
+ private readonly memoryLimitMb;
33
+ constructor(poolSize: number | undefined, toolResolver: SandboxToolResolver, workerScriptPath?: string, memoryLimitMb?: number);
34
+ private resolveWorkerScriptPath;
35
+ /**
36
+ * Pre-spawns worker threads up to poolSize.
37
+ */
38
+ initialize(): Promise<void>;
39
+ /**
40
+ * Submits a JavaScript script for sandboxed execution in an isolated worker thread.
41
+ */
42
+ executeScript(code: string, limits: ExecutionLimits, context?: ExecutionContext): Promise<SandboxExecutionResult>;
43
+ private spawnWorker;
44
+ private handleWorkerMessage;
45
+ private handleToolRequest;
46
+ private abortInflight;
47
+ private dispatchToolRequest;
48
+ /**
49
+ * A guest that exhausts the worker heap kills the thread. That is the memory
50
+ * limit working, not a broken pool: replace the worker once and do not count
51
+ * it toward the startup-failure latch.
52
+ */
53
+ private handleGuestMemoryLimit;
54
+ private handleWorkerCrash;
55
+ private removeWorker;
56
+ private dispatchNext;
57
+ /**
58
+ * Diagnostic pool statistics.
59
+ */
60
+ getStats(): {
61
+ totalWorkers: number;
62
+ idleWorkers: number;
63
+ activeTasks: number;
64
+ queuedTasks: number;
65
+ };
66
+ /**
67
+ * Gracefully drains task queue and terminates all worker threads.
68
+ */
69
+ dispose(): Promise<void>;
70
+ }
71
+ //# sourceMappingURL=worker-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../../../src/core/transforms/code-mode/worker-pool.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAyBrE;;;;GAIG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,gBAAgB,KACvB,OAAO,CAAC,IAAI,GAAG,SAAS,CAAC,CAAC;AAE/B,qBAAa,UAAU;IAwBnB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAxB/B,kEAAkE;IAClE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IACpD,8EAA8E;IAC9E,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAW;IAEnD,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,SAAS,CAAoB;IACrC,0FAA0F;IAC1F,OAAO,CAAC,aAAa,CAAwC;IAC7D,0FAA0F;IAC1F,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,kBAAkB,CAAK;IAC/B,qFAAqF;IACrF,OAAO,CAAC,cAAc,CAAsB;IAC5C,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;gBAGpB,QAAQ,EAAE,MAAM,YAAI,EACpB,YAAY,EAAE,mBAAmB,EAClD,gBAAgB,CAAC,EAAE,MAAM,EACzB,aAAa,GAAE,MAAY;IAM7B,OAAO,CAAC,uBAAuB;IAwC/B;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAMjC;;OAEG;IACG,aAAa,CACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,eAAe,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,sBAAsB,CAAC;IAwBlC,OAAO,CAAC,WAAW;IAsDnB,OAAO,CAAC,mBAAmB;IAuC3B,OAAO,CAAC,iBAAiB;IAqBzB,OAAO,CAAC,aAAa;YASP,mBAAmB;IA8FjC;;;;OAIG;IACH,OAAO,CAAC,sBAAsB;IAsB9B,OAAO,CAAC,iBAAiB;IAiCzB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,YAAY;IAuDpB;;OAEG;IACH,QAAQ;;;;;;IASR;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAgC/B"}
@@ -0,0 +1,435 @@
1
+ import { Worker } from 'node:worker_threads';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs';
5
+ import { CatalogTransform } from '../catalog.transform.js';
6
+ import { formatLegibleToolError } from './legible-error.js';
7
+ import { assertToolAllowed } from './destructive-guard.js';
8
+ import { validateToolArguments } from '../validate-tool-arguments.js';
9
+ export class WorkerPool {
10
+ poolSize;
11
+ toolResolver;
12
+ /** Respawn attempts tolerated before the pool disables itself. */
13
+ static MAX_CONSECUTIVE_CRASHES = 5;
14
+ /** Guest scripts longer than this are rejected before they reach a worker. */
15
+ static MAX_SCRIPT_CHARS = 100_000;
16
+ workers = [];
17
+ idleWorkers = [];
18
+ activeTasks = new Map();
19
+ taskQueue = [];
20
+ /** Host-side tool calls still running for a sandbox task. Aborted when that task ends. */
21
+ inflightCalls = new Map();
22
+ /** Calls whose guest is already gone; abort the handler without posting TOOL_RESPONSE. */
23
+ silentCalls = new Set();
24
+ isDisposed = false;
25
+ consecutiveCrashes = 0;
26
+ /** Set when the crash ceiling is breached; the pool stops respawning permanently. */
27
+ disabledReason = null;
28
+ /** Workers stopped by the watchdog. Their exit is not a crash and must not respawn again. */
29
+ intentionalStop = new WeakSet();
30
+ resolvedScriptPath;
31
+ memoryLimitMb;
32
+ constructor(poolSize = 4, toolResolver, workerScriptPath, memoryLimitMb = 100) {
33
+ this.poolSize = poolSize;
34
+ this.toolResolver = toolResolver;
35
+ this.memoryLimitMb = memoryLimitMb;
36
+ this.resolvedScriptPath = this.resolveWorkerScriptPath(workerScriptPath);
37
+ }
38
+ resolveWorkerScriptPath(customPath) {
39
+ if (customPath && fs.existsSync(customPath)) {
40
+ return customPath;
41
+ }
42
+ // 1. Check relative to compiled module (e.g. dist/core/transforms/code-mode/sandbox.worker.js)
43
+ try {
44
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
45
+ const localJs = path.join(currentDir, 'sandbox.worker.js');
46
+ if (fs.existsSync(localJs)) {
47
+ return localJs;
48
+ }
49
+ }
50
+ catch {
51
+ // ignore import.meta.url failure in some bundlers
52
+ }
53
+ // 2. Check process.cwd() dist path
54
+ const cwdDist = path.resolve(process.cwd(), 'dist/core/transforms/code-mode/sandbox.worker.js');
55
+ if (fs.existsSync(cwdDist)) {
56
+ return cwdDist;
57
+ }
58
+ // 3. Fallback to package root dist path
59
+ const packageDist = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../../dist/core/transforms/code-mode/sandbox.worker.js');
60
+ if (fs.existsSync(packageDist)) {
61
+ return packageDist;
62
+ }
63
+ // Fail here rather than returning an unverified path: every spawn would fail
64
+ // asynchronously, and each failure triggers a replacement spawn.
65
+ throw new Error(`Code Mode sandbox worker script not found (looked for sandbox.worker.js next to the ` +
66
+ `compiled module, under ${process.cwd()}/dist, and at ${packageDist}). ` +
67
+ `Build @nitrostack/core or pass an explicit workerScriptPath.`);
68
+ }
69
+ /**
70
+ * Pre-spawns worker threads up to poolSize.
71
+ */
72
+ async initialize() {
73
+ while (this.workers.length < this.poolSize) {
74
+ this.spawnWorker();
75
+ }
76
+ }
77
+ /**
78
+ * Submits a JavaScript script for sandboxed execution in an isolated worker thread.
79
+ */
80
+ async executeScript(code, limits, context) {
81
+ if (this.isDisposed) {
82
+ throw new Error('WorkerPool is already disposed');
83
+ }
84
+ if (this.disabledReason) {
85
+ throw this.disabledReason;
86
+ }
87
+ if (code.length > WorkerPool.MAX_SCRIPT_CHARS) {
88
+ throw new Error(`Script exceeds the maximum length of ${WorkerPool.MAX_SCRIPT_CHARS} characters`);
89
+ }
90
+ const queueCap = Math.max(this.poolSize, 1) * 8;
91
+ if (this.taskQueue.length >= queueCap) {
92
+ throw new Error('Code Mode sandbox queue is full');
93
+ }
94
+ return new Promise((resolve, reject) => {
95
+ const taskId = crypto.randomUUID();
96
+ this.taskQueue.push({ taskId, code, limits, context, resolve, reject });
97
+ this.dispatchNext();
98
+ });
99
+ }
100
+ spawnWorker() {
101
+ const execArgv = process.execArgv.filter((arg, i, arr) => arg !== '-e' &&
102
+ arg !== '--eval' &&
103
+ arg !== '-p' &&
104
+ arg !== '--print' &&
105
+ arr[i - 1] !== '-e' &&
106
+ arr[i - 1] !== '--eval' &&
107
+ arr[i - 1] !== '-p' &&
108
+ arr[i - 1] !== '--print');
109
+ const worker = new Worker(this.resolvedScriptPath, {
110
+ execArgv,
111
+ resourceLimits: {
112
+ maxOldGenerationSizeMb: this.memoryLimitMb,
113
+ },
114
+ });
115
+ this.workers.push(worker);
116
+ this.idleWorkers.push(worker);
117
+ worker.on('message', (msg) => {
118
+ this.handleWorkerMessage(worker, msg);
119
+ });
120
+ // A failing worker emits both 'error' and 'exit'. Without this latch each crash
121
+ // would be handled twice and spawn two replacements, growing the pool unbounded.
122
+ let crashHandled = false;
123
+ const onCrash = (err) => {
124
+ if (crashHandled)
125
+ return;
126
+ crashHandled = true;
127
+ this.handleWorkerCrash(worker, err);
128
+ };
129
+ worker.on('error', (err) => {
130
+ if (err?.code === 'ERR_WORKER_OUT_OF_MEMORY') {
131
+ this.handleGuestMemoryLimit(worker);
132
+ return;
133
+ }
134
+ onCrash(err);
135
+ });
136
+ worker.on('exit', (code) => {
137
+ if (this.intentionalStop.delete(worker))
138
+ return;
139
+ if (!this.isDisposed) {
140
+ onCrash(new Error(`Worker stopped with exit code ${code}`));
141
+ }
142
+ });
143
+ return worker;
144
+ }
145
+ handleWorkerMessage(worker, msg) {
146
+ const active = this.activeTasks.get(worker);
147
+ if (!active)
148
+ return;
149
+ // A late message from the previous script must not run against the next task.
150
+ if (msg.taskId !== active.task.taskId)
151
+ return;
152
+ if (msg.type === 'TOOL_REQUEST') {
153
+ this.handleToolRequest(worker, msg, active.task.limits, active.task.context);
154
+ return;
155
+ }
156
+ if (msg.type === 'EXECUTION_COMPLETE') {
157
+ clearTimeout(active.watchdogTimer);
158
+ this.abortInflight(active.task.taskId);
159
+ this.activeTasks.delete(worker);
160
+ this.idleWorkers.push(worker);
161
+ this.consecutiveCrashes = 0;
162
+ active.task.resolve(msg.result);
163
+ this.dispatchNext();
164
+ return;
165
+ }
166
+ if (msg.type === 'EXECUTION_FAILED') {
167
+ clearTimeout(active.watchdogTimer);
168
+ this.abortInflight(active.task.taskId);
169
+ this.activeTasks.delete(worker);
170
+ this.idleWorkers.push(worker);
171
+ active.task.resolve({
172
+ success: false,
173
+ error: msg.error,
174
+ logs: [],
175
+ toolCallCount: 0,
176
+ durationMs: 0,
177
+ memoryUsedMb: 0,
178
+ });
179
+ this.dispatchNext();
180
+ }
181
+ }
182
+ handleToolRequest(worker, msg, limits, context) {
183
+ const respond = (payload) => {
184
+ if (this.silentCalls.delete(msg.callId))
185
+ return;
186
+ worker.postMessage({
187
+ type: 'TOOL_RESPONSE',
188
+ taskId: msg.taskId,
189
+ callId: msg.callId,
190
+ ...payload,
191
+ });
192
+ };
193
+ void this.dispatchToolRequest(msg, limits, context).then(respond, (err) => respond({ error: err instanceof Error ? err.message : String(err) }));
194
+ }
195
+ abortInflight(taskId) {
196
+ for (const [callId, inflight] of this.inflightCalls) {
197
+ if (inflight.taskId !== taskId)
198
+ continue;
199
+ this.inflightCalls.delete(callId);
200
+ this.silentCalls.add(callId);
201
+ inflight.controller.abort();
202
+ }
203
+ }
204
+ async dispatchToolRequest(msg, limits, context) {
205
+ // Register before any await so a timeout during resolution still aborts the call.
206
+ const controller = new AbortController();
207
+ this.inflightCalls.set(msg.callId, { taskId: msg.taskId, controller });
208
+ const interrupted = 'callTool interrupted: script execution ended';
209
+ const stop = (payload) => {
210
+ this.inflightCalls.delete(msg.callId);
211
+ return payload;
212
+ };
213
+ if (controller.signal.aborted || this.silentCalls.has(msg.callId)) {
214
+ return stop({ error: interrupted });
215
+ }
216
+ // Resolution runs OUTSIDE withBypass so authorization transforms (session
217
+ // visibility) still gate tools reached from inside the sandbox. A guest script
218
+ // must not be able to call what its session is not allowed to call.
219
+ let tool;
220
+ try {
221
+ tool = await this.toolResolver(msg.toolName, context);
222
+ }
223
+ catch (err) {
224
+ // A transform denied resolution (e.g. VisibilityResolutionError). Surface it to
225
+ // the guest as a rejected callTool rather than failing the whole script.
226
+ return stop({ error: err instanceof Error ? err.message : String(err) });
227
+ }
228
+ if (controller.signal.aborted || this.silentCalls.has(msg.callId)) {
229
+ return stop({ error: interrupted });
230
+ }
231
+ if (!tool) {
232
+ return stop({
233
+ error: `Tool '${msg.toolName}' not found. Use search to discover valid tools.`,
234
+ });
235
+ }
236
+ let parsedArgs = {};
237
+ try {
238
+ assertToolAllowed(tool, limits.allowDestructive);
239
+ parsedArgs = validateToolArguments(tool, msg.args ?? {});
240
+ }
241
+ catch (err) {
242
+ return stop({ error: err instanceof Error ? err.message : String(err) });
243
+ }
244
+ if (controller.signal.aborted || this.silentCalls.has(msg.callId)) {
245
+ return stop({ error: interrupted });
246
+ }
247
+ const signals = [controller.signal];
248
+ if (context?.abortSignal)
249
+ signals.push(context.abortSignal);
250
+ const abortSignal = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
251
+ const execContext = {
252
+ ...(context ?? { metadata: {} }),
253
+ abortSignal,
254
+ };
255
+ return new Promise((resolve) => {
256
+ let finished = false;
257
+ const finish = (payload) => {
258
+ if (finished)
259
+ return;
260
+ finished = true;
261
+ this.inflightCalls.delete(msg.callId);
262
+ resolve(payload);
263
+ };
264
+ if (abortSignal.aborted) {
265
+ finish({ error: interrupted });
266
+ return;
267
+ }
268
+ abortSignal.addEventListener('abort', () => finish({ error: interrupted }), { once: true });
269
+ void (async () => {
270
+ try {
271
+ // withBypass covers catalog listing inside the handler so it does not
272
+ // re-enter the pipeline. Authorization transforms still run: they do not
273
+ // treat the bypass flag as permission to skip resolveTool.
274
+ const result = await CatalogTransform.withBypass(() => tool.execute(parsedArgs, execContext));
275
+ if (abortSignal.aborted) {
276
+ finish({ error: interrupted });
277
+ return;
278
+ }
279
+ finish({ result });
280
+ }
281
+ catch (err) {
282
+ finish({ error: await formatLegibleToolError(tool, err) });
283
+ }
284
+ })();
285
+ });
286
+ }
287
+ /**
288
+ * A guest that exhausts the worker heap kills the thread. That is the memory
289
+ * limit working, not a broken pool: replace the worker once and do not count
290
+ * it toward the startup-failure latch.
291
+ */
292
+ handleGuestMemoryLimit(worker) {
293
+ if (this.intentionalStop.has(worker))
294
+ return;
295
+ this.intentionalStop.add(worker);
296
+ const active = this.activeTasks.get(worker);
297
+ if (active) {
298
+ clearTimeout(active.watchdogTimer);
299
+ this.abortInflight(active.task.taskId);
300
+ this.activeTasks.delete(worker);
301
+ active.task.reject(new Error(`Script exceeded the sandbox memory limit of ${this.memoryLimitMb}MB`));
302
+ }
303
+ this.removeWorker(worker);
304
+ void worker.terminate().catch(() => undefined);
305
+ if (this.isDisposed || this.disabledReason)
306
+ return;
307
+ this.spawnWorker();
308
+ this.dispatchNext();
309
+ }
310
+ handleWorkerCrash(worker, err) {
311
+ const active = this.activeTasks.get(worker);
312
+ if (active) {
313
+ clearTimeout(active.watchdogTimer);
314
+ this.abortInflight(active.task.taskId);
315
+ this.activeTasks.delete(worker);
316
+ active.task.reject(err);
317
+ }
318
+ this.removeWorker(worker);
319
+ if (this.isDisposed || this.disabledReason) {
320
+ return;
321
+ }
322
+ // A worker that cannot start (bad script path, unsupported runtime) crashes on
323
+ // every respawn. Give up rather than spin.
324
+ if (++this.consecutiveCrashes > WorkerPool.MAX_CONSECUTIVE_CRASHES) {
325
+ this.disabledReason = new Error(`Code Mode sandbox disabled after ${WorkerPool.MAX_CONSECUTIVE_CRASHES} consecutive ` +
326
+ `worker failures. Last error: ${err.message}`);
327
+ for (const task of this.taskQueue) {
328
+ task.reject(this.disabledReason);
329
+ }
330
+ this.taskQueue = [];
331
+ return;
332
+ }
333
+ this.spawnWorker();
334
+ this.dispatchNext();
335
+ }
336
+ removeWorker(worker) {
337
+ const idx = this.workers.indexOf(worker);
338
+ if (idx !== -1) {
339
+ this.workers.splice(idx, 1);
340
+ }
341
+ const idleIdx = this.idleWorkers.indexOf(worker);
342
+ if (idleIdx !== -1) {
343
+ this.idleWorkers.splice(idleIdx, 1);
344
+ }
345
+ }
346
+ dispatchNext() {
347
+ if (this.isDisposed || this.taskQueue.length === 0) {
348
+ return;
349
+ }
350
+ // If no idle worker available, check if we can spawn up to poolSize
351
+ if (this.idleWorkers.length === 0) {
352
+ if (this.workers.length < this.poolSize) {
353
+ this.spawnWorker();
354
+ }
355
+ else {
356
+ return; // All workers are busy; wait for a task to finish
357
+ }
358
+ }
359
+ const worker = this.idleWorkers.shift();
360
+ const task = this.taskQueue.shift();
361
+ if (!worker || !task) {
362
+ return;
363
+ }
364
+ // Tier 2 Timeout Watchdog: host deadline is limits.timeoutMs + 2000
365
+ const watchdogTimeoutMs = task.limits.timeoutMs + 2000;
366
+ const watchdogTimer = setTimeout(async () => {
367
+ this.intentionalStop.add(worker);
368
+ this.abortInflight(task.taskId);
369
+ this.activeTasks.delete(worker);
370
+ this.removeWorker(worker);
371
+ try {
372
+ await worker.terminate();
373
+ }
374
+ catch {
375
+ // Ignore termination failure
376
+ }
377
+ task.reject(new Error('Worker timed out and was terminated (Watchdog deadline exceeded)'));
378
+ if (!this.isDisposed) {
379
+ this.spawnWorker();
380
+ this.dispatchNext();
381
+ }
382
+ }, watchdogTimeoutMs);
383
+ this.activeTasks.set(worker, { task, watchdogTimer });
384
+ worker.postMessage({
385
+ type: 'EXECUTE',
386
+ taskId: task.taskId,
387
+ code: task.code,
388
+ limits: task.limits,
389
+ });
390
+ }
391
+ /**
392
+ * Diagnostic pool statistics.
393
+ */
394
+ getStats() {
395
+ return {
396
+ totalWorkers: this.workers.length,
397
+ idleWorkers: this.idleWorkers.length,
398
+ activeTasks: this.activeTasks.size,
399
+ queuedTasks: this.taskQueue.length,
400
+ };
401
+ }
402
+ /**
403
+ * Gracefully drains task queue and terminates all worker threads.
404
+ */
405
+ async dispose() {
406
+ this.isDisposed = true;
407
+ for (const task of this.taskQueue) {
408
+ task.reject(new Error('WorkerPool disposed'));
409
+ }
410
+ this.taskQueue = [];
411
+ for (const active of this.activeTasks.values()) {
412
+ clearTimeout(active.watchdogTimer);
413
+ this.abortInflight(active.task.taskId);
414
+ active.task.reject(new Error('WorkerPool disposed'));
415
+ }
416
+ for (const inflight of this.inflightCalls.values()) {
417
+ inflight.controller.abort();
418
+ }
419
+ this.inflightCalls.clear();
420
+ this.activeTasks.clear();
421
+ const terminations = this.workers.map(async (w) => {
422
+ try {
423
+ w.postMessage({ type: 'SHUTDOWN' });
424
+ await w.terminate();
425
+ }
426
+ catch {
427
+ // Ignore termination errors during disposal
428
+ }
429
+ });
430
+ this.workers = [];
431
+ this.idleWorkers = [];
432
+ await Promise.all(terminations);
433
+ }
434
+ }
435
+ //# sourceMappingURL=worker-pool.js.map