@gotza02/mathinking 2.9.4 → 2.9.6

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 (61) hide show
  1. package/dist/final-demo.js +0 -3
  2. package/dist/final-demo.js.map +1 -1
  3. package/dist/index.js +17 -16
  4. package/dist/index.js.map +1 -1
  5. package/dist/schemas/brain.schema.d.ts +70 -0
  6. package/dist/schemas/brain.schema.d.ts.map +1 -0
  7. package/dist/schemas/brain.schema.js +70 -0
  8. package/dist/schemas/brain.schema.js.map +1 -0
  9. package/dist/security.test.d.ts +2 -0
  10. package/dist/security.test.d.ts.map +1 -0
  11. package/dist/security.test.js +81 -0
  12. package/dist/security.test.js.map +1 -0
  13. package/dist/sse-server.js +39 -8
  14. package/dist/sse-server.js.map +1 -1
  15. package/dist/test-all.js +0 -20
  16. package/dist/test-all.js.map +1 -1
  17. package/dist/test-extended.js +0 -9
  18. package/dist/test-extended.js.map +1 -1
  19. package/dist/test-max-intelligence.js +0 -7
  20. package/dist/test-max-intelligence.js.map +1 -1
  21. package/dist/test-memory.js +0 -2
  22. package/dist/test-memory.js.map +1 -1
  23. package/dist/test-reflective.js +0 -6
  24. package/dist/test-reflective.js.map +1 -1
  25. package/dist/test-resilience.js +0 -8
  26. package/dist/test-resilience.js.map +1 -1
  27. package/dist/tools/orchestrator.d.ts +7 -46
  28. package/dist/tools/orchestrator.d.ts.map +1 -1
  29. package/dist/tools/orchestrator.js +178 -181
  30. package/dist/tools/orchestrator.js.map +1 -1
  31. package/dist/tools/sequential-thinking.d.ts +7 -6
  32. package/dist/tools/sequential-thinking.d.ts.map +1 -1
  33. package/dist/tools/sequential-thinking.js +210 -90
  34. package/dist/tools/sequential-thinking.js.map +1 -1
  35. package/dist/types/index.d.ts +2 -2
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/utils/dag.d.ts +0 -20
  38. package/dist/utils/dag.d.ts.map +1 -1
  39. package/dist/utils/dag.js +8 -48
  40. package/dist/utils/dag.js.map +1 -1
  41. package/dist/utils/memory.d.ts +5 -0
  42. package/dist/utils/memory.d.ts.map +1 -1
  43. package/dist/utils/memory.js +66 -60
  44. package/dist/utils/memory.js.map +1 -1
  45. package/dist/utils/mutex.d.ts +6 -0
  46. package/dist/utils/mutex.d.ts.map +1 -0
  47. package/dist/utils/mutex.js +22 -0
  48. package/dist/utils/mutex.js.map +1 -0
  49. package/dist/utils/resilience.d.ts +11 -5
  50. package/dist/utils/resilience.d.ts.map +1 -1
  51. package/dist/utils/resilience.js +42 -21
  52. package/dist/utils/resilience.js.map +1 -1
  53. package/dist/utils/tool-cache.d.ts +4 -12
  54. package/dist/utils/tool-cache.d.ts.map +1 -1
  55. package/dist/utils/tool-cache.js +12 -46
  56. package/dist/utils/tool-cache.js.map +1 -1
  57. package/dist/utils/vector-memory.d.ts +1 -22
  58. package/dist/utils/vector-memory.d.ts.map +1 -1
  59. package/dist/utils/vector-memory.js +31 -77
  60. package/dist/utils/vector-memory.js.map +1 -1
  61. package/package.json +22 -20
@@ -1,39 +1,72 @@
1
- import * as vm from 'vm';
1
+ import { getQuickJS } from 'quickjs-emscripten';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
2
4
  import { resilienceManager } from '../utils/resilience.js';
3
5
  import { toolCache } from '../utils/tool-cache.js';
4
6
  import { topologicalSortWithLayers, validateDAG, visualizeDAG, getCriticalPath, getMaxParallelism } from '../utils/dag.js';
5
- /**
6
- * Orchestrator Manager (The Body)
7
- *
8
- * Provides DAG-based task execution with:
9
- * - Topological sorting for dependency resolution
10
- * - Parallel execution of independent tasks
11
- * - Tool orchestration via registry
12
- * - Graceful error handling
13
- * - Result aggregation
14
- */
15
7
  export class OrchestratorManager {
16
8
  toolRegistry = {};
17
9
  executionHistory = new Map();
18
10
  lastActionTimestamp = 0;
19
11
  MIN_DELAY_MS = 1000; // 1s delay for orchestrator actions
12
+ quickJS;
13
+ // 🔒 Security: Allowed directory roots for file operations
14
+ ALLOWED_ROOTS = [
15
+ process.cwd(),
16
+ path.join(os.homedir(), 'tmp', 'mathinking')
17
+ ];
20
18
  constructor() {
21
19
  this.registerBuiltInTools();
22
20
  }
21
+ /**
22
+ * 🔒 Security: Validate path to prevent directory traversal attacks
23
+ * Ensures that file operations are limited to allowed directories
24
+ */
25
+ validatePath(inputPath) {
26
+ // Expand ~ to home directory
27
+ const expandedPath = inputPath.replace(/^~/, os.homedir());
28
+ // Resolve to absolute path (also normalizes ../ etc)
29
+ const resolvedPath = path.resolve(expandedPath);
30
+ // Check if path is within allowed roots
31
+ const isAllowed = this.ALLOWED_ROOTS.some(root => {
32
+ const resolvedRoot = path.resolve(root);
33
+ return resolvedPath.startsWith(resolvedRoot);
34
+ });
35
+ if (!isAllowed) {
36
+ throw new Error(`Security: Path "${resolvedPath}" is outside allowed directories: ` +
37
+ `[${this.ALLOWED_ROOTS.join(', ')}]`);
38
+ }
39
+ return resolvedPath;
40
+ }
41
+ async safeEval(code) {
42
+ if (!this.quickJS) {
43
+ this.quickJS = await getQuickJS();
44
+ }
45
+ const vm = this.quickJS.newContext();
46
+ try {
47
+ const result = vm.evalCode(code);
48
+ if (result.error) {
49
+ const error = vm.dump(result.error);
50
+ result.error.dispose();
51
+ throw new Error(String(error));
52
+ }
53
+ const value = vm.dump(result.value);
54
+ result.value.dispose();
55
+ return value;
56
+ }
57
+ finally {
58
+ vm.dispose();
59
+ }
60
+ }
23
61
  sleep(ms) {
24
62
  return new Promise((resolve) => setTimeout(resolve, ms));
25
63
  }
26
- /**
27
- * Register built-in tools for testing and common operations
28
- */
29
64
  registerBuiltInTools() {
30
- // Echo tool - returns input as output (useful for testing)
31
65
  this.registerTool({
32
66
  name: 'echo',
33
67
  description: 'Returns the input as output',
34
68
  execute: async (input) => input
35
69
  });
36
- // Delay tool - waits for specified milliseconds
37
70
  this.registerTool({
38
71
  name: 'delay',
39
72
  description: 'Waits for specified milliseconds',
@@ -43,7 +76,6 @@ export class OrchestratorManager {
43
76
  return { delayed: ms, timestamp: new Date().toISOString() };
44
77
  }
45
78
  });
46
- // Transform tool - applies simple transformations
47
79
  this.registerTool({
48
80
  name: 'transform',
49
81
  description: 'Applies transformations to data',
@@ -66,7 +98,6 @@ export class OrchestratorManager {
66
98
  }
67
99
  }
68
100
  });
69
- // Aggregate tool - combines multiple inputs
70
101
  this.registerTool({
71
102
  name: 'aggregate',
72
103
  description: 'Aggregates multiple values',
@@ -90,7 +121,6 @@ export class OrchestratorManager {
90
121
  }
91
122
  }
92
123
  });
93
- // HTTP fetch tool - Real implementation using native fetch
94
124
  this.registerTool({
95
125
  name: 'fetch',
96
126
  description: 'Fetches data from a URL',
@@ -99,8 +129,15 @@ export class OrchestratorManager {
99
129
  const method = input.method || 'GET';
100
130
  const body = input.body ? JSON.stringify(input.body) : undefined;
101
131
  const headers = input.headers || {};
132
+ const controller = new AbortController();
133
+ const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
102
134
  try {
103
- const response = await fetch(url, { method, body, headers });
135
+ const response = await fetch(url, {
136
+ method,
137
+ body,
138
+ headers,
139
+ signal: controller.signal
140
+ });
104
141
  const contentType = response.headers.get('content-type');
105
142
  let data;
106
143
  if (contentType && contentType.includes('application/json')) {
@@ -120,64 +157,103 @@ export class OrchestratorManager {
120
157
  catch (error) {
121
158
  throw new Error(`Fetch failed: ${error instanceof Error ? error.message : String(error)}`);
122
159
  }
160
+ finally {
161
+ clearTimeout(timeoutId);
162
+ }
123
163
  }
124
164
  });
125
- // File System: Read file
126
165
  this.registerTool({
127
166
  name: 'read_file',
128
167
  description: 'Reads content from a file',
129
168
  execute: async (input) => {
130
- const path = (input.file_path || input.path);
169
+ const rawPath = (input.file_path || input.path);
170
+ const validatedPath = this.validatePath(rawPath); // 🔒 Security validation
131
171
  const encoding = input.encoding || 'utf8';
132
172
  const fs = await import('fs/promises');
133
173
  try {
134
- const content = await fs.readFile(path, { encoding });
135
- return { path, content, timestamp: new Date().toISOString() };
174
+ const content = await fs.readFile(validatedPath, { encoding });
175
+ return { path: validatedPath, content, timestamp: new Date().toISOString() };
136
176
  }
137
177
  catch (error) {
138
178
  throw new Error(`Read file failed: ${error instanceof Error ? error.message : String(error)}`);
139
179
  }
140
180
  }
141
181
  });
142
- // File System: Write file
143
182
  this.registerTool({
144
183
  name: 'write_file',
145
184
  description: 'Writes content to a file',
146
185
  execute: async (input) => {
147
- const path = (input.file_path || input.path);
186
+ const rawPath = (input.file_path || input.path);
187
+ const validatedPath = this.validatePath(rawPath); // 🔒 Security validation
148
188
  const content = input.content;
149
189
  const fs = await import('fs/promises');
150
190
  try {
151
- await fs.writeFile(path, content);
152
- return { path, success: true, timestamp: new Date().toISOString() };
191
+ await fs.writeFile(validatedPath, content);
192
+ return { path: validatedPath, success: true, timestamp: new Date().toISOString() };
153
193
  }
154
194
  catch (error) {
155
195
  throw new Error(`Write file failed: ${error instanceof Error ? error.message : String(error)}`);
156
196
  }
157
197
  }
158
198
  });
159
- // File System: List directory
160
199
  this.registerTool({
161
200
  name: 'list_directory',
162
201
  description: 'Lists files in a directory',
163
202
  execute: async (input) => {
164
- const path = (input.dir_path || input.path);
203
+ const rawPath = (input.dir_path || input.path);
204
+ const validatedPath = this.validatePath(rawPath); // 🔒 Security validation
165
205
  const fs = await import('fs/promises');
166
206
  try {
167
- const files = await fs.readdir(path);
168
- return { path, files, timestamp: new Date().toISOString() };
207
+ const files = await fs.readdir(validatedPath);
208
+ return { path: validatedPath, files, timestamp: new Date().toISOString() };
169
209
  }
170
210
  catch (error) {
171
211
  throw new Error(`List directory failed: ${error instanceof Error ? error.message : String(error)}`);
172
212
  }
173
213
  }
174
214
  });
175
- // Shell execute
176
215
  this.registerTool({
177
216
  name: 'shell_execute',
178
217
  description: 'Executes a shell command',
179
218
  execute: async (input) => {
180
219
  const command = input.command;
220
+ const path = await import('path');
221
+ const ALLOWED_COMMANDS = [
222
+ 'ls',
223
+ 'echo',
224
+ 'cat',
225
+ 'mkdir',
226
+ 'rm',
227
+ 'touch',
228
+ 'git',
229
+ 'grep',
230
+ 'pwd',
231
+ 'find',
232
+ 'whoami'
233
+ ];
234
+ const tokenRegex = /[^\s"]+|"([^"]*)"/gi;
235
+ const tokens = [];
236
+ let match;
237
+ while ((match = tokenRegex.exec(command)) !== null) {
238
+ tokens.push(match[1] ? match[1] : match[0]); // match[1] is the content inside quotes
239
+ }
240
+ let binaryToken = tokens[0];
241
+ for (const token of tokens) {
242
+ if (!token.includes('=')) {
243
+ binaryToken = token;
244
+ break;
245
+ }
246
+ }
247
+ const binary = path.basename(binaryToken);
248
+ if (!ALLOWED_COMMANDS.includes(binary)) {
249
+ throw new Error(`Command "${binary}" is not allowed. Allowed: ${ALLOWED_COMMANDS.join(', ')}`);
250
+ }
251
+ if (/[\n\r;&|`>]|\$\(/.test(command)) {
252
+ throw new Error('Command contains disallowed characters (newlines, operators, redirection) to prevent injection.');
253
+ }
254
+ if (binary === 'git' && (command.includes('-c') || command.includes('--config'))) {
255
+ throw new Error('Git configuration flags are not allowed for security reasons.');
256
+ }
181
257
  const { exec } = await import('child_process');
182
258
  const { promisify } = await import('util');
183
259
  const execAsync = promisify(exec);
@@ -190,7 +266,6 @@ export class OrchestratorManager {
190
266
  }
191
267
  }
192
268
  });
193
- // Web Search tool - Enhanced with Google, Exa, and Brave API support
194
269
  this.registerTool({
195
270
  name: 'web_search',
196
271
  description: 'Searches the web for information (Prioritizes Tools like Brave/Exa, then MPC/Google, then Fallback)',
@@ -200,7 +275,6 @@ export class OrchestratorManager {
200
275
  const freshness = input.freshness || 'year'; // day, week, month, year
201
276
  const getFreshnessParams = (provider) => {
202
277
  if (provider === 'google') {
203
- // d[number], w[number], m[number], y[number]
204
278
  if (freshness === 'day')
205
279
  return 'd1';
206
280
  if (freshness === 'week')
@@ -210,7 +284,6 @@ export class OrchestratorManager {
210
284
  return 'y1'; // Default to past year for "latest" relevance
211
285
  }
212
286
  if (provider === 'brave') {
213
- // pd, pw, pm, py
214
287
  if (freshness === 'day')
215
288
  return 'pd';
216
289
  if (freshness === 'week')
@@ -220,7 +293,6 @@ export class OrchestratorManager {
220
293
  return 'py';
221
294
  }
222
295
  if (provider === 'scraper') {
223
- // qdr:d, qdr:w, qdr:m, qdr:y
224
296
  if (freshness === 'day')
225
297
  return '&tbs=qdr:d';
226
298
  if (freshness === 'week')
@@ -231,7 +303,6 @@ export class OrchestratorManager {
231
303
  }
232
304
  return '';
233
305
  };
234
- // 1. Brave Search (Tool) - Priority 1A
235
306
  const braveApiKey = process.env.BRAVE_SEARCH_API_KEY;
236
307
  if (braveApiKey) {
237
308
  try {
@@ -255,10 +326,8 @@ export class OrchestratorManager {
255
326
  console.error('Brave API failed, falling back:', e);
256
327
  }
257
328
  }
258
- // 2. Exa.ai Search (Tool) - Priority 1B
259
329
  if (process.env.EXA_API_KEY) {
260
330
  try {
261
- // Exa uses startPublishedDate for freshness
262
331
  let startPublishedDate;
263
332
  const now = new Date();
264
333
  if (freshness === 'day')
@@ -300,7 +369,6 @@ export class OrchestratorManager {
300
369
  console.error('Exa Search failed, falling back:', e);
301
370
  }
302
371
  }
303
- // 3. Google Custom Search (MPC/Gemini) - Priority 2
304
372
  if (process.env.GOOGLE_SEARCH_API_KEY && process.env.GOOGLE_SEARCH_CX) {
305
373
  try {
306
374
  const dateRestrict = getFreshnessParams('google');
@@ -324,50 +392,16 @@ export class OrchestratorManager {
324
392
  console.error('Google Search failed, falling back:', e);
325
393
  }
326
394
  }
327
- // 4. Fallback to basic scraper
328
395
  try {
329
- const freshParam = getFreshnessParams('scraper');
330
- const searchUrl = `https://www.google.com/search?q=${encodeURIComponent(query)}${freshParam}`;
331
- const response = await fetch(searchUrl, {
332
- headers: {
333
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
334
- }
335
- });
336
- const html = await response.text();
337
- const results = [];
338
- // Enhanced patterns for Google search results
339
- const patterns = [
340
- // Standard snippets
341
- /<div class="vvSyob">(.+?)<\/div>| <div class="BNeawe s3v9rd AP7Wnd">(.+?)<\/div>/g,
342
- // Mobile snippets
343
- /<div class="BNeawe vvSyob s3v9rd AP7Wnd">(.+?)<\/div>/g,
344
- // Alternative layout snippets
345
- /<span class="aCOpbd">(.+?)<\/span>/g
346
- ];
347
- for (const pattern of patterns) {
348
- let match;
349
- while ((match = pattern.exec(html)) !== null && results.length < numResults) {
350
- const text = (match[1] || match[2] || match[0]).replace(/<[^>]+>/g, '').trim();
351
- if (text.length > 20 && !results.some((r) => r.snippet === text)) {
352
- results.push({ snippet: text });
353
- }
354
- }
355
- }
356
- // Fallback: If no snippets found, try to extract anything that looks like a title/link description
357
- if (results.length === 0) {
358
- const linkDescRegex = /<h3[^>]*>(.+?)<\/h3>/g;
359
- let match;
360
- while ((match = linkDescRegex.exec(html)) !== null && results.length < numResults) {
361
- const text = match[1].replace(/<[^>]+>/g, '').trim();
362
- if (text.length > 10)
363
- results.push({ snippet: text });
364
- }
365
- }
396
+ console.warn('[Web Search] Scraper fallback is deprecated/unstable. Please set GOOGLE_SEARCH_API_KEY or BRAVE_SEARCH_API_KEY.');
366
397
  return {
367
398
  query,
368
- source: 'Scraper Fallback',
369
- results,
370
- searchUrl,
399
+ source: 'System',
400
+ results: [{
401
+ title: 'Configuration Required',
402
+ snippet: 'Web search scraping is disabled due to reliability issues. Please configure GOOGLE_SEARCH_API_KEY (and CX) or BRAVE_SEARCH_API_KEY in your environment to enable search functionality.',
403
+ url: 'https://console.cloud.google.com/apis/credentials'
404
+ }],
371
405
  timestamp: new Date().toISOString()
372
406
  };
373
407
  }
@@ -376,7 +410,6 @@ export class OrchestratorManager {
376
410
  }
377
411
  }
378
412
  });
379
- // Memory Save tool
380
413
  this.registerTool({
381
414
  name: 'memory_save',
382
415
  description: 'Saves a fact or information to long-term memory',
@@ -387,7 +420,6 @@ export class OrchestratorManager {
387
420
  return { success: true, message: `Saved to memory: ${key}`, entry };
388
421
  }
389
422
  });
390
- // Memory Query tool - Enhanced with filters
391
423
  this.registerTool({
392
424
  name: 'memory_query',
393
425
  description: 'Queries long-term memory with optional category and tag filters',
@@ -401,7 +433,6 @@ export class OrchestratorManager {
401
433
  return { query, results, count: results.length };
402
434
  }
403
435
  });
404
- // Memory Delete tool
405
436
  this.registerTool({
406
437
  name: 'memory_delete',
407
438
  description: 'Deletes an entry from long-term memory by its ID or Key',
@@ -412,7 +443,6 @@ export class OrchestratorManager {
412
443
  return { success: true, ...result };
413
444
  }
414
445
  });
415
- // Vector Memory Save
416
446
  this.registerTool({
417
447
  name: 'vector_save',
418
448
  description: 'Saves text to semantic vector memory (The Scholar)',
@@ -423,7 +453,6 @@ export class OrchestratorManager {
423
453
  return { success: true, id: entry.id, message: 'Saved to vector memory' };
424
454
  }
425
455
  });
426
- // Vector Memory Search
427
456
  this.registerTool({
428
457
  name: 'vector_search',
429
458
  description: 'Semantically searches vector memory',
@@ -434,49 +463,39 @@ export class OrchestratorManager {
434
463
  return { query, results };
435
464
  }
436
465
  });
437
- // Compute tool - performs calculations
438
466
  this.registerTool({
439
467
  name: 'compute',
440
468
  description: 'Performs mathematical computations',
441
469
  execute: async (input) => {
442
470
  const expression = input.expression;
443
471
  const variables = input.variables || {};
444
- // Simple expression evaluator (safe subset)
445
472
  let resultExpr = expression;
446
473
  for (const [key, value] of Object.entries(variables)) {
447
474
  resultExpr = resultExpr.replace(new RegExp(`\\b${key}\\b`, 'g'), String(value));
448
475
  }
449
- // Evaluate simple math expressions
450
476
  try {
451
- // Use vm for sandboxed execution
452
- if (/^[\d\s+\-*/().]+$/.test(resultExpr)) {
453
- return { result: vm.runInNewContext(resultExpr, {}) };
477
+ if (/^[\d\s+\-*/().,MathPIE_a-zA-Z]+$/.test(resultExpr) &&
478
+ !resultExpr.includes('process') &&
479
+ !resultExpr.includes('require') &&
480
+ !resultExpr.includes('import')) {
481
+ const result = await this.safeEval(resultExpr);
482
+ return { result };
454
483
  }
455
484
  return { error: 'Invalid expression', expression: resultExpr };
456
485
  }
457
- catch {
458
- return { error: 'Computation failed', expression: resultExpr };
486
+ catch (error) {
487
+ return { error: 'Computation failed', expression: resultExpr, details: String(error) };
459
488
  }
460
489
  }
461
490
  });
462
491
  }
463
- /**
464
- * Register a custom tool
465
- */
466
492
  registerTool(tool) {
467
493
  this.toolRegistry[tool.name] = tool;
468
494
  }
469
- /**
470
- * Get list of registered tools
471
- */
472
495
  getRegisteredTools() {
473
496
  return Object.keys(this.toolRegistry);
474
497
  }
475
- /**
476
- * Process an orchestrator action
477
- */
478
498
  async process(input) {
479
- // Rate limiting / Flood protection
480
499
  const now = Date.now();
481
500
  const elapsed = now - this.lastActionTimestamp;
482
501
  if (elapsed < this.MIN_DELAY_MS) {
@@ -501,9 +520,6 @@ export class OrchestratorManager {
501
520
  };
502
521
  }
503
522
  }
504
- /**
505
- * Delete an execution plan from history
506
- */
507
523
  deletePlan(input) {
508
524
  if (!input.planId) {
509
525
  return {
@@ -524,9 +540,6 @@ export class OrchestratorManager {
524
540
  message: `Successfully deleted execution history for plan: ${input.planId}`
525
541
  };
526
542
  }
527
- /**
528
- * Validate an execution plan
529
- */
530
543
  validatePlan(input) {
531
544
  if (!input.plan) {
532
545
  return {
@@ -537,7 +550,6 @@ export class OrchestratorManager {
537
550
  }
538
551
  const validation = validateDAG(input.plan);
539
552
  const errors = [...validation.errors];
540
- // Check for unregistered tools
541
553
  for (const task of input.plan.tasks) {
542
554
  if (!this.toolRegistry[task.toolName]) {
543
555
  errors.push(`Task "${task.id}" uses unregistered tool "${task.toolName}". Available tools: ${this.getRegisteredTools().join(', ')}`);
@@ -559,9 +571,6 @@ export class OrchestratorManager {
559
571
  executionOrder: topoResult.layers
560
572
  };
561
573
  }
562
- /**
563
- * Execute a plan with parallel processing
564
- */
565
574
  async executePlan(input) {
566
575
  if (!input.plan) {
567
576
  return {
@@ -569,14 +578,12 @@ export class OrchestratorManager {
569
578
  message: 'Execution plan is required'
570
579
  };
571
580
  }
572
- // Validate first
573
581
  const validation = this.validatePlan(input);
574
582
  if (!validation.success) {
575
583
  return validation;
576
584
  }
577
585
  const plan = input.plan;
578
586
  const startTime = new Date();
579
- // Get execution layers
580
587
  const topoResult = topologicalSortWithLayers(plan);
581
588
  const taskMap = new Map(plan.tasks.map((t) => [t.id, t]));
582
589
  const taskResults = [];
@@ -586,19 +593,44 @@ export class OrchestratorManager {
586
593
  console.log(`📊 Total tasks: ${plan.tasks.length}`);
587
594
  console.log(`📦 Execution layers: ${topoResult.layers.length}`);
588
595
  console.log(`⚡ Max parallelism: ${Math.max(...topoResult.layers.map((l) => l.length))}\n`);
589
- // Execute layer by layer
590
596
  for (let layerIndex = 0; layerIndex < topoResult.layers.length; layerIndex++) {
591
597
  const layer = topoResult.layers[layerIndex];
592
598
  console.log(`\n📍 Layer ${layerIndex + 1}: Executing ${layer.length} task(s) in parallel`);
593
599
  console.log(` Tasks: [${layer.join(', ')}]`);
594
- // Execute all tasks in this layer in parallel
595
600
  const layerPromises = layer.map(async (taskId) => {
596
601
  const task = taskMap.get(taskId);
602
+ const dependencies = task.dependencies.map(d => taskResults.find(r => r.taskId === d));
603
+ const failedDep = dependencies.find(d => d?.status === 'failed');
604
+ const skippedDep = dependencies.find(d => d?.status === 'skipped');
605
+ if (failedDep) {
606
+ console.log(` ○ Skipped: ${task.name} (Dependency "${failedDep.taskName}" failed)`);
607
+ return {
608
+ taskId: task.id,
609
+ taskName: task.name,
610
+ status: 'skipped',
611
+ error: `Dependency ${failedDep.taskName} failed`,
612
+ startTime: new Date().toISOString(),
613
+ endTime: new Date().toISOString(),
614
+ duration: 0,
615
+ retries: 0
616
+ };
617
+ }
618
+ if (skippedDep) {
619
+ console.log(` ○ Skipped: ${task.name} (Dependency "${skippedDep.taskName}" skipped)`);
620
+ return {
621
+ taskId: task.id,
622
+ taskName: task.name,
623
+ status: 'skipped',
624
+ error: `Dependency ${skippedDep.taskName} skipped`,
625
+ startTime: new Date().toISOString(),
626
+ endTime: new Date().toISOString(),
627
+ duration: 0,
628
+ retries: 0
629
+ };
630
+ }
597
631
  return this.executeTask(task, taskOutputs, errors);
598
632
  });
599
- // Wait for all tasks in this layer to complete
600
633
  const layerResults = await Promise.all(layerPromises);
601
- // Store results
602
634
  for (const result of layerResults) {
603
635
  taskResults.push(result);
604
636
  if (result.status === 'completed' && result.result !== undefined) {
@@ -608,7 +640,6 @@ export class OrchestratorManager {
608
640
  }
609
641
  const endTime = new Date();
610
642
  const totalDuration = endTime.getTime() - startTime.getTime();
611
- // Aggregate results
612
643
  const aggregatedResults = {};
613
644
  for (const [taskId, output] of taskOutputs) {
614
645
  aggregatedResults[taskId] = output;
@@ -647,9 +678,6 @@ export class OrchestratorManager {
647
678
  executionOrder: topoResult.layers
648
679
  };
649
680
  }
650
- /**
651
- * Execute a single task with error handling, retries, and resilience
652
- */
653
681
  async executeTask(task, taskOutputs, errors) {
654
682
  const startTime = new Date();
655
683
  const maxRetries = task.retryCount || 0;
@@ -658,9 +686,9 @@ export class OrchestratorManager {
658
686
  let repairAttempts = 0; // Guard against infinite repair loops
659
687
  let currentInput = task.toolInput; // Allow modification
660
688
  console.log(` ⚙️ Starting: ${task.name} (${task.id})`);
661
- // 1. Check Cache
689
+ let resolvedInput = this.resolveInput(currentInput, taskOutputs);
662
690
  if (['fetch', 'web_search'].includes(task.toolName)) {
663
- const cached = toolCache.get(task.toolName, currentInput);
691
+ const cached = toolCache.get(task.toolName, resolvedInput);
664
692
  if (cached) {
665
693
  console.log(` ⚡ Cached Hit: ${task.name}`);
666
694
  return {
@@ -675,20 +703,14 @@ export class OrchestratorManager {
675
703
  };
676
704
  }
677
705
  }
678
- // Get tool
679
706
  const tool = this.toolRegistry[task.toolName];
680
707
  if (!tool) {
681
- // ... error handling ...
682
708
  const error = `Tool "${task.toolName}" not found`;
683
709
  errors.push({ taskId: task.id, taskName: task.name, error, timestamp: new Date().toISOString() });
684
710
  return { taskId: task.id, taskName: task.name, status: 'failed', error, startTime: startTime.toISOString(), endTime: new Date().toISOString(), duration: 0, retries: 0 };
685
711
  }
686
- // Resolve input
687
- let resolvedInput = this.resolveInput(currentInput, taskOutputs);
688
- // Evaluate condition
689
712
  if (task.runIf) {
690
- // ... existing runIf logic ...
691
- const isConditionMet = this.evaluateCondition(task.runIf, taskOutputs);
713
+ const isConditionMet = await this.evaluateCondition(task.runIf, taskOutputs);
692
714
  if (!isConditionMet) {
693
715
  console.log(` ○ Skipped: ${task.name} (Condition "${task.runIf}" not met)`);
694
716
  return { taskId: task.id, taskName: task.name, status: 'skipped', startTime: startTime.toISOString(), endTime: new Date().toISOString(), duration: 0, retries: 0 };
@@ -701,7 +723,6 @@ export class OrchestratorManager {
701
723
  : await tool.execute(resolvedInput);
702
724
  const endTime = new Date();
703
725
  console.log(` ✓ Completed: ${task.name} (${endTime.getTime() - startTime.getTime()}ms)`);
704
- // Update Cache
705
726
  if (['fetch', 'web_search'].includes(task.toolName)) {
706
727
  toolCache.set(task.toolName, currentInput, result);
707
728
  }
@@ -718,33 +739,27 @@ export class OrchestratorManager {
718
739
  }
719
740
  catch (err) {
720
741
  const errorMessage = err instanceof Error ? err.message : String(err);
721
- // 🛡️ Resilience / Self-Healing
722
- // Only attempt repair if we haven't exceeded the limit (hardcoded to 3 for safety)
723
742
  if (repairAttempts < 3) {
724
- // Ask the Immune System what to do
725
743
  const recovery = await resilienceManager.diagnose(err, { ...task, toolInput: currentInput });
726
744
  if (recovery) {
727
745
  console.log(` 🚑 Self-Healing (${recovery.type}): ${recovery.description}`);
728
746
  repairAttempts++; // Increment guard
729
747
  if (recovery.type === 'run_command' && recovery.payload?.command) {
730
- // Execute the fix (e.g., mkdir)
731
748
  try {
732
749
  await this.toolRegistry['shell_execute'].execute({ command: recovery.payload.command });
733
- continue;
750
+ // continue; // Let it fall through to retry logic
734
751
  }
735
752
  catch (fixErr) {
736
753
  console.log(` ❌ Fix failed: ${fixErr}`);
737
754
  }
738
755
  }
739
756
  else if (recovery.type === 'modify_input' && recovery.payload) {
740
- // Modify input for next attempt
741
757
  currentInput = { ...currentInput, ...recovery.payload };
742
758
  resolvedInput = this.resolveInput(currentInput, taskOutputs);
743
- continue;
759
+ // continue; // Let it fall through to retry logic
744
760
  }
745
761
  }
746
762
  }
747
- // Standard Retry Logic
748
763
  retries++;
749
764
  if (retries <= maxRetries) {
750
765
  const backoff = Math.min(retryDelay * Math.pow(2, retries - 1), 30000);
@@ -752,14 +767,12 @@ export class OrchestratorManager {
752
767
  await new Promise((resolve) => setTimeout(resolve, backoff));
753
768
  }
754
769
  else {
755
- // 🚨 ACTIVE LEARNING: All retries failed. Ask Brain for a miracle.
756
770
  console.log(` 🚨 All retries failed for: ${task.name}. Requesting mid-execution re-plan...`);
757
771
  const replanResult = await resilienceManager.requestStrategicReplan(errorMessage, task);
758
772
  if (replanResult?.success && replanResult.executionPlan) {
759
773
  console.log(` ✨ MIRACLE! Brain provided a recovery plan: ${replanResult.executionPlan.name}`);
760
774
  const subResult = await this.executePlan({ action: 'execute_plan', plan: replanResult.executionPlan });
761
775
  if (subResult.success) {
762
- // If recovery plan succeeded, we can technically count the original task as "recovered"
763
776
  return {
764
777
  taskId: task.id,
765
778
  taskName: task.name,
@@ -780,28 +793,24 @@ export class OrchestratorManager {
780
793
  }
781
794
  return { taskId: task.id, taskName: task.name, status: 'failed', error: 'Unknown', startTime: startTime.toISOString(), endTime: new Date().toISOString(), duration: 0, retries };
782
795
  }
783
- evaluateCondition(condition, taskOutputs) {
784
- // 1. Resolve placeholders in the condition string
785
- const resolvedCondition = this.resolveValue(condition, taskOutputs);
786
- // 2. Simple evaluation for common cases
787
- // Handling literal booleans
788
- if (resolvedCondition === 'true')
789
- return true;
790
- if (resolvedCondition === 'false')
791
- return false;
792
- try {
793
- // 3. Safe evaluation using vm (sandboxed)
794
- // This prevents access to global objects like process, require, etc.
795
- return !!vm.runInNewContext(resolvedCondition, {});
796
- }
797
- catch {
798
- return false;
796
+ async evaluateCondition(condition, taskOutputs) {
797
+ const resolvedCondition = this.resolveValue(condition, taskOutputs, true);
798
+ if (typeof resolvedCondition === 'boolean')
799
+ return resolvedCondition;
800
+ if (typeof resolvedCondition === 'string') {
801
+ if (resolvedCondition === 'true')
802
+ return true;
803
+ if (resolvedCondition === 'false')
804
+ return false;
805
+ try {
806
+ return !!(await this.safeEval(resolvedCondition));
807
+ }
808
+ catch {
809
+ return false;
810
+ }
799
811
  }
812
+ return !!resolvedCondition;
800
813
  }
801
- /**
802
- * Resolve input placeholders with outputs from previous tasks
803
- * Supports syntax: "${taskId.property}" or "${taskId}"
804
- */
805
814
  resolveInput(input, taskOutputs) {
806
815
  const resolved = {};
807
816
  for (const [key, value] of Object.entries(input)) {
@@ -809,18 +818,18 @@ export class OrchestratorManager {
809
818
  }
810
819
  return resolved;
811
820
  }
812
- resolveValue(value, taskOutputs) {
821
+ resolveValue(value, taskOutputs, safeForEval = false) {
813
822
  if (typeof value === 'string') {
814
- // Check for placeholder pattern: ${taskId} or ${taskId.property}
815
823
  const placeholderRegex = /\$\{([^}]+)\}/g;
816
- // If entire string is a single placeholder, return the actual value (not stringified)
817
824
  const singleMatch = value.match(/^\$\{([^}]+)\}$/);
818
825
  if (singleMatch) {
819
826
  return this.resolvePlaceholder(singleMatch[1], taskOutputs);
820
827
  }
821
- // Otherwise, replace placeholders in string
822
828
  return value.replace(placeholderRegex, (_, placeholder) => {
823
829
  const resolved = this.resolvePlaceholder(placeholder, taskOutputs);
830
+ if (safeForEval && typeof resolved === 'string') {
831
+ return resolved.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
832
+ }
824
833
  return String(resolved);
825
834
  });
826
835
  }
@@ -842,10 +851,8 @@ export class OrchestratorManager {
842
851
  if (parts.length === 1) {
843
852
  return output;
844
853
  }
845
- // Navigate nested properties
846
854
  let current = output;
847
855
  for (let i = 1; i < parts.length; i++) {
848
- // Allow traversal of objects and strings (for .length etc)
849
856
  if (current && (typeof current === 'object' || typeof current === 'string')) {
850
857
  current = current[parts[i]];
851
858
  }
@@ -855,18 +862,12 @@ export class OrchestratorManager {
855
862
  }
856
863
  return current;
857
864
  }
858
- /**
859
- * Execute a promise with timeout
860
- */
861
865
  async executeWithTimeout(promise, timeoutMs) {
862
866
  return Promise.race([
863
867
  promise,
864
868
  new Promise((_, reject) => setTimeout(() => reject(new Error(`Task timed out after ${timeoutMs}ms`)), timeoutMs))
865
869
  ]);
866
870
  }
867
- /**
868
- * Get execution status for a plan
869
- */
870
871
  getExecutionStatus(input) {
871
872
  if (!input.planId) {
872
873
  return {
@@ -887,9 +888,6 @@ export class OrchestratorManager {
887
888
  result
888
889
  };
889
890
  }
890
- /**
891
- * Format execution summary for display
892
- */
893
891
  formatExecutionSummary(result) {
894
892
  const lines = [
895
893
  '┌─────────────────────────────────────────────────┐',
@@ -914,6 +912,5 @@ export class OrchestratorManager {
914
912
  return lines.join('\n');
915
913
  }
916
914
  }
917
- // Export singleton instance
918
915
  export const orchestratorManager = new OrchestratorManager();
919
916
  //# sourceMappingURL=orchestrator.js.map