@cogitator-ai/self-modifying 17.0.18 → 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 (84) 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 +38 -37
  4. package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
  5. package/dist/architecture-evolution/index.d.ts +2 -2
  6. package/dist/architecture-evolution/index.d.ts.map +1 -1
  7. package/dist/architecture-evolution/index.js +1 -1
  8. package/dist/architecture-evolution/index.js.map +1 -1
  9. package/dist/architecture-evolution/parameter-optimizer.d.ts +5 -4
  10. package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
  11. package/dist/architecture-evolution/parameter-optimizer.js +36 -31
  12. package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
  13. package/dist/architecture-evolution/prompts.d.ts +21 -2
  14. package/dist/architecture-evolution/prompts.d.ts.map +1 -1
  15. package/dist/architecture-evolution/prompts.js +150 -36
  16. package/dist/architecture-evolution/prompts.js.map +1 -1
  17. package/dist/constraints/modification-validator.d.ts +1 -0
  18. package/dist/constraints/modification-validator.d.ts.map +1 -1
  19. package/dist/constraints/modification-validator.js +30 -14
  20. package/dist/constraints/modification-validator.js.map +1 -1
  21. package/dist/index.d.ts +4 -4
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/meta-reasoning/meta-reasoner.d.ts +3 -0
  26. package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
  27. package/dist/meta-reasoning/meta-reasoner.js +101 -33
  28. package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
  29. package/dist/meta-reasoning/prompts.d.ts +10 -14
  30. package/dist/meta-reasoning/prompts.d.ts.map +1 -1
  31. package/dist/meta-reasoning/prompts.js +109 -1
  32. package/dist/meta-reasoning/prompts.js.map +1 -1
  33. package/dist/self-modifying-agent.d.ts +83 -17
  34. package/dist/self-modifying-agent.d.ts.map +1 -1
  35. package/dist/self-modifying-agent.js +557 -444
  36. package/dist/self-modifying-agent.js.map +1 -1
  37. package/dist/tool-generation/gap-analyzer.d.ts +0 -1
  38. package/dist/tool-generation/gap-analyzer.d.ts.map +1 -1
  39. package/dist/tool-generation/gap-analyzer.js +14 -14
  40. package/dist/tool-generation/gap-analyzer.js.map +1 -1
  41. package/dist/tool-generation/index.d.ts +3 -3
  42. package/dist/tool-generation/index.d.ts.map +1 -1
  43. package/dist/tool-generation/index.js +2 -2
  44. package/dist/tool-generation/index.js.map +1 -1
  45. package/dist/tool-generation/prompts.d.ts +1 -0
  46. package/dist/tool-generation/prompts.d.ts.map +1 -1
  47. package/dist/tool-generation/prompts.js +33 -8
  48. package/dist/tool-generation/prompts.js.map +1 -1
  49. package/dist/tool-generation/tool-generator.d.ts +5 -5
  50. package/dist/tool-generation/tool-generator.d.ts.map +1 -1
  51. package/dist/tool-generation/tool-generator.js +41 -28
  52. package/dist/tool-generation/tool-generator.js.map +1 -1
  53. package/dist/tool-generation/tool-sandbox.d.ts +9 -6
  54. package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
  55. package/dist/tool-generation/tool-sandbox.js +257 -157
  56. package/dist/tool-generation/tool-sandbox.js.map +1 -1
  57. package/dist/tool-generation/tool-validator.d.ts +3 -8
  58. package/dist/tool-generation/tool-validator.d.ts.map +1 -1
  59. package/dist/tool-generation/tool-validator.js +53 -54
  60. package/dist/tool-generation/tool-validator.js.map +1 -1
  61. package/dist/utils/llm-helper.d.ts.map +1 -1
  62. package/dist/utils/llm-helper.js +1 -0
  63. package/dist/utils/llm-helper.js.map +1 -1
  64. package/package.json +3 -12
  65. package/src/__tests__/component-regressions.test.ts +599 -0
  66. package/src/__tests__/meta-reasoning.test.ts +5 -1
  67. package/src/__tests__/sandbox-security.test.ts +186 -0
  68. package/src/__tests__/self-modifying-agent.test.ts +363 -1
  69. package/src/architecture-evolution/capability-analyzer.ts +56 -40
  70. package/src/architecture-evolution/index.ts +2 -0
  71. package/src/architecture-evolution/parameter-optimizer.ts +52 -34
  72. package/src/architecture-evolution/prompts.ts +179 -41
  73. package/src/constraints/modification-validator.ts +30 -14
  74. package/src/index.ts +14 -0
  75. package/src/meta-reasoning/meta-reasoner.ts +105 -40
  76. package/src/meta-reasoning/prompts.ts +129 -15
  77. package/src/self-modifying-agent.ts +764 -489
  78. package/src/tool-generation/gap-analyzer.ts +19 -18
  79. package/src/tool-generation/index.ts +13 -3
  80. package/src/tool-generation/prompts.ts +41 -8
  81. package/src/tool-generation/tool-generator.ts +59 -33
  82. package/src/tool-generation/tool-sandbox.ts +306 -175
  83. package/src/tool-generation/tool-validator.ts +66 -63
  84. package/src/utils/llm-helper.ts +1 -0
@@ -9,42 +9,141 @@ export const DEFAULT_SANDBOX_CONFIG: ToolSandboxConfig = {
9
9
  isolationLevel: 'strict',
10
10
  };
11
11
 
12
- const WORKER_SOURCE = [
13
- 'const { parentPort, workerData } = require("worker_threads");',
14
- 'const vm = require("vm");',
15
- 'const { code, params } = workerData;',
16
- 'const logs = [];',
17
- 'const sandbox = Object.create(null);',
18
- 'Object.assign(sandbox, {',
19
- ' console: {',
20
- ' log: (...a) => { logs.push("[LOG] " + a.map(String).join(" ")); },',
21
- ' warn: (...a) => { logs.push("[WARN] " + a.map(String).join(" ")); },',
22
- ' error: (...a) => { logs.push("[ERROR] " + a.map(String).join(" ")); },',
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
+ ' }',
23
50
  ' },',
24
- ' Math, JSON, Date, Array, Object, String, Number, Boolean, RegExp, Map, Set,',
25
- ' Promise, Error, TypeError, RangeError,',
26
- ' parseInt, parseFloat, isNaN, isFinite,',
27
- ' encodeURIComponent, decodeURIComponent, encodeURI, decodeURI,',
28
- ' undefined, NaN, Infinity,',
29
- ' params,',
30
- '});',
31
- 'const context = vm.createContext(sandbox);',
32
- 'try {',
33
- ' const wrapped = "(async function sandboxedExecution() {\\n" +',
34
- ' code + "\\n" +',
35
- ' "if (typeof execute === \\"function\\") { return await execute(params); }\\n" +',
36
- ' "throw new Error(\\"Implementation must define an execute function\\");\\n" +',
37
- ' "})()";',
38
- ' const promise = vm.runInContext(wrapped, context);',
39
- ' Promise.resolve(promise).then(',
40
- ' (result) => { parentPort.postMessage({ success: true, result, logs }); },',
41
- ' (err) => { parentPort.postMessage({ success: false, error: err instanceof Error ? err.message : String(err), logs }); }',
42
- ' );',
43
- '} catch (err) {',
44
- ' parentPort.postMessage({ success: false, error: err instanceof Error ? err.message : String(err), logs });',
45
- '}',
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
+ ');',
46
57
  ].join('\n');
47
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;
145
+ }
146
+
48
147
  export class ToolSandbox {
49
148
  private readonly config: ToolSandboxConfig;
50
149
 
@@ -53,30 +152,13 @@ export class ToolSandbox {
53
152
  }
54
153
 
55
154
  async execute(tool: GeneratedTool, params: unknown): Promise<ToolSandboxResult> {
56
- const startTime = Date.now();
57
-
58
- try {
59
- this.validateImplementation(tool.implementation);
60
- } catch (error) {
61
- return {
62
- success: false,
63
- error: error instanceof Error ? error.message : String(error),
64
- executionTime: Date.now() - startTime,
65
- memoryUsed: 0,
66
- logs: [],
67
- };
68
- }
69
-
70
- if (!this.config.enabled) {
71
- return this.executeUnsandboxed(tool, params, startTime);
72
- }
73
-
74
- return this.executeInWorker(tool.implementation, params, startTime);
155
+ const run = await this.run(tool, params);
156
+ return run.result;
75
157
  }
76
158
 
77
159
  async testWithCases(
78
160
  tool: GeneratedTool,
79
- testCases: Array<{ input: unknown; expectedOutput?: unknown; shouldThrow?: boolean }>
161
+ testCases: SandboxTestCase[]
80
162
  ): Promise<{
81
163
  passed: number;
82
164
  failed: number;
@@ -97,13 +179,15 @@ export class ToolSandbox {
97
179
  }> = [];
98
180
 
99
181
  for (const testCase of testCases) {
100
- const execResult = await this.execute(tool, testCase.input);
182
+ const { result: execResult, thrownByTool } = await this.run(tool, testCase.input);
101
183
 
102
- let passed = false;
184
+ let passed: boolean;
103
185
  if (testCase.shouldThrow) {
104
- passed = !execResult.success;
186
+ passed = !execResult.success && thrownByTool;
105
187
  } else if (testCase.expectedOutput !== undefined) {
106
188
  passed = execResult.success && deepEqual(execResult.result, testCase.expectedOutput);
189
+ } else if (testCase.allowThrow) {
190
+ passed = execResult.success || thrownByTool;
107
191
  } else {
108
192
  passed = execResult.success;
109
193
  }
@@ -124,6 +208,38 @@ export class ToolSandbox {
124
208
  };
125
209
  }
126
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
+
127
243
  private validateImplementation(code: string): void {
128
244
  const forbidden: Array<{ pattern: RegExp; label: string }> = [
129
245
  { pattern: /\beval\s*\(/, label: 'eval()' },
@@ -196,102 +312,84 @@ export class ToolSandbox {
196
312
  }
197
313
  }
198
314
 
199
- private async executeInWorker(
315
+ private executeInWorker(
200
316
  code: string,
201
- params: unknown,
317
+ paramsJson: string,
202
318
  startTime: number
203
- ): Promise<ToolSandboxResult> {
204
- let serializableParams: unknown;
205
- try {
206
- serializableParams = JSON.parse(JSON.stringify(params ?? null));
207
- } catch {
208
- serializableParams = null;
209
- }
210
-
211
- return new Promise<ToolSandboxResult>((resolve) => {
319
+ ): Promise<SandboxRun> {
320
+ return new Promise<SandboxRun>((resolve) => {
212
321
  let settled = false;
322
+ let worker: Worker | undefined;
213
323
 
214
- const settle = (result: ToolSandboxResult) => {
215
- if (!settled) {
216
- settled = true;
217
- resolve(result);
218
- }
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 });
219
330
  };
220
331
 
332
+ const timer = setTimeout(() => {
333
+ settle(failure(`Execution timeout: exceeded ${this.config.maxExecutionTime}ms`, startTime));
334
+ }, this.config.maxExecutionTime);
335
+
221
336
  const maxMemoryMb = Math.max(4, Math.ceil(this.config.maxMemory / (1024 * 1024)));
222
337
 
223
- let worker: Worker;
224
338
  try {
225
339
  worker = new Worker(WORKER_SOURCE, {
226
340
  eval: true,
227
- workerData: { code, params: serializableParams },
341
+ env: {},
342
+ workerData: { code, paramsJson, timeout: this.config.maxExecutionTime },
228
343
  resourceLimits: {
229
344
  maxOldGenerationSizeMb: maxMemoryMb,
230
345
  maxYoungGenerationSizeMb: Math.max(2, Math.ceil(maxMemoryMb / 4)),
231
346
  },
232
347
  });
233
348
  } catch (error) {
234
- settle({
235
- success: false,
236
- error: `Failed to create sandbox worker: ${error instanceof Error ? error.message : String(error)}`,
237
- executionTime: Date.now() - startTime,
238
- memoryUsed: 0,
239
- logs: [],
240
- });
349
+ settle(
350
+ failure(
351
+ `Failed to create sandbox worker: ${error instanceof Error ? error.message : String(error)}`,
352
+ startTime
353
+ )
354
+ );
241
355
  return;
242
356
  }
243
357
 
244
- const timer = setTimeout(() => {
245
- void worker.terminate();
246
- settle({
247
- success: false,
248
- error: `Execution timeout: exceeded ${this.config.maxExecutionTime}ms`,
249
- executionTime: Date.now() - startTime,
250
- memoryUsed: 0,
251
- logs: [],
252
- });
253
- }, this.config.maxExecutionTime);
254
-
255
- worker.on(
256
- 'message',
257
- (msg: { success: boolean; result?: unknown; error?: string; logs?: string[] }) => {
258
- clearTimeout(timer);
358
+ worker.on('message', (msg: WorkerMessage) => {
359
+ const logs = msg.logs ?? [];
360
+ if (msg.success) {
259
361
  settle({
260
- success: msg.success,
362
+ success: true,
261
363
  result: msg.result,
262
- error: msg.error,
263
364
  executionTime: Date.now() - startTime,
264
365
  memoryUsed: estimateMemoryUsage(msg.result),
265
- logs: msg.logs ?? [],
366
+ logs,
266
367
  });
368
+ } else {
369
+ settle({ ...failure(msg.error ?? 'Tool execution failed', startTime), logs }, msg.thrown);
267
370
  }
268
- );
371
+ });
269
372
 
270
- worker.on('error', (error: Error) => {
271
- clearTimeout(timer);
272
- settle({
273
- success: false,
274
- error: `Sandbox worker error: ${error.message}`,
275
- executionTime: Date.now() - startTime,
276
- memoryUsed: 0,
277
- logs: [],
278
- });
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
+ );
279
382
  });
280
383
 
281
384
  worker.on('exit', (exitCode: number) => {
282
- clearTimeout(timer);
283
- if (exitCode !== 0) {
284
- settle({
285
- success: false,
286
- error:
287
- exitCode === 134 || exitCode === 137
288
- ? `Execution exceeded memory limit (${this.config.maxMemory} bytes)`
289
- : `Sandbox worker exited with code ${exitCode}`,
290
- executionTime: Date.now() - startTime,
291
- memoryUsed: 0,
292
- logs: [],
293
- });
294
- }
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
+ );
295
393
  });
296
394
  });
297
395
  }
@@ -300,46 +398,72 @@ export class ToolSandbox {
300
398
  tool: GeneratedTool,
301
399
  params: unknown,
302
400
  startTime: number
303
- ): Promise<ToolSandboxResult> {
401
+ ): Promise<SandboxRun> {
402
+ let timer: ReturnType<typeof setTimeout> | undefined;
304
403
  try {
305
404
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
306
405
  const factory = new Function(`
307
406
  "use strict";
308
407
  ${tool.implementation}
309
408
  return execute;
310
- `);
409
+ `) as () => unknown;
311
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
+ }
312
417
 
313
- const result = await Promise.race([
314
- execute(params),
315
- new Promise((_, reject) =>
316
- setTimeout(
317
- () =>
318
- reject(new Error(`Execution timeout: exceeded ${this.config.maxExecutionTime}ms`)),
319
- this.config.maxExecutionTime
320
- )
321
- ),
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,
322
427
  ]);
323
428
 
324
429
  return {
325
- success: true,
326
- result,
327
- executionTime: Date.now() - startTime,
328
- memoryUsed: estimateMemoryUsage(result),
329
- logs: [],
430
+ result: {
431
+ success: true,
432
+ result,
433
+ executionTime: Date.now() - startTime,
434
+ memoryUsed: estimateMemoryUsage(result),
435
+ logs: [],
436
+ },
437
+ thrownByTool: false,
330
438
  };
331
439
  } catch (error) {
332
440
  return {
333
- success: false,
334
- error: error instanceof Error ? error.message : String(error),
335
- executionTime: Date.now() - startTime,
336
- memoryUsed: 0,
337
- logs: [],
441
+ result: failure(error instanceof Error ? error.message : String(error), startTime),
442
+ thrownByTool: !(error instanceof SandboxTimeoutError),
338
443
  };
444
+ } finally {
445
+ clearTimeout(timer);
339
446
  }
340
447
  }
341
448
  }
342
449
 
450
+ class SandboxTimeoutError extends Error {
451
+ constructor(limit: number) {
452
+ super(`Execution timeout: exceeded ${limit}ms`);
453
+ this.name = 'SandboxTimeoutError';
454
+ }
455
+ }
456
+
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
+ }
466
+
343
467
  function estimateMemoryUsage(value: unknown): number {
344
468
  try {
345
469
  const str = JSON.stringify(value);
@@ -349,55 +473,62 @@ function estimateMemoryUsage(value: unknown): number {
349
473
  }
350
474
  }
351
475
 
352
- export function deepEqual(a: unknown, b: unknown, seen = new WeakSet<object>()): boolean {
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 {
353
481
  if (Object.is(a, b)) return true;
482
+ if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) return false;
354
483
 
355
- if (typeof a !== typeof b) return false;
356
- if (a === null || b === null) return a === b;
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]));
357
488
 
358
- if (a instanceof Date && b instanceof Date) {
359
- return a.getTime() === b.getTime();
489
+ if (a instanceof Date || b instanceof Date) {
490
+ return a instanceof Date && b instanceof Date && a.getTime() === b.getTime();
360
491
  }
361
492
 
362
- if (a instanceof RegExp && b instanceof RegExp) {
363
- return a.source === b.source && a.flags === b.flags;
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
+ );
364
497
  }
365
498
 
366
- if (a instanceof Map && b instanceof Map) {
367
- if (a.size !== b.size) return false;
499
+ if (a instanceof Map || b instanceof Map) {
500
+ if (!(a instanceof Map && b instanceof Map) || a.size !== b.size) return false;
368
501
  for (const [key, val] of a) {
369
- if (!b.has(key) || !deepEqual(val, b.get(key), seen)) return false;
502
+ if (!b.has(key) || !deepEqualInner(val, b.get(key), visited)) return false;
370
503
  }
371
504
  return true;
372
505
  }
373
506
 
374
- if (a instanceof Set && b instanceof Set) {
375
- if (a.size !== b.size) 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];
376
510
  for (const val of a) {
377
- if (!b.has(val)) return false;
511
+ const idx = unmatched.findIndex((candidate) => deepEqualInner(val, candidate, visited));
512
+ if (idx === -1) return false;
513
+ unmatched.splice(idx, 1);
378
514
  }
379
515
  return true;
380
516
  }
381
517
 
382
- if (Array.isArray(a) && Array.isArray(b)) {
383
- if (a.length !== b.length) return false;
384
- return a.every((val, i) => deepEqual(val, b[i], seen));
385
- }
386
-
387
- if (typeof a === 'object' && typeof b === 'object') {
388
- const aObj = a as Record<string, unknown>;
389
- const bObj = b as Record<string, unknown>;
390
-
391
- if (seen.has(aObj) || seen.has(bObj)) return false;
392
- seen.add(aObj);
393
- seen.add(bObj);
394
-
395
- const keysA = Object.keys(aObj).filter((k) => aObj[k] !== undefined);
396
- const keysB = Object.keys(bObj).filter((k) => bObj[k] !== undefined);
397
-
398
- if (keysA.length !== keysB.length) return false;
399
- return keysA.every((key) => deepEqual(aObj[key], bObj[key], seen));
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));
400
521
  }
401
522
 
402
- return false;
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
+ );
403
534
  }