@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.
- package/dist/final-demo.js +0 -3
- package/dist/final-demo.js.map +1 -1
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/schemas/brain.schema.d.ts +70 -0
- package/dist/schemas/brain.schema.d.ts.map +1 -0
- package/dist/schemas/brain.schema.js +70 -0
- package/dist/schemas/brain.schema.js.map +1 -0
- package/dist/security.test.d.ts +2 -0
- package/dist/security.test.d.ts.map +1 -0
- package/dist/security.test.js +81 -0
- package/dist/security.test.js.map +1 -0
- package/dist/sse-server.js +39 -8
- package/dist/sse-server.js.map +1 -1
- package/dist/test-all.js +0 -20
- package/dist/test-all.js.map +1 -1
- package/dist/test-extended.js +0 -9
- package/dist/test-extended.js.map +1 -1
- package/dist/test-max-intelligence.js +0 -7
- package/dist/test-max-intelligence.js.map +1 -1
- package/dist/test-memory.js +0 -2
- package/dist/test-memory.js.map +1 -1
- package/dist/test-reflective.js +0 -6
- package/dist/test-reflective.js.map +1 -1
- package/dist/test-resilience.js +0 -8
- package/dist/test-resilience.js.map +1 -1
- package/dist/tools/orchestrator.d.ts +7 -46
- package/dist/tools/orchestrator.d.ts.map +1 -1
- package/dist/tools/orchestrator.js +178 -181
- package/dist/tools/orchestrator.js.map +1 -1
- package/dist/tools/sequential-thinking.d.ts +7 -6
- package/dist/tools/sequential-thinking.d.ts.map +1 -1
- package/dist/tools/sequential-thinking.js +210 -90
- package/dist/tools/sequential-thinking.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/dag.d.ts +0 -20
- package/dist/utils/dag.d.ts.map +1 -1
- package/dist/utils/dag.js +8 -48
- package/dist/utils/dag.js.map +1 -1
- package/dist/utils/memory.d.ts +5 -0
- package/dist/utils/memory.d.ts.map +1 -1
- package/dist/utils/memory.js +66 -60
- package/dist/utils/memory.js.map +1 -1
- package/dist/utils/mutex.d.ts +6 -0
- package/dist/utils/mutex.d.ts.map +1 -0
- package/dist/utils/mutex.js +22 -0
- package/dist/utils/mutex.js.map +1 -0
- package/dist/utils/resilience.d.ts +11 -5
- package/dist/utils/resilience.d.ts.map +1 -1
- package/dist/utils/resilience.js +42 -21
- package/dist/utils/resilience.js.map +1 -1
- package/dist/utils/tool-cache.d.ts +4 -12
- package/dist/utils/tool-cache.d.ts.map +1 -1
- package/dist/utils/tool-cache.js +12 -46
- package/dist/utils/tool-cache.js.map +1 -1
- package/dist/utils/vector-memory.d.ts +1 -22
- package/dist/utils/vector-memory.d.ts.map +1 -1
- package/dist/utils/vector-memory.js +31 -77
- package/dist/utils/vector-memory.js.map +1 -1
- package/package.json +22 -20
|
@@ -1,39 +1,72 @@
|
|
|
1
|
-
import
|
|
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, {
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
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: '
|
|
369
|
-
results
|
|
370
|
-
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
689
|
+
let resolvedInput = this.resolveInput(currentInput, taskOutputs);
|
|
662
690
|
if (['fetch', 'web_search'].includes(task.toolName)) {
|
|
663
|
-
const cached = toolCache.get(task.toolName,
|
|
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
|
-
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|