@cogitator-ai/self-modifying 17.0.17 → 18.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 (114) hide show
  1. package/README.md +112 -48
  2. package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
  3. package/dist/architecture-evolution/capability-analyzer.js +44 -40
  4. package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
  5. package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
  6. package/dist/architecture-evolution/evolution-strategy.js +4 -2
  7. package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
  8. package/dist/architecture-evolution/index.d.ts +2 -2
  9. package/dist/architecture-evolution/index.d.ts.map +1 -1
  10. package/dist/architecture-evolution/index.js +1 -1
  11. package/dist/architecture-evolution/index.js.map +1 -1
  12. package/dist/architecture-evolution/parameter-optimizer.d.ts +5 -4
  13. package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
  14. package/dist/architecture-evolution/parameter-optimizer.js +62 -34
  15. package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
  16. package/dist/architecture-evolution/prompts.d.ts +21 -2
  17. package/dist/architecture-evolution/prompts.d.ts.map +1 -1
  18. package/dist/architecture-evolution/prompts.js +189 -37
  19. package/dist/architecture-evolution/prompts.js.map +1 -1
  20. package/dist/constraints/modification-validator.d.ts +1 -0
  21. package/dist/constraints/modification-validator.d.ts.map +1 -1
  22. package/dist/constraints/modification-validator.js +45 -18
  23. package/dist/constraints/modification-validator.js.map +1 -1
  24. package/dist/constraints/rollback-manager.d.ts +1 -0
  25. package/dist/constraints/rollback-manager.d.ts.map +1 -1
  26. package/dist/constraints/rollback-manager.js +25 -1
  27. package/dist/constraints/rollback-manager.js.map +1 -1
  28. package/dist/events/event-emitter.d.ts +0 -1
  29. package/dist/events/event-emitter.d.ts.map +1 -1
  30. package/dist/events/event-emitter.js +6 -10
  31. package/dist/events/event-emitter.js.map +1 -1
  32. package/dist/index.d.ts +4 -4
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +3 -3
  35. package/dist/index.js.map +1 -1
  36. package/dist/meta-reasoning/meta-reasoner.d.ts +4 -1
  37. package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
  38. package/dist/meta-reasoning/meta-reasoner.js +120 -43
  39. package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
  40. package/dist/meta-reasoning/observation-collector.d.ts.map +1 -1
  41. package/dist/meta-reasoning/observation-collector.js +6 -2
  42. package/dist/meta-reasoning/observation-collector.js.map +1 -1
  43. package/dist/meta-reasoning/prompts.d.ts +10 -14
  44. package/dist/meta-reasoning/prompts.d.ts.map +1 -1
  45. package/dist/meta-reasoning/prompts.js +108 -33
  46. package/dist/meta-reasoning/prompts.js.map +1 -1
  47. package/dist/meta-reasoning/strategy-selector.d.ts.map +1 -1
  48. package/dist/meta-reasoning/strategy-selector.js +6 -0
  49. package/dist/meta-reasoning/strategy-selector.js.map +1 -1
  50. package/dist/self-modifying-agent.d.ts +88 -15
  51. package/dist/self-modifying-agent.d.ts.map +1 -1
  52. package/dist/self-modifying-agent.js +583 -376
  53. package/dist/self-modifying-agent.js.map +1 -1
  54. package/dist/tool-generation/gap-analyzer.d.ts +0 -1
  55. package/dist/tool-generation/gap-analyzer.d.ts.map +1 -1
  56. package/dist/tool-generation/gap-analyzer.js +16 -15
  57. package/dist/tool-generation/gap-analyzer.js.map +1 -1
  58. package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
  59. package/dist/tool-generation/generated-tool-store.js +90 -10
  60. package/dist/tool-generation/generated-tool-store.js.map +1 -1
  61. package/dist/tool-generation/index.d.ts +3 -3
  62. package/dist/tool-generation/index.d.ts.map +1 -1
  63. package/dist/tool-generation/index.js +2 -2
  64. package/dist/tool-generation/index.js.map +1 -1
  65. package/dist/tool-generation/prompts.d.ts +4 -3
  66. package/dist/tool-generation/prompts.d.ts.map +1 -1
  67. package/dist/tool-generation/prompts.js +96 -18
  68. package/dist/tool-generation/prompts.js.map +1 -1
  69. package/dist/tool-generation/tool-generator.d.ts +6 -6
  70. package/dist/tool-generation/tool-generator.d.ts.map +1 -1
  71. package/dist/tool-generation/tool-generator.js +88 -23
  72. package/dist/tool-generation/tool-generator.js.map +1 -1
  73. package/dist/tool-generation/tool-sandbox.d.ts +10 -11
  74. package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
  75. package/dist/tool-generation/tool-sandbox.js +346 -156
  76. package/dist/tool-generation/tool-sandbox.js.map +1 -1
  77. package/dist/tool-generation/tool-validator.d.ts +4 -8
  78. package/dist/tool-generation/tool-validator.d.ts.map +1 -1
  79. package/dist/tool-generation/tool-validator.js +74 -28
  80. package/dist/tool-generation/tool-validator.js.map +1 -1
  81. package/dist/utils/index.d.ts.map +1 -1
  82. package/dist/utils/index.js +30 -5
  83. package/dist/utils/index.js.map +1 -1
  84. package/dist/utils/llm-helper.d.ts.map +1 -1
  85. package/dist/utils/llm-helper.js +1 -0
  86. package/dist/utils/llm-helper.js.map +1 -1
  87. package/package.json +3 -12
  88. package/src/__tests__/component-regressions.test.ts +599 -0
  89. package/src/__tests__/meta-reasoning.test.ts +5 -1
  90. package/src/__tests__/sandbox-security.test.ts +186 -0
  91. package/src/__tests__/self-modifying-agent.test.ts +405 -11
  92. package/src/architecture-evolution/capability-analyzer.ts +63 -44
  93. package/src/architecture-evolution/evolution-strategy.ts +5 -2
  94. package/src/architecture-evolution/index.ts +2 -0
  95. package/src/architecture-evolution/parameter-optimizer.ts +82 -40
  96. package/src/architecture-evolution/prompts.ts +224 -42
  97. package/src/constraints/modification-validator.ts +48 -21
  98. package/src/constraints/rollback-manager.ts +20 -1
  99. package/src/events/event-emitter.ts +6 -15
  100. package/src/index.ts +14 -0
  101. package/src/meta-reasoning/meta-reasoner.ts +124 -52
  102. package/src/meta-reasoning/observation-collector.ts +6 -2
  103. package/src/meta-reasoning/prompts.ts +131 -56
  104. package/src/meta-reasoning/strategy-selector.ts +6 -0
  105. package/src/self-modifying-agent.ts +797 -413
  106. package/src/tool-generation/gap-analyzer.ts +21 -19
  107. package/src/tool-generation/generated-tool-store.ts +97 -10
  108. package/src/tool-generation/index.ts +13 -3
  109. package/src/tool-generation/prompts.ts +120 -18
  110. package/src/tool-generation/tool-generator.ts +120 -26
  111. package/src/tool-generation/tool-sandbox.ts +408 -206
  112. package/src/tool-generation/tool-validator.ts +90 -37
  113. package/src/utils/index.ts +29 -5
  114. package/src/utils/llm-helper.ts +1 -0
@@ -1,3 +1,4 @@
1
+ import { Worker } from 'node:worker_threads';
1
2
  import type { ToolSandboxConfig, ToolSandboxResult, GeneratedTool } from '@cogitator-ai/types';
2
3
 
3
4
  export const DEFAULT_SANDBOX_CONFIG: ToolSandboxConfig = {
@@ -8,79 +9,156 @@ export const DEFAULT_SANDBOX_CONFIG: ToolSandboxConfig = {
8
9
  isolationLevel: 'strict',
9
10
  };
10
11
 
11
- interface SandboxContext {
12
- console: {
13
- log: (...args: unknown[]) => void;
14
- warn: (...args: unknown[]) => void;
15
- error: (...args: unknown[]) => void;
16
- };
17
- Math: typeof Math;
18
- JSON: typeof JSON;
19
- Date: typeof Date;
20
- Array: typeof Array;
21
- Object: typeof Object;
22
- String: typeof String;
23
- Number: typeof Number;
24
- Boolean: typeof Boolean;
25
- RegExp: typeof RegExp;
26
- Map: typeof Map;
27
- Set: typeof Set;
28
- Promise: typeof Promise;
29
- Error: typeof Error;
30
- TypeError: typeof TypeError;
31
- RangeError: typeof RangeError;
12
+ const WORKER_SOURCE = String.raw`
13
+ const { parentPort, workerData } = require('node:worker_threads');
14
+ const vm = require('node:vm');
15
+ const { code, paramsJson, timeout } = workerData;
16
+
17
+ const BOOTSTRAP = [
18
+ '(function () {',
19
+ ' "use strict";',
20
+ ' const logs = [];',
21
+ ' const format = (args) => args.map((v) => {',
22
+ ' if (typeof v === "string") return v;',
23
+ ' try { const s = JSON.stringify(v); return s === undefined ? String(v) : s; } catch (e) { return String(v); }',
24
+ ' }).join(" ");',
25
+ ' const logger = (level) => (...args) => { if (logs.length < 1000) logs.push("[" + level + "] " + format(args)); };',
26
+ ' Object.defineProperty(globalThis, "console", { value: Object.freeze({',
27
+ ' log: logger("LOG"), info: logger("INFO"), debug: logger("DEBUG"), warn: logger("WARN"), error: logger("ERROR"),',
28
+ ' }) });',
29
+ ' Object.defineProperty(globalThis, "__sandboxState", { value: { status: "pending", payload: "", hasValue: false, logs } });',
30
+ '})();',
31
+ ].join('\n');
32
+
33
+ const runner = [
34
+ '(async function sandboxedExecution(params) {',
35
+ code,
36
+ ' if (typeof execute !== "function") { throw new Error("Implementation must define an execute function"); }',
37
+ ' return await execute(params);',
38
+ '})(JSON.parse(' + JSON.stringify(paramsJson) + ')).then(',
39
+ ' (value) => {',
40
+ ' const state = globalThis.__sandboxState;',
41
+ ' try {',
42
+ ' const json = JSON.stringify(value);',
43
+ ' state.hasValue = json !== undefined;',
44
+ ' state.payload = json === undefined ? "" : json;',
45
+ ' state.status = "fulfilled";',
46
+ ' } catch (e) {',
47
+ ' state.payload = "Tool returned a non-serializable value: " + (e && e.message);',
48
+ ' state.status = "failed";',
49
+ ' }',
50
+ ' },',
51
+ ' (err) => {',
52
+ ' const state = globalThis.__sandboxState;',
53
+ ' state.payload = err && typeof err.message === "string" ? err.message : String(err);',
54
+ ' state.status = "rejected";',
55
+ ' }',
56
+ ');',
57
+ ].join('\n');
58
+
59
+ const asString = (value) => (typeof value === 'string' ? value : '');
60
+ const readState = (context, expression) => {
61
+ try {
62
+ return vm.runInContext(expression, context);
63
+ } catch {
64
+ return undefined;
65
+ }
66
+ };
67
+ const readLogs = (context) => {
68
+ const raw = readState(context, 'JSON.stringify(__sandboxState.logs)');
69
+ if (typeof raw !== 'string') return [];
70
+ try {
71
+ const parsed = JSON.parse(raw);
72
+ return Array.isArray(parsed) ? parsed.filter((l) => typeof l === 'string') : [];
73
+ } catch {
74
+ return [];
75
+ }
76
+ };
77
+
78
+ const context = vm.createContext(Object.create(null), {
79
+ codeGeneration: { strings: false, wasm: false },
80
+ });
81
+
82
+ let started = false;
83
+ try {
84
+ vm.runInContext(BOOTSTRAP, context);
85
+ vm.runInContext(runner, context, { timeout });
86
+ started = true;
87
+ } catch (err) {
88
+ parentPort.postMessage({
89
+ success: false,
90
+ thrown: true,
91
+ error: err && typeof err.message === 'string' ? err.message : String(err),
92
+ logs: readLogs(context),
93
+ });
94
+ }
95
+
96
+ if (started) setImmediate(() => {
97
+ const status = asString(readState(context, '__sandboxState.status'));
98
+ const payload = asString(readState(context, '__sandboxState.payload'));
99
+ const logs = readLogs(context);
100
+
101
+ if (status === 'fulfilled') {
102
+ let result;
103
+ if (readState(context, '__sandboxState.hasValue') === true) {
104
+ try {
105
+ result = JSON.parse(payload);
106
+ } catch {
107
+ parentPort.postMessage({ success: false, thrown: false, error: 'Tool returned an unreadable value', logs });
108
+ return;
109
+ }
110
+ }
111
+ parentPort.postMessage({ success: true, result, logs });
112
+ } else if (status === 'rejected') {
113
+ parentPort.postMessage({ success: false, thrown: true, error: payload, logs });
114
+ } else if (status === 'failed') {
115
+ parentPort.postMessage({ success: false, thrown: false, error: payload, logs });
116
+ } else {
117
+ parentPort.postMessage({
118
+ success: false,
119
+ thrown: false,
120
+ error: 'Tool execution never settled: execute() returned a promise that cannot resolve inside the sandbox',
121
+ logs,
122
+ });
123
+ }
124
+ });
125
+ `;
126
+
127
+ interface WorkerMessage {
128
+ success: boolean;
129
+ result?: unknown;
130
+ error?: string;
131
+ thrown?: boolean;
132
+ logs?: string[];
133
+ }
134
+
135
+ interface SandboxRun {
136
+ result: ToolSandboxResult;
137
+ thrownByTool: boolean;
138
+ }
139
+
140
+ export interface SandboxTestCase {
141
+ input: unknown;
142
+ expectedOutput?: unknown;
143
+ shouldThrow?: boolean;
144
+ allowThrow?: boolean;
32
145
  }
33
146
 
34
147
  export class ToolSandbox {
35
148
  private readonly config: ToolSandboxConfig;
36
- private readonly logs: string[] = [];
37
149
 
38
150
  constructor(config: Partial<ToolSandboxConfig> = {}) {
39
151
  this.config = { ...DEFAULT_SANDBOX_CONFIG, ...config };
40
152
  }
41
153
 
42
154
  async execute(tool: GeneratedTool, params: unknown): Promise<ToolSandboxResult> {
43
- const startTime = Date.now();
44
- this.logs.length = 0;
45
-
46
- if (!this.config.enabled) {
47
- return this.executeUnsandboxed(tool, params, startTime);
48
- }
49
-
50
- try {
51
- this.validateImplementation(tool.implementation);
52
-
53
- const context = this.createContext();
54
- const wrappedCode = this.wrapImplementation(tool.implementation);
55
-
56
- const result = await this.executeWithTimeout(
57
- wrappedCode,
58
- context,
59
- params,
60
- this.config.maxExecutionTime
61
- );
62
-
63
- return {
64
- success: true,
65
- result,
66
- executionTime: Date.now() - startTime,
67
- memoryUsed: this.estimateMemoryUsage(result),
68
- logs: [...this.logs],
69
- };
70
- } catch (error) {
71
- return {
72
- success: false,
73
- error: error instanceof Error ? error.message : String(error),
74
- executionTime: Date.now() - startTime,
75
- memoryUsed: 0,
76
- logs: [...this.logs],
77
- };
78
- }
155
+ const run = await this.run(tool, params);
156
+ return run.result;
79
157
  }
80
158
 
81
159
  async testWithCases(
82
160
  tool: GeneratedTool,
83
- testCases: Array<{ input: unknown; expectedOutput?: unknown; shouldThrow?: boolean }>
161
+ testCases: SandboxTestCase[]
84
162
  ): Promise<{
85
163
  passed: number;
86
164
  failed: number;
@@ -101,13 +179,15 @@ export class ToolSandbox {
101
179
  }> = [];
102
180
 
103
181
  for (const testCase of testCases) {
104
- const execResult = await this.execute(tool, testCase.input);
182
+ const { result: execResult, thrownByTool } = await this.run(tool, testCase.input);
105
183
 
106
- let passed = false;
184
+ let passed: boolean;
107
185
  if (testCase.shouldThrow) {
108
- passed = !execResult.success;
186
+ passed = !execResult.success && thrownByTool;
109
187
  } else if (testCase.expectedOutput !== undefined) {
110
- passed = execResult.success && this.deepEqual(execResult.result, testCase.expectedOutput);
188
+ passed = execResult.success && deepEqual(execResult.result, testCase.expectedOutput);
189
+ } else if (testCase.allowThrow) {
190
+ passed = execResult.success || thrownByTool;
111
191
  } else {
112
192
  passed = execResult.success;
113
193
  }
@@ -128,45 +208,101 @@ export class ToolSandbox {
128
208
  };
129
209
  }
130
210
 
211
+ private async run(tool: GeneratedTool, params: unknown): Promise<SandboxRun> {
212
+ const startTime = Date.now();
213
+
214
+ try {
215
+ this.validateImplementation(tool.implementation);
216
+ } catch (error) {
217
+ return {
218
+ result: failure(error instanceof Error ? error.message : String(error), startTime),
219
+ thrownByTool: false,
220
+ };
221
+ }
222
+
223
+ let paramsJson: string;
224
+ try {
225
+ paramsJson = JSON.stringify(params ?? null);
226
+ } catch (error) {
227
+ return {
228
+ result: failure(
229
+ `Parameters must be JSON-serializable: ${error instanceof Error ? error.message : String(error)}`,
230
+ startTime
231
+ ),
232
+ thrownByTool: false,
233
+ };
234
+ }
235
+
236
+ if (!this.config.enabled) {
237
+ return this.executeUnsandboxed(tool, JSON.parse(paramsJson) as unknown, startTime);
238
+ }
239
+
240
+ return this.executeInWorker(tool.implementation, paramsJson, startTime);
241
+ }
242
+
131
243
  private validateImplementation(code: string): void {
132
- const forbidden = [
133
- /\beval\s*\(/,
134
- /\bFunction\s*\(/,
135
- /\bnew\s+Function\s*\(/,
136
- /\bimport\s*\(/,
137
- /\brequire\s*\(/,
138
- /\bprocess\./,
139
- /\bglobal\./,
140
- /\bglobalThis\./,
141
- /\bwindow\./,
142
- /\bdocument\./,
143
- /\bchild_process/,
144
- /\bfs\./,
145
- /\bhttp\./,
146
- /\bhttps\./,
147
- /\bnet\./,
148
- /\bdns\./,
149
- /\bos\./,
150
- /\bexec\s*\(/,
151
- /\bspawn\s*\(/,
152
- /__proto__/,
153
- /\bconstructor\b/,
154
- /\bgetPrototypeOf\b/,
244
+ const forbidden: Array<{ pattern: RegExp; label: string }> = [
245
+ { pattern: /\beval\s*\(/, label: 'eval()' },
246
+ { pattern: /\bnew\s+Function\s*\(/, label: 'new Function()' },
247
+ { pattern: /\bFunction\s*\.\s*prototype/, label: 'Function.prototype' },
248
+ { pattern: /\bimport\s*\(/, label: 'dynamic import()' },
249
+ { pattern: /\brequire\s*\(/, label: 'require()' },
250
+ { pattern: /\bprocess\s*[[.]/, label: 'process access' },
251
+ { pattern: /\bglobal\s*[[.]/, label: 'global access' },
252
+ { pattern: /\bglobalThis\s*[[.]/, label: 'globalThis access' },
253
+ { pattern: /\bwindow\s*[[.]/, label: 'window access' },
254
+ { pattern: /\bdocument\s*[[.]/, label: 'document access' },
255
+ { pattern: /\bchild_process\b/, label: 'child_process' },
256
+ { pattern: /\brequire\s*\(\s*['"]fs['"]/, label: 'fs module' },
257
+ { pattern: /\brequire\s*\(\s*['"]net['"]/, label: 'net module' },
258
+ { pattern: /\brequire\s*\(\s*['"]dns['"]/, label: 'dns module' },
259
+ { pattern: /\brequire\s*\(\s*['"]http['"]/, label: 'http module' },
260
+ { pattern: /\brequire\s*\(\s*['"]https['"]/, label: 'https module' },
261
+ { pattern: /\brequire\s*\(\s*['"]os['"]/, label: 'os module' },
262
+ { pattern: /\bspawn\s*\(/, label: 'spawn()' },
263
+ { pattern: /\bexecSync\s*\(/, label: 'execSync()' },
264
+ { pattern: /\bexecFile\s*\(/, label: 'execFile()' },
265
+ { pattern: /__proto__/, label: '__proto__' },
266
+ { pattern: /\.\s*constructor\s*[[.(]/, label: 'constructor access' },
267
+ { pattern: /\bgetPrototypeOf\s*\(/, label: 'getPrototypeOf()' },
268
+ { pattern: /\bsetPrototypeOf\s*\(/, label: 'setPrototypeOf()' },
269
+ { pattern: /\bReflect\s*[[.]/, label: 'Reflect access' },
270
+ { pattern: /\bProxy\s*[[.(]/, label: 'Proxy access' },
271
+ { pattern: /\bSymbol\s*\.\s*for\s*\(/, label: 'Symbol.for()' },
272
+ { pattern: /\bSharedArrayBuffer\b/, label: 'SharedArrayBuffer' },
273
+ { pattern: /\bAtomics\b/, label: 'Atomics' },
274
+ { pattern: /\bWeakRef\b/, label: 'WeakRef' },
275
+ { pattern: /\bFinalizationRegistry\b/, label: 'FinalizationRegistry' },
155
276
  ];
156
277
 
157
278
  if (this.config.isolationLevel === 'strict') {
158
279
  forbidden.push(
159
- /\bfetch\s*\(/,
160
- /\bXMLHttpRequest/,
161
- /\bWebSocket/,
162
- /\bsetTimeout\s*\(/,
163
- /\bsetInterval\s*\(/
280
+ { pattern: /\bfetch\s*\(/, label: 'fetch()' },
281
+ { pattern: /\bXMLHttpRequest\b/, label: 'XMLHttpRequest' },
282
+ { pattern: /\bWebSocket\b/, label: 'WebSocket' },
283
+ { pattern: /\bsetTimeout\s*\(/, label: 'setTimeout()' },
284
+ { pattern: /\bsetInterval\s*\(/, label: 'setInterval()' }
164
285
  );
165
286
  }
166
287
 
167
- for (const pattern of forbidden) {
288
+ for (const { pattern, label } of forbidden) {
168
289
  if (pattern.test(code)) {
169
- throw new Error(`Security violation: forbidden pattern detected - ${pattern.source}`);
290
+ throw new Error(`Security violation: forbidden pattern detected - ${label}`);
291
+ }
292
+ }
293
+
294
+ const bracketGlobalAccess = /\b(?:global|globalThis|process|window|document)\s*\[\s*['"`]/;
295
+ if (bracketGlobalAccess.test(code)) {
296
+ throw new Error('Security violation: bracket notation access to global objects');
297
+ }
298
+
299
+ const stringConcatEscape = /['"`]\s*\+\s*['"`]/;
300
+ if (stringConcatEscape.test(code)) {
301
+ const concatenated = code.replace(/['"`]\s*\+\s*['"`]/g, '');
302
+ for (const { pattern, label } of forbidden) {
303
+ if (pattern.test(concatenated)) {
304
+ throw new Error(`Security violation: obfuscated forbidden pattern detected - ${label}`);
305
+ }
170
306
  }
171
307
  }
172
308
 
@@ -176,82 +312,85 @@ export class ToolSandbox {
176
312
  }
177
313
  }
178
314
 
179
- private createContext(): SandboxContext {
180
- return {
181
- console: {
182
- log: (...args: unknown[]) => {
183
- this.logs.push(`[LOG] ${args.map(String).join(' ')}`);
184
- },
185
- warn: (...args: unknown[]) => {
186
- this.logs.push(`[WARN] ${args.map(String).join(' ')}`);
187
- },
188
- error: (...args: unknown[]) => {
189
- this.logs.push(`[ERROR] ${args.map(String).join(' ')}`);
190
- },
191
- },
192
- Math,
193
- JSON,
194
- Date,
195
- Array,
196
- Object,
197
- String,
198
- Number,
199
- Boolean,
200
- RegExp,
201
- Map,
202
- Set,
203
- Promise,
204
- Error,
205
- TypeError,
206
- RangeError,
207
- };
208
- }
315
+ private executeInWorker(
316
+ code: string,
317
+ paramsJson: string,
318
+ startTime: number
319
+ ): Promise<SandboxRun> {
320
+ return new Promise<SandboxRun>((resolve) => {
321
+ let settled = false;
322
+ let worker: Worker | undefined;
323
+
324
+ const settle = (result: ToolSandboxResult, thrownByTool = false) => {
325
+ if (settled) return;
326
+ settled = true;
327
+ clearTimeout(timer);
328
+ if (worker) void worker.terminate();
329
+ resolve({ result, thrownByTool });
330
+ };
209
331
 
210
- private wrapImplementation(code: string): string {
211
- return `
212
- "use strict";
213
- return (async function sandboxedExecution(params, context) {
214
- const { console, Math, JSON, Date, Array, Object, String, Number, Boolean, RegExp, Map, Set, Promise, Error, TypeError, RangeError } = context;
332
+ const timer = setTimeout(() => {
333
+ settle(failure(`Execution timeout: exceeded ${this.config.maxExecutionTime}ms`, startTime));
334
+ }, this.config.maxExecutionTime);
215
335
 
216
- ${code}
336
+ const maxMemoryMb = Math.max(4, Math.ceil(this.config.maxMemory / (1024 * 1024)));
217
337
 
218
- if (typeof execute === 'function') {
219
- return await execute(params);
338
+ try {
339
+ worker = new Worker(WORKER_SOURCE, {
340
+ eval: true,
341
+ env: {},
342
+ workerData: { code, paramsJson, timeout: this.config.maxExecutionTime },
343
+ resourceLimits: {
344
+ maxOldGenerationSizeMb: maxMemoryMb,
345
+ maxYoungGenerationSizeMb: Math.max(2, Math.ceil(maxMemoryMb / 4)),
346
+ },
347
+ });
348
+ } catch (error) {
349
+ settle(
350
+ failure(
351
+ `Failed to create sandbox worker: ${error instanceof Error ? error.message : String(error)}`,
352
+ startTime
353
+ )
354
+ );
355
+ return;
356
+ }
357
+
358
+ worker.on('message', (msg: WorkerMessage) => {
359
+ const logs = msg.logs ?? [];
360
+ if (msg.success) {
361
+ settle({
362
+ success: true,
363
+ result: msg.result,
364
+ executionTime: Date.now() - startTime,
365
+ memoryUsed: estimateMemoryUsage(msg.result),
366
+ logs,
367
+ });
368
+ } else {
369
+ settle({ ...failure(msg.error ?? 'Tool execution failed', startTime), logs }, msg.thrown);
220
370
  }
221
- throw new Error('Implementation must define an execute function');
222
371
  });
223
- `;
224
- }
225
372
 
226
- private async executeWithTimeout(
227
- wrappedCode: string,
228
- context: SandboxContext,
229
- params: unknown,
230
- timeout: number
231
- ): Promise<unknown> {
232
- return new Promise((resolve, reject) => {
233
- const timer = setTimeout(() => {
234
- reject(new Error(`Execution timeout: exceeded ${timeout}ms`));
235
- }, timeout);
373
+ worker.on('error', (error: Error & { code?: string }) => {
374
+ settle(
375
+ failure(
376
+ error.code === 'ERR_WORKER_OUT_OF_MEMORY'
377
+ ? `Execution exceeded memory limit (${this.config.maxMemory} bytes)`
378
+ : `Sandbox worker error: ${error.message}`,
379
+ startTime
380
+ )
381
+ );
382
+ });
236
383
 
237
- try {
238
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
239
- const factory = new Function(wrappedCode);
240
- const executor = factory();
241
-
242
- Promise.resolve(executor(params, context))
243
- .then((result) => {
244
- clearTimeout(timer);
245
- resolve(result);
246
- })
247
- .catch((error: unknown) => {
248
- clearTimeout(timer);
249
- reject(error instanceof Error ? error : new Error(String(error)));
250
- });
251
- } catch (error) {
252
- clearTimeout(timer);
253
- reject(error instanceof Error ? error : new Error(String(error)));
254
- }
384
+ worker.on('exit', (exitCode: number) => {
385
+ settle(
386
+ failure(
387
+ exitCode === 134 || exitCode === 137
388
+ ? `Execution exceeded memory limit (${this.config.maxMemory} bytes)`
389
+ : `Sandbox worker exited with code ${exitCode} before reporting a result`,
390
+ startTime
391
+ )
392
+ );
393
+ });
255
394
  });
256
395
  }
257
396
 
@@ -259,74 +398,137 @@ export class ToolSandbox {
259
398
  tool: GeneratedTool,
260
399
  params: unknown,
261
400
  startTime: number
262
- ): Promise<ToolSandboxResult> {
401
+ ): Promise<SandboxRun> {
402
+ let timer: ReturnType<typeof setTimeout> | undefined;
263
403
  try {
264
404
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
265
405
  const factory = new Function(`
266
406
  "use strict";
267
407
  ${tool.implementation}
268
408
  return execute;
269
- `);
409
+ `) as () => unknown;
270
410
  const execute = factory();
411
+ if (typeof execute !== 'function') {
412
+ return {
413
+ result: failure('Implementation must define an execute function', startTime),
414
+ thrownByTool: false,
415
+ };
416
+ }
271
417
 
272
- const result = await Promise.race([
273
- execute(params),
274
- new Promise((_, reject) =>
275
- setTimeout(
276
- () =>
277
- reject(new Error(`Execution timeout: exceeded ${this.config.maxExecutionTime}ms`)),
278
- this.config.maxExecutionTime
279
- )
280
- ),
418
+ const timeout = new Promise<never>((_, reject) => {
419
+ timer = setTimeout(
420
+ () => reject(new SandboxTimeoutError(this.config.maxExecutionTime)),
421
+ this.config.maxExecutionTime
422
+ );
423
+ });
424
+ const result: unknown = await Promise.race([
425
+ (execute as (p: unknown) => unknown)(params),
426
+ timeout,
281
427
  ]);
282
428
 
283
429
  return {
284
- success: true,
285
- result,
286
- executionTime: Date.now() - startTime,
287
- memoryUsed: this.estimateMemoryUsage(result),
288
- logs: [],
430
+ result: {
431
+ success: true,
432
+ result,
433
+ executionTime: Date.now() - startTime,
434
+ memoryUsed: estimateMemoryUsage(result),
435
+ logs: [],
436
+ },
437
+ thrownByTool: false,
289
438
  };
290
439
  } catch (error) {
291
440
  return {
292
- success: false,
293
- error: error instanceof Error ? error.message : String(error),
294
- executionTime: Date.now() - startTime,
295
- memoryUsed: 0,
296
- logs: [],
441
+ result: failure(error instanceof Error ? error.message : String(error), startTime),
442
+ thrownByTool: !(error instanceof SandboxTimeoutError),
297
443
  };
444
+ } finally {
445
+ clearTimeout(timer);
298
446
  }
299
447
  }
448
+ }
300
449
 
301
- private estimateMemoryUsage(value: unknown): number {
302
- try {
303
- const str = JSON.stringify(value);
304
- return str.length * 2;
305
- } catch {
306
- return 0;
307
- }
450
+ class SandboxTimeoutError extends Error {
451
+ constructor(limit: number) {
452
+ super(`Execution timeout: exceeded ${limit}ms`);
453
+ this.name = 'SandboxTimeoutError';
308
454
  }
455
+ }
309
456
 
310
- private deepEqual(a: unknown, b: unknown): boolean {
311
- if (a === b) return true;
312
- if (typeof a !== typeof b) return false;
313
- if (a === null || b === null) return a === b;
457
+ function failure(error: string, startTime: number): ToolSandboxResult {
458
+ return {
459
+ success: false,
460
+ error,
461
+ executionTime: Date.now() - startTime,
462
+ memoryUsed: 0,
463
+ logs: [],
464
+ };
465
+ }
314
466
 
315
- if (Array.isArray(a) && Array.isArray(b)) {
316
- if (a.length !== b.length) return false;
317
- return a.every((val, i) => this.deepEqual(val, b[i]));
318
- }
467
+ function estimateMemoryUsage(value: unknown): number {
468
+ try {
469
+ const str = JSON.stringify(value);
470
+ return str ? str.length * 2 : 0;
471
+ } catch {
472
+ return 0;
473
+ }
474
+ }
475
+
476
+ export function deepEqual(a: unknown, b: unknown): boolean {
477
+ return deepEqualInner(a, b, new Map<object, Set<object>>());
478
+ }
479
+
480
+ function deepEqualInner(a: unknown, b: unknown, visited: Map<object, Set<object>>): boolean {
481
+ if (Object.is(a, b)) return true;
482
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
483
+
484
+ const pairs = visited.get(a);
485
+ if (pairs?.has(b)) return true;
486
+ if (pairs) pairs.add(b);
487
+ else visited.set(a, new Set([b]));
488
+
489
+ if (a instanceof Date || b instanceof Date) {
490
+ return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
491
+ }
319
492
 
320
- if (typeof a === 'object' && typeof b === 'object') {
321
- const aObj = a as Record<string, unknown>;
322
- const bObj = b as Record<string, unknown>;
323
- const keysA = Object.keys(aObj);
324
- const keysB = Object.keys(bObj);
493
+ if (a instanceof RegExp || b instanceof RegExp) {
494
+ return (
495
+ a instanceof RegExp && b instanceof RegExp && a.source === b.source && a.flags === b.flags
496
+ );
497
+ }
325
498
 
326
- if (keysA.length !== keysB.length) return false;
327
- return keysA.every((key) => this.deepEqual(aObj[key], bObj[key]));
499
+ if (a instanceof Map || b instanceof Map) {
500
+ if (!(a instanceof Map && b instanceof Map) || a.size !== b.size) return false;
501
+ for (const [key, val] of a) {
502
+ if (!b.has(key) || !deepEqualInner(val, b.get(key), visited)) return false;
328
503
  }
504
+ return true;
505
+ }
329
506
 
330
- return false;
507
+ if (a instanceof Set || b instanceof Set) {
508
+ if (!(a instanceof Set && b instanceof Set) || a.size !== b.size) return false;
509
+ const unmatched = [...b];
510
+ for (const val of a) {
511
+ const idx = unmatched.findIndex((candidate) => deepEqualInner(val, candidate, visited));
512
+ if (idx === -1) return false;
513
+ unmatched.splice(idx, 1);
514
+ }
515
+ return true;
331
516
  }
517
+
518
+ if (Array.isArray(a) || Array.isArray(b)) {
519
+ if (!(Array.isArray(a) && Array.isArray(b)) || a.length !== b.length) return false;
520
+ return a.every((val, i) => deepEqualInner(val, b[i], visited));
521
+ }
522
+
523
+ const aObj = a as Record<string, unknown>;
524
+ const bObj = b as Record<string, unknown>;
525
+ const keysA = Object.keys(aObj).filter((k) => aObj[k] !== undefined);
526
+ const keysB = Object.keys(bObj).filter((k) => bObj[k] !== undefined);
527
+
528
+ if (keysA.length !== keysB.length) return false;
529
+ return keysA.every(
530
+ (key) =>
531
+ Object.prototype.hasOwnProperty.call(bObj, key) &&
532
+ deepEqualInner(aObj[key], bObj[key], visited)
533
+ );
332
534
  }