@cogitator-ai/self-modifying 17.0.17 → 17.0.18

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 (91) hide show
  1. package/dist/architecture-evolution/capability-analyzer.d.ts.map +1 -1
  2. package/dist/architecture-evolution/capability-analyzer.js +6 -3
  3. package/dist/architecture-evolution/capability-analyzer.js.map +1 -1
  4. package/dist/architecture-evolution/evolution-strategy.d.ts.map +1 -1
  5. package/dist/architecture-evolution/evolution-strategy.js +4 -2
  6. package/dist/architecture-evolution/evolution-strategy.js.map +1 -1
  7. package/dist/architecture-evolution/parameter-optimizer.d.ts.map +1 -1
  8. package/dist/architecture-evolution/parameter-optimizer.js +32 -9
  9. package/dist/architecture-evolution/parameter-optimizer.js.map +1 -1
  10. package/dist/architecture-evolution/prompts.d.ts.map +1 -1
  11. package/dist/architecture-evolution/prompts.js +42 -4
  12. package/dist/architecture-evolution/prompts.js.map +1 -1
  13. package/dist/constraints/modification-validator.d.ts.map +1 -1
  14. package/dist/constraints/modification-validator.js +16 -5
  15. package/dist/constraints/modification-validator.js.map +1 -1
  16. package/dist/constraints/rollback-manager.d.ts +1 -0
  17. package/dist/constraints/rollback-manager.d.ts.map +1 -1
  18. package/dist/constraints/rollback-manager.js +25 -1
  19. package/dist/constraints/rollback-manager.js.map +1 -1
  20. package/dist/events/event-emitter.d.ts +0 -1
  21. package/dist/events/event-emitter.d.ts.map +1 -1
  22. package/dist/events/event-emitter.js +6 -10
  23. package/dist/events/event-emitter.js.map +1 -1
  24. package/dist/meta-reasoning/meta-reasoner.d.ts +1 -1
  25. package/dist/meta-reasoning/meta-reasoner.d.ts.map +1 -1
  26. package/dist/meta-reasoning/meta-reasoner.js +22 -13
  27. package/dist/meta-reasoning/meta-reasoner.js.map +1 -1
  28. package/dist/meta-reasoning/observation-collector.d.ts.map +1 -1
  29. package/dist/meta-reasoning/observation-collector.js +6 -2
  30. package/dist/meta-reasoning/observation-collector.js.map +1 -1
  31. package/dist/meta-reasoning/prompts.d.ts.map +1 -1
  32. package/dist/meta-reasoning/prompts.js +5 -38
  33. package/dist/meta-reasoning/prompts.js.map +1 -1
  34. package/dist/meta-reasoning/strategy-selector.d.ts.map +1 -1
  35. package/dist/meta-reasoning/strategy-selector.js +6 -0
  36. package/dist/meta-reasoning/strategy-selector.js.map +1 -1
  37. package/dist/self-modifying-agent.d.ts +8 -1
  38. package/dist/self-modifying-agent.d.ts.map +1 -1
  39. package/dist/self-modifying-agent.js +108 -14
  40. package/dist/self-modifying-agent.js.map +1 -1
  41. package/dist/tool-generation/gap-analyzer.d.ts.map +1 -1
  42. package/dist/tool-generation/gap-analyzer.js +3 -2
  43. package/dist/tool-generation/gap-analyzer.js.map +1 -1
  44. package/dist/tool-generation/generated-tool-store.d.ts.map +1 -1
  45. package/dist/tool-generation/generated-tool-store.js +90 -10
  46. package/dist/tool-generation/generated-tool-store.js.map +1 -1
  47. package/dist/tool-generation/index.d.ts +1 -1
  48. package/dist/tool-generation/index.d.ts.map +1 -1
  49. package/dist/tool-generation/index.js +1 -1
  50. package/dist/tool-generation/index.js.map +1 -1
  51. package/dist/tool-generation/prompts.d.ts +3 -3
  52. package/dist/tool-generation/prompts.d.ts.map +1 -1
  53. package/dist/tool-generation/prompts.js +65 -12
  54. package/dist/tool-generation/prompts.js.map +1 -1
  55. package/dist/tool-generation/tool-generator.d.ts +1 -1
  56. package/dist/tool-generation/tool-generator.d.ts.map +1 -1
  57. package/dist/tool-generation/tool-generator.js +55 -3
  58. package/dist/tool-generation/tool-generator.js.map +1 -1
  59. package/dist/tool-generation/tool-sandbox.d.ts +2 -6
  60. package/dist/tool-generation/tool-sandbox.d.ts.map +1 -1
  61. package/dist/tool-generation/tool-sandbox.js +220 -130
  62. package/dist/tool-generation/tool-sandbox.js.map +1 -1
  63. package/dist/tool-generation/tool-validator.d.ts +1 -0
  64. package/dist/tool-generation/tool-validator.d.ts.map +1 -1
  65. package/dist/tool-generation/tool-validator.js +48 -1
  66. package/dist/tool-generation/tool-validator.js.map +1 -1
  67. package/dist/utils/index.d.ts.map +1 -1
  68. package/dist/utils/index.js +30 -5
  69. package/dist/utils/index.js.map +1 -1
  70. package/package.json +4 -4
  71. package/src/__tests__/self-modifying-agent.test.ts +42 -10
  72. package/src/architecture-evolution/capability-analyzer.ts +7 -4
  73. package/src/architecture-evolution/evolution-strategy.ts +5 -2
  74. package/src/architecture-evolution/parameter-optimizer.ts +39 -15
  75. package/src/architecture-evolution/prompts.ts +48 -4
  76. package/src/constraints/modification-validator.ts +19 -8
  77. package/src/constraints/rollback-manager.ts +20 -1
  78. package/src/events/event-emitter.ts +6 -15
  79. package/src/meta-reasoning/meta-reasoner.ts +22 -15
  80. package/src/meta-reasoning/observation-collector.ts +6 -2
  81. package/src/meta-reasoning/prompts.ts +5 -44
  82. package/src/meta-reasoning/strategy-selector.ts +6 -0
  83. package/src/self-modifying-agent.ts +123 -14
  84. package/src/tool-generation/gap-analyzer.ts +3 -2
  85. package/src/tool-generation/generated-tool-store.ts +97 -10
  86. package/src/tool-generation/index.ts +1 -1
  87. package/src/tool-generation/prompts.ts +81 -12
  88. package/src/tool-generation/tool-generator.ts +71 -3
  89. package/src/tool-generation/tool-sandbox.ts +241 -170
  90. package/src/tool-generation/tool-validator.ts +51 -1
  91. package/src/utils/index.ts +29 -5
@@ -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,32 +9,44 @@ 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;
32
- }
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(" ")); },',
23
+ ' },',
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
+ '}',
46
+ ].join('\n');
33
47
 
34
48
  export class ToolSandbox {
35
49
  private readonly config: ToolSandboxConfig;
36
- private readonly logs: string[] = [];
37
50
 
38
51
  constructor(config: Partial<ToolSandboxConfig> = {}) {
39
52
  this.config = { ...DEFAULT_SANDBOX_CONFIG, ...config };
@@ -41,41 +54,24 @@ export class ToolSandbox {
41
54
 
42
55
  async execute(tool: GeneratedTool, params: unknown): Promise<ToolSandboxResult> {
43
56
  const startTime = Date.now();
44
- this.logs.length = 0;
45
-
46
- if (!this.config.enabled) {
47
- return this.executeUnsandboxed(tool, params, startTime);
48
- }
49
57
 
50
58
  try {
51
59
  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
60
  } catch (error) {
71
61
  return {
72
62
  success: false,
73
63
  error: error instanceof Error ? error.message : String(error),
74
64
  executionTime: Date.now() - startTime,
75
65
  memoryUsed: 0,
76
- logs: [...this.logs],
66
+ logs: [],
77
67
  };
78
68
  }
69
+
70
+ if (!this.config.enabled) {
71
+ return this.executeUnsandboxed(tool, params, startTime);
72
+ }
73
+
74
+ return this.executeInWorker(tool.implementation, params, startTime);
79
75
  }
80
76
 
81
77
  async testWithCases(
@@ -107,7 +103,7 @@ export class ToolSandbox {
107
103
  if (testCase.shouldThrow) {
108
104
  passed = !execResult.success;
109
105
  } else if (testCase.expectedOutput !== undefined) {
110
- passed = execResult.success && this.deepEqual(execResult.result, testCase.expectedOutput);
106
+ passed = execResult.success && deepEqual(execResult.result, testCase.expectedOutput);
111
107
  } else {
112
108
  passed = execResult.success;
113
109
  }
@@ -129,44 +125,68 @@ export class ToolSandbox {
129
125
  }
130
126
 
131
127
  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/,
128
+ const forbidden: Array<{ pattern: RegExp; label: string }> = [
129
+ { pattern: /\beval\s*\(/, label: 'eval()' },
130
+ { pattern: /\bnew\s+Function\s*\(/, label: 'new Function()' },
131
+ { pattern: /\bFunction\s*\.\s*prototype/, label: 'Function.prototype' },
132
+ { pattern: /\bimport\s*\(/, label: 'dynamic import()' },
133
+ { pattern: /\brequire\s*\(/, label: 'require()' },
134
+ { pattern: /\bprocess\s*[[.]/, label: 'process access' },
135
+ { pattern: /\bglobal\s*[[.]/, label: 'global access' },
136
+ { pattern: /\bglobalThis\s*[[.]/, label: 'globalThis access' },
137
+ { pattern: /\bwindow\s*[[.]/, label: 'window access' },
138
+ { pattern: /\bdocument\s*[[.]/, label: 'document access' },
139
+ { pattern: /\bchild_process\b/, label: 'child_process' },
140
+ { pattern: /\brequire\s*\(\s*['"]fs['"]/, label: 'fs module' },
141
+ { pattern: /\brequire\s*\(\s*['"]net['"]/, label: 'net module' },
142
+ { pattern: /\brequire\s*\(\s*['"]dns['"]/, label: 'dns module' },
143
+ { pattern: /\brequire\s*\(\s*['"]http['"]/, label: 'http module' },
144
+ { pattern: /\brequire\s*\(\s*['"]https['"]/, label: 'https module' },
145
+ { pattern: /\brequire\s*\(\s*['"]os['"]/, label: 'os module' },
146
+ { pattern: /\bspawn\s*\(/, label: 'spawn()' },
147
+ { pattern: /\bexecSync\s*\(/, label: 'execSync()' },
148
+ { pattern: /\bexecFile\s*\(/, label: 'execFile()' },
149
+ { pattern: /__proto__/, label: '__proto__' },
150
+ { pattern: /\.\s*constructor\s*[[.(]/, label: 'constructor access' },
151
+ { pattern: /\bgetPrototypeOf\s*\(/, label: 'getPrototypeOf()' },
152
+ { pattern: /\bsetPrototypeOf\s*\(/, label: 'setPrototypeOf()' },
153
+ { pattern: /\bReflect\s*[[.]/, label: 'Reflect access' },
154
+ { pattern: /\bProxy\s*[[.(]/, label: 'Proxy access' },
155
+ { pattern: /\bSymbol\s*\.\s*for\s*\(/, label: 'Symbol.for()' },
156
+ { pattern: /\bSharedArrayBuffer\b/, label: 'SharedArrayBuffer' },
157
+ { pattern: /\bAtomics\b/, label: 'Atomics' },
158
+ { pattern: /\bWeakRef\b/, label: 'WeakRef' },
159
+ { pattern: /\bFinalizationRegistry\b/, label: 'FinalizationRegistry' },
155
160
  ];
156
161
 
157
162
  if (this.config.isolationLevel === 'strict') {
158
163
  forbidden.push(
159
- /\bfetch\s*\(/,
160
- /\bXMLHttpRequest/,
161
- /\bWebSocket/,
162
- /\bsetTimeout\s*\(/,
163
- /\bsetInterval\s*\(/
164
+ { pattern: /\bfetch\s*\(/, label: 'fetch()' },
165
+ { pattern: /\bXMLHttpRequest\b/, label: 'XMLHttpRequest' },
166
+ { pattern: /\bWebSocket\b/, label: 'WebSocket' },
167
+ { pattern: /\bsetTimeout\s*\(/, label: 'setTimeout()' },
168
+ { pattern: /\bsetInterval\s*\(/, label: 'setInterval()' }
164
169
  );
165
170
  }
166
171
 
167
- for (const pattern of forbidden) {
172
+ for (const { pattern, label } of forbidden) {
168
173
  if (pattern.test(code)) {
169
- throw new Error(`Security violation: forbidden pattern detected - ${pattern.source}`);
174
+ throw new Error(`Security violation: forbidden pattern detected - ${label}`);
175
+ }
176
+ }
177
+
178
+ const bracketGlobalAccess = /\b(?:global|globalThis|process|window|document)\s*\[\s*['"`]/;
179
+ if (bracketGlobalAccess.test(code)) {
180
+ throw new Error('Security violation: bracket notation access to global objects');
181
+ }
182
+
183
+ const stringConcatEscape = /['"`]\s*\+\s*['"`]/;
184
+ if (stringConcatEscape.test(code)) {
185
+ const concatenated = code.replace(/['"`]\s*\+\s*['"`]/g, '');
186
+ for (const { pattern, label } of forbidden) {
187
+ if (pattern.test(concatenated)) {
188
+ throw new Error(`Security violation: obfuscated forbidden pattern detected - ${label}`);
189
+ }
170
190
  }
171
191
  }
172
192
 
@@ -176,82 +196,103 @@ export class ToolSandbox {
176
196
  }
177
197
  }
178
198
 
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
- }
209
-
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;
199
+ private async executeInWorker(
200
+ code: string,
201
+ params: unknown,
202
+ 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
+ }
215
210
 
216
- ${code}
211
+ return new Promise<ToolSandboxResult>((resolve) => {
212
+ let settled = false;
217
213
 
218
- if (typeof execute === 'function') {
219
- return await execute(params);
214
+ const settle = (result: ToolSandboxResult) => {
215
+ if (!settled) {
216
+ settled = true;
217
+ resolve(result);
220
218
  }
221
- throw new Error('Implementation must define an execute function');
222
- });
223
- `;
224
- }
219
+ };
225
220
 
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);
221
+ const maxMemoryMb = Math.max(4, Math.ceil(this.config.maxMemory / (1024 * 1024)));
236
222
 
223
+ let worker: Worker;
237
224
  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
- });
225
+ worker = new Worker(WORKER_SOURCE, {
226
+ eval: true,
227
+ workerData: { code, params: serializableParams },
228
+ resourceLimits: {
229
+ maxOldGenerationSizeMb: maxMemoryMb,
230
+ maxYoungGenerationSizeMb: Math.max(2, Math.ceil(maxMemoryMb / 4)),
231
+ },
232
+ });
251
233
  } catch (error) {
252
- clearTimeout(timer);
253
- reject(error instanceof Error ? error : new Error(String(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
+ });
241
+ return;
254
242
  }
243
+
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);
259
+ settle({
260
+ success: msg.success,
261
+ result: msg.result,
262
+ error: msg.error,
263
+ executionTime: Date.now() - startTime,
264
+ memoryUsed: estimateMemoryUsage(msg.result),
265
+ logs: msg.logs ?? [],
266
+ });
267
+ }
268
+ );
269
+
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
+ });
279
+ });
280
+
281
+ 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
+ }
295
+ });
255
296
  });
256
297
  }
257
298
 
@@ -284,7 +325,7 @@ export class ToolSandbox {
284
325
  success: true,
285
326
  result,
286
327
  executionTime: Date.now() - startTime,
287
- memoryUsed: this.estimateMemoryUsage(result),
328
+ memoryUsed: estimateMemoryUsage(result),
288
329
  logs: [],
289
330
  };
290
331
  } catch (error) {
@@ -297,36 +338,66 @@ export class ToolSandbox {
297
338
  };
298
339
  }
299
340
  }
341
+ }
300
342
 
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
- }
343
+ function estimateMemoryUsage(value: unknown): number {
344
+ try {
345
+ const str = JSON.stringify(value);
346
+ return str ? str.length * 2 : 0;
347
+ } catch {
348
+ return 0;
308
349
  }
350
+ }
309
351
 
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;
352
+ export function deepEqual(a: unknown, b: unknown, seen = new WeakSet<object>()): boolean {
353
+ if (Object.is(a, b)) return true;
314
354
 
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
- }
355
+ if (typeof a !== typeof b) return false;
356
+ if (a === null || b === null) return a === b;
357
+
358
+ if (a instanceof Date && b instanceof Date) {
359
+ return a.getTime() === b.getTime();
360
+ }
361
+
362
+ if (a instanceof RegExp && b instanceof RegExp) {
363
+ return a.source === b.source && a.flags === b.flags;
364
+ }
319
365
 
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);
366
+ if (a instanceof Map && b instanceof Map) {
367
+ if (a.size !== b.size) return false;
368
+ for (const [key, val] of a) {
369
+ if (!b.has(key) || !deepEqual(val, b.get(key), seen)) return false;
370
+ }
371
+ return true;
372
+ }
325
373
 
326
- if (keysA.length !== keysB.length) return false;
327
- return keysA.every((key) => this.deepEqual(aObj[key], bObj[key]));
374
+ if (a instanceof Set && b instanceof Set) {
375
+ if (a.size !== b.size) return false;
376
+ for (const val of a) {
377
+ if (!b.has(val)) return false;
328
378
  }
379
+ return true;
380
+ }
329
381
 
330
- return false;
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));
331
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));
400
+ }
401
+
402
+ return false;
332
403
  }
@@ -299,13 +299,44 @@ Be thorough but practical - focus on real issues.`,
299
299
  const params = tool.parameters;
300
300
 
301
301
  if (params.type === 'object' && params.properties) {
302
- const validInput: Record<string, unknown> = {};
303
302
  const properties = params.properties as Record<string, { type?: string; default?: unknown }>;
303
+ const required = Array.isArray(params.required)
304
+ ? new Set(params.required as string[])
305
+ : new Set<string>();
304
306
 
307
+ const validInput: Record<string, unknown> = {};
305
308
  for (const [key, schema] of Object.entries(properties)) {
306
309
  validInput[key] = this.generateSampleValue(schema.type, schema.default);
307
310
  }
308
311
  testCases.push({ input: validInput });
312
+
313
+ const emptyInput: Record<string, unknown> = {};
314
+ for (const [key, schema] of Object.entries(properties)) {
315
+ if (!required.has(key)) {
316
+ emptyInput[key] = this.generateSampleValue(schema.type, schema.default);
317
+ }
318
+ }
319
+ if (Object.keys(emptyInput).length > 0) {
320
+ testCases.push({ input: emptyInput });
321
+ }
322
+
323
+ const edgeInput: Record<string, unknown> = {};
324
+ for (const [key, schema] of Object.entries(properties)) {
325
+ edgeInput[key] = this.generateEdgeValue(schema.type);
326
+ }
327
+ testCases.push({ input: edgeInput });
328
+
329
+ if (required.size > 0) {
330
+ const missingRequired: Record<string, unknown> = {};
331
+ for (const [key, schema] of Object.entries(properties)) {
332
+ if (!required.has(key)) {
333
+ missingRequired[key] = this.generateSampleValue(schema.type, schema.default);
334
+ }
335
+ }
336
+ testCases.push({ input: missingRequired, shouldThrow: true });
337
+ }
338
+ } else {
339
+ testCases.push({ input: {} });
309
340
  }
310
341
 
311
342
  return testCases;
@@ -331,6 +362,25 @@ Be thorough but practical - focus on real issues.`,
331
362
  }
332
363
  }
333
364
 
365
+ private generateEdgeValue(type?: string): unknown {
366
+ switch (type) {
367
+ case 'string':
368
+ return '';
369
+ case 'number':
370
+ return 0;
371
+ case 'integer':
372
+ return -1;
373
+ case 'boolean':
374
+ return false;
375
+ case 'array':
376
+ return [null, 0, ''];
377
+ case 'object':
378
+ return { unexpected: true };
379
+ default:
380
+ return undefined;
381
+ }
382
+ }
383
+
334
384
  private calculateScore(
335
385
  securityCount: number,
336
386
  logicCount: number,
@@ -9,8 +9,32 @@ export function extractJson(text: string): string | null {
9
9
  cleaned = codeBlockMatch[1].trim();
10
10
  }
11
11
 
12
- const start = cleaned.indexOf('{');
13
- if (start === -1) return null;
12
+ const objectStart = cleaned.indexOf('{');
13
+ const arrayStart = cleaned.indexOf('[');
14
+
15
+ let start: number;
16
+ let open: string;
17
+ let close: string;
18
+
19
+ if (objectStart === -1 && arrayStart === -1) {
20
+ return null;
21
+ } else if (objectStart === -1) {
22
+ start = arrayStart;
23
+ open = '[';
24
+ close = ']';
25
+ } else if (arrayStart === -1) {
26
+ start = objectStart;
27
+ open = '{';
28
+ close = '}';
29
+ } else if (objectStart < arrayStart) {
30
+ start = objectStart;
31
+ open = '{';
32
+ close = '}';
33
+ } else {
34
+ start = arrayStart;
35
+ open = '[';
36
+ close = ']';
37
+ }
14
38
 
15
39
  let depth = 0;
16
40
  let inString = false;
@@ -36,8 +60,8 @@ export function extractJson(text: string): string | null {
36
60
 
37
61
  if (inString) continue;
38
62
 
39
- if (ch === '{') depth++;
40
- else if (ch === '}') {
63
+ if (ch === open) depth++;
64
+ else if (ch === close) {
41
65
  depth--;
42
66
  if (depth === 0) {
43
67
  return cleaned.slice(start, i + 1);
@@ -48,7 +72,7 @@ export function extractJson(text: string): string | null {
48
72
  if (depth > 0) {
49
73
  let repaired = cleaned.slice(start);
50
74
  repaired = repaired.replace(/,\s*$/, '');
51
- repaired += '}'.repeat(depth);
75
+ repaired += close.repeat(depth);
52
76
  try {
53
77
  JSON.parse(repaired);
54
78
  return repaired;