minovative-mind-cli 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/services/agent/commandApproval.js +5 -2
- package/dist/services/agent/slashCommands.js +78 -51
- package/dist/services/agent-tools.d.ts +4 -3
- package/dist/services/agent-tools.js +32 -79
- package/dist/services/agent.d.ts +5 -6
- package/dist/services/agent.js +11 -15
- package/dist/services/ai.d.ts +19 -0
- package/dist/services/ai.js +108 -1
- package/dist/services/chatHistoryService.d.ts +95 -2
- package/dist/services/chatHistoryService.js +236 -9
- package/dist/services/contextAgent.js +161 -77
- package/dist/services/orchestration/investigationAgent.js +101 -84
- package/dist/services/orchestration/investigationOrchestrator.js +6 -2
- package/dist/services/orchestration/orchestrator.js +6 -3
- package/dist/services/orchestration/scopedTools.js +5 -0
- package/dist/services/orchestration/subAgent.d.ts +31 -1
- package/dist/services/orchestration/subAgent.js +153 -2
- package/dist/utils/analysisRunner.d.ts +29 -0
- package/dist/utils/analysisRunner.js +200 -5
- package/dist/utils/contextPrompts.d.ts +19 -3
- package/dist/utils/contextPrompts.js +144 -26
- package/dist/utils/historyPrompt.d.ts +92 -1
- package/dist/utils/historyPrompt.js +166 -2
- package/dist/utils/symbolExtractor.d.ts +12 -0
- package/dist/utils/symbolExtractor.js +946 -0
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +10 -7
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
|
@@ -9,7 +9,48 @@ import { debugLog } from '../utils/logger.js';
|
|
|
9
9
|
import { buildDependencyGraph } from '../utils/dependencyTracer.js';
|
|
10
10
|
import { runEphemeralScript } from '../utils/analysisRunner.js';
|
|
11
11
|
import { getMetricCollector } from './metrics.js';
|
|
12
|
+
import { estimateTokenCount, pruneTextToTokenBudget } from '../utils/historyPrompt.js';
|
|
12
13
|
const metrics = getMetricCollector();
|
|
14
|
+
/** Token budget constants for context optimization */
|
|
15
|
+
const MAX_TREE_TOKENS = 12000;
|
|
16
|
+
const MAX_CHAT_HISTORY_ROUTER_TOKENS = 3000;
|
|
17
|
+
const MAX_CHAT_HISTORY_INVESTIGATION_TOKENS = 4500;
|
|
18
|
+
const MAX_SINGLE_FILE_TOKENS = 25000;
|
|
19
|
+
const MAX_TOTAL_FILE_TOKENS = 150000;
|
|
20
|
+
const MAX_TOTAL_FILES = 30;
|
|
21
|
+
const MAX_TOOL_OUTPUT_TOKENS = 10000;
|
|
22
|
+
/**
|
|
23
|
+
* Bounds individual file content to ensure it does not exceed the single-file token budget.
|
|
24
|
+
*
|
|
25
|
+
* @param filePath The file path.
|
|
26
|
+
* @param content Raw file text.
|
|
27
|
+
* @returns Budget-constrained file text.
|
|
28
|
+
*/
|
|
29
|
+
function boundFileContent(filePath, content) {
|
|
30
|
+
const tokens = estimateTokenCount(content);
|
|
31
|
+
if (tokens <= MAX_SINGLE_FILE_TOKENS) {
|
|
32
|
+
return content;
|
|
33
|
+
}
|
|
34
|
+
debugLog(`Truncating large file context for ${filePath}: ${tokens} tokens -> capped at ${MAX_SINGLE_FILE_TOKENS}`);
|
|
35
|
+
return pruneTextToTokenBudget(content, MAX_SINGLE_FILE_TOKENS, {
|
|
36
|
+
truncationMarker: `\n\n... [File content truncated by Context Agent: ${tokens} estimated tokens exceeded the ${MAX_SINGLE_FILE_TOKENS} token limit. Use read_file with line ranges if specific sections are needed.] ...\n\n`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Bounds tool output text to prevent massive outputs from blowing the context window during investigation turns.
|
|
41
|
+
*
|
|
42
|
+
* @param output Raw tool output string.
|
|
43
|
+
* @returns Budget-constrained tool output.
|
|
44
|
+
*/
|
|
45
|
+
function boundToolOutput(output) {
|
|
46
|
+
const tokens = estimateTokenCount(output);
|
|
47
|
+
if (tokens <= MAX_TOOL_OUTPUT_TOKENS) {
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
return pruneTextToTokenBudget(output, MAX_TOOL_OUTPUT_TOKENS, {
|
|
51
|
+
truncationMarker: `\n\n... [Tool output truncated: exceeded ${MAX_TOOL_OUTPUT_TOKENS} tokens limit] ...\n`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
13
54
|
async function detectProjectType(workspaceRoot) {
|
|
14
55
|
const types = [];
|
|
15
56
|
const fileExists = async (fileName) => {
|
|
@@ -149,7 +190,8 @@ export async function routeIntent(userRequest, chatHistory = '', abortSignal) {
|
|
|
149
190
|
const session = createIntentRouterSession();
|
|
150
191
|
let prompt = `User Request: "${userRequest}"`;
|
|
151
192
|
if (chatHistory) {
|
|
152
|
-
|
|
193
|
+
const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_ROUTER_TOKENS, { fromStart: true });
|
|
194
|
+
prompt = `Previous Conversation Context:\n${prunedHistory}\n\n${prompt}`;
|
|
153
195
|
}
|
|
154
196
|
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
155
197
|
const text = result.response.text()?.trim() || '{}';
|
|
@@ -183,7 +225,8 @@ export async function evaluateExecutionComplexity(userRequest, investigationSumm
|
|
|
183
225
|
Investigation Summary: ${investigationSummary || 'None (0 files needed)'}
|
|
184
226
|
Number of Relevant Files: ${numRelevantFiles}`;
|
|
185
227
|
if (chatHistory) {
|
|
186
|
-
|
|
228
|
+
const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_ROUTER_TOKENS, { fromStart: true });
|
|
229
|
+
prompt = `Previous Conversation Context:\n${prunedHistory}\n\n${prompt}`;
|
|
187
230
|
}
|
|
188
231
|
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
189
232
|
const text = result.response.text()?.trim() || '{}';
|
|
@@ -235,8 +278,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
235
278
|
const label = alias ? `@${alias} (${root})` : `Primary Workspace (${root})`;
|
|
236
279
|
const treeResult = await executeTool(root, 'list_directory', { dirPath: '.', maxDepth: 10 });
|
|
237
280
|
let tree = treeResult.output;
|
|
238
|
-
|
|
239
|
-
|
|
281
|
+
const treeTokens = estimateTokenCount(tree);
|
|
282
|
+
if (treeTokens > MAX_TREE_TOKENS) {
|
|
283
|
+
tree = pruneTextToTokenBudget(tree, MAX_TREE_TOKENS, {
|
|
284
|
+
truncationMarker: '\n... (Project tree truncated to fit token budget)\n',
|
|
285
|
+
});
|
|
240
286
|
}
|
|
241
287
|
const type = await detectProjectType(root);
|
|
242
288
|
if (!alias) {
|
|
@@ -275,15 +321,20 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
275
321
|
if (onProgress)
|
|
276
322
|
onProgress(`⚡ Memory Bank HIT — loaded ${cacheHit.entry.relevantFiles.length} files from cache`);
|
|
277
323
|
const cachedFiles = new Map();
|
|
324
|
+
let cumulativeFileTokens = 0;
|
|
278
325
|
for (const filePath of cacheHit.entry.relevantFiles) {
|
|
279
326
|
if (abortSignal.aborted) {
|
|
280
327
|
const err = new Error('Operation aborted');
|
|
281
328
|
err.name = 'AbortError';
|
|
282
329
|
throw err;
|
|
283
330
|
}
|
|
331
|
+
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
332
|
+
break;
|
|
284
333
|
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
285
334
|
if (!readResult.error) {
|
|
286
|
-
|
|
335
|
+
const boundedText = boundFileContent(filePath, readResult.output);
|
|
336
|
+
cachedFiles.set(filePath, { text: boundedText, inlineData: readResult.inlineData });
|
|
337
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
287
338
|
}
|
|
288
339
|
}
|
|
289
340
|
const MAX_TOTAL_FILES = 30;
|
|
@@ -304,10 +355,14 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
304
355
|
continue;
|
|
305
356
|
if (cachedFiles.size + autoDiscovered.size >= MAX_TOTAL_FILES)
|
|
306
357
|
break;
|
|
358
|
+
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
359
|
+
break;
|
|
307
360
|
autoDiscovered.add(dep);
|
|
308
361
|
const depReadRes = await executeTool(resolved.workspaceRoot, 'read_file', { filePath: dep });
|
|
309
362
|
if (!depReadRes.error) {
|
|
310
|
-
|
|
363
|
+
const boundedText = boundFileContent(dep, depReadRes.output);
|
|
364
|
+
cachedFiles.set(dep, { text: boundedText, inlineData: depReadRes.inlineData });
|
|
365
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
311
366
|
}
|
|
312
367
|
}
|
|
313
368
|
}
|
|
@@ -352,10 +407,12 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
352
407
|
let isInvestigationFinished = false;
|
|
353
408
|
let chainedMessages = [];
|
|
354
409
|
let webSearchSummary = '';
|
|
355
|
-
|
|
410
|
+
let cumulativeFileTokens = 0;
|
|
411
|
+
// Initial prompt with history budget management
|
|
356
412
|
let currentMessage = `User Request: "${userRequest}"\n\nProject Type: ${projectType}\n\nProject Structure:\n${projectTree}`;
|
|
357
413
|
if (chatHistory) {
|
|
358
|
-
|
|
414
|
+
const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_INVESTIGATION_TOKENS, { fromStart: true });
|
|
415
|
+
currentMessage = `Previous Conversation Context:\n${prunedHistory}\n\n` + currentMessage;
|
|
359
416
|
}
|
|
360
417
|
currentMessage += `\n\nStart investigating to find relevant files.`;
|
|
361
418
|
const MAX_TURNS = Infinity;
|
|
@@ -403,11 +460,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
403
460
|
break;
|
|
404
461
|
}
|
|
405
462
|
let isFinished = false;
|
|
406
|
-
|
|
463
|
+
// Log progress for all requested tool calls in this turn
|
|
407
464
|
for (const call of functionCalls) {
|
|
408
465
|
if (abortSignal.aborted)
|
|
409
466
|
break;
|
|
410
|
-
const args = call.args;
|
|
467
|
+
const args = (call.args || {});
|
|
411
468
|
let logMsg = ` [Context Agent] Executing ${call.name}`;
|
|
412
469
|
if (call.name === 'finish_investigation') {
|
|
413
470
|
const filesToRead = args.relevantFiles || [];
|
|
@@ -434,69 +491,84 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
434
491
|
logMsg = ` [Context Agent] Looking for recently modified files`;
|
|
435
492
|
}
|
|
436
493
|
else if (call.name === 'run_analysis_script') {
|
|
437
|
-
|
|
438
|
-
logMsg = ` [Context Agent] Running analysis script${target}`;
|
|
439
|
-
}
|
|
440
|
-
const cleanMsg = logMsg.trim().replace(/^\[Context Agent\] /, '');
|
|
441
|
-
if (onToolCall) {
|
|
442
|
-
onToolCall(cleanMsg, 'Context Agent');
|
|
494
|
+
logMsg = ` [Context Agent] Running analysis script (${args.language})`;
|
|
443
495
|
}
|
|
444
|
-
|
|
445
|
-
onProgress(
|
|
496
|
+
if (onProgress) {
|
|
497
|
+
onProgress(logMsg.trim());
|
|
446
498
|
}
|
|
447
499
|
else {
|
|
448
500
|
console.log(pc.dim(logMsg));
|
|
449
501
|
}
|
|
502
|
+
}
|
|
503
|
+
// Execute tool calls concurrently while preserving 1:1 Gemini response positional ordering
|
|
504
|
+
const functionResponses = await Promise.all(functionCalls.map(async (call) => {
|
|
505
|
+
if (abortSignal.aborted) {
|
|
506
|
+
return {
|
|
507
|
+
functionResponse: {
|
|
508
|
+
name: call.name,
|
|
509
|
+
response: { error: 'Operation aborted.' },
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const args = (call.args || {});
|
|
450
514
|
if (call.name === 'finish_investigation') {
|
|
515
|
+
isInvestigationFinished = true;
|
|
516
|
+
isFinished = true;
|
|
451
517
|
summary = args.summary || '';
|
|
452
518
|
const filesToRead = args.relevantFiles || [];
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
519
|
+
const uniqueFilesToRead = filesToRead.filter((fp) => !relevantFiles.has(fp));
|
|
520
|
+
const readResults = await Promise.all(uniqueFilesToRead.map(async (filePath) => {
|
|
521
|
+
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
522
|
+
return { filePath, readResult };
|
|
523
|
+
}));
|
|
524
|
+
for (const { filePath, readResult } of readResults) {
|
|
525
|
+
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
526
|
+
break;
|
|
527
|
+
if (!readResult.error) {
|
|
528
|
+
const boundedText = boundFileContent(filePath, readResult.output);
|
|
529
|
+
relevantFiles.set(filePath, { text: boundedText, inlineData: readResult.inlineData });
|
|
530
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
460
531
|
}
|
|
461
532
|
}
|
|
462
|
-
|
|
463
|
-
isInvestigationFinished = true;
|
|
464
|
-
// ── Auto-trace reverse dependencies ──
|
|
465
|
-
// When the Context Agent finalizes its investigation, we automatically
|
|
466
|
-
// discover files that DEPEND ON the selected files. This ensures the
|
|
467
|
-
// Execution Agent won't break imports when modifying/deleting/renaming.
|
|
468
|
-
const MAX_TOTAL_FILES = 30;
|
|
533
|
+
// Auto-trace reverse dependencies for all selected files to include active importers
|
|
469
534
|
try {
|
|
535
|
+
const { buildDependencyGraph } = await import('../utils/dependencyTracer.js');
|
|
470
536
|
const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
|
|
471
537
|
const autoDiscovered = new Set();
|
|
472
|
-
|
|
538
|
+
await Promise.all(filesToRead.map(async (filePath) => {
|
|
473
539
|
try {
|
|
474
540
|
const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath);
|
|
475
541
|
const graph = await buildDependencyGraph(resolved.workspaceRoot);
|
|
476
542
|
const reverseDeps = graph.getImportedBy(resolved.relativePath);
|
|
477
543
|
for (const dep of reverseDeps) {
|
|
478
544
|
const aliasedDep = resolved.alias ? `@${resolved.alias}/${dep}` : dep;
|
|
479
|
-
if (!filesToRead.includes(aliasedDep)
|
|
545
|
+
if (!filesToRead.includes(aliasedDep)) {
|
|
480
546
|
autoDiscovered.add(aliasedDep);
|
|
481
547
|
}
|
|
482
548
|
}
|
|
483
549
|
}
|
|
484
|
-
catch
|
|
550
|
+
catch {
|
|
485
551
|
// Ignore path resolution errors for trace dependencies
|
|
486
552
|
}
|
|
487
|
-
}
|
|
488
|
-
// Merge auto-discovered dependents, respecting
|
|
553
|
+
}));
|
|
554
|
+
// Merge auto-discovered dependents, respecting both file count and token budget caps
|
|
489
555
|
const remaining = MAX_TOTAL_FILES - relevantFiles.size;
|
|
556
|
+
const depsToRead = Array.from(autoDiscovered)
|
|
557
|
+
.filter((dep) => !relevantFiles.has(dep))
|
|
558
|
+
.slice(0, Math.max(0, remaining));
|
|
559
|
+
const depReadResults = await Promise.all(depsToRead.map(async (dep) => {
|
|
560
|
+
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath: dep });
|
|
561
|
+
return { dep, readResult };
|
|
562
|
+
}));
|
|
490
563
|
let added = 0;
|
|
491
|
-
for (const dep of
|
|
492
|
-
if (
|
|
564
|
+
for (const { dep, readResult } of depReadResults) {
|
|
565
|
+
if (relevantFiles.size >= MAX_TOTAL_FILES || cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
493
566
|
break;
|
|
494
|
-
if (!
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
567
|
+
if (!readResult.error) {
|
|
568
|
+
const boundedText = boundFileContent(dep, readResult.output);
|
|
569
|
+
relevantFiles.set(dep, { text: boundedText, inlineData: readResult.inlineData });
|
|
570
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
571
|
+
added++;
|
|
500
572
|
}
|
|
501
573
|
}
|
|
502
574
|
if (added > 0) {
|
|
@@ -510,46 +582,50 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
510
582
|
catch {
|
|
511
583
|
// Dependency tracing is best-effort — don't block investigation
|
|
512
584
|
}
|
|
513
|
-
|
|
585
|
+
return {
|
|
514
586
|
functionResponse: {
|
|
515
587
|
name: call.name,
|
|
516
588
|
response: { output: 'Investigation finished.' },
|
|
517
589
|
},
|
|
518
|
-
}
|
|
519
|
-
break;
|
|
590
|
+
};
|
|
520
591
|
}
|
|
521
592
|
else if (call.name === 'list_directory') {
|
|
522
593
|
const listRes = await executeTool(workspaceRoot, 'list_directory', args);
|
|
523
|
-
|
|
594
|
+
const output = boundToolOutput(listRes.output);
|
|
595
|
+
return {
|
|
524
596
|
functionResponse: {
|
|
525
597
|
name: call.name,
|
|
526
598
|
response: {
|
|
527
|
-
output
|
|
599
|
+
output,
|
|
528
600
|
...(listRes.error ? { error: listRes.error } : {}),
|
|
529
601
|
},
|
|
530
602
|
},
|
|
531
|
-
}
|
|
603
|
+
};
|
|
532
604
|
}
|
|
533
605
|
else if (call.name === 'search_codebase') {
|
|
534
606
|
const grepRes = await executeTool(workspaceRoot, 'grep_search', { ...args, workspace: 'all' });
|
|
535
|
-
|
|
607
|
+
const output = boundToolOutput(grepRes.error ? grepRes.error : grepRes.output);
|
|
608
|
+
return {
|
|
536
609
|
functionResponse: {
|
|
537
610
|
name: call.name,
|
|
538
|
-
response: { output
|
|
611
|
+
response: { output },
|
|
539
612
|
},
|
|
540
|
-
}
|
|
613
|
+
};
|
|
541
614
|
}
|
|
542
615
|
else if (call.name === 'read_file') {
|
|
543
616
|
const readRes = await executeTool(workspaceRoot, 'read_file', args);
|
|
544
617
|
if (!readRes.error) {
|
|
545
|
-
|
|
618
|
+
const boundedText = boundFileContent(args.filePath, readRes.output);
|
|
619
|
+
relevantFiles.set(args.filePath, { text: boundedText, inlineData: readRes.inlineData });
|
|
620
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
546
621
|
}
|
|
547
|
-
|
|
622
|
+
const output = boundToolOutput(readRes.error ? readRes.error : readRes.output);
|
|
623
|
+
return {
|
|
548
624
|
functionResponse: {
|
|
549
625
|
name: call.name,
|
|
550
|
-
response: { output
|
|
626
|
+
response: { output },
|
|
551
627
|
},
|
|
552
|
-
}
|
|
628
|
+
};
|
|
553
629
|
}
|
|
554
630
|
else if (call.name === 'perform_web_search') {
|
|
555
631
|
try {
|
|
@@ -564,56 +640,64 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
564
640
|
console.log(pc.dim(` [Web Search] ${webMsg}`));
|
|
565
641
|
}
|
|
566
642
|
const searchSummary = webResult.response.text()?.trim() || 'No relevant information found.';
|
|
567
|
-
|
|
568
|
-
|
|
643
|
+
const boundedSummary = boundToolOutput(searchSummary);
|
|
644
|
+
webSearchSummary += `\nQuery: ${args.query}\nFindings:\n${boundedSummary}\n`;
|
|
645
|
+
return {
|
|
569
646
|
functionResponse: {
|
|
570
647
|
name: call.name,
|
|
571
|
-
response: { output:
|
|
648
|
+
response: { output: boundedSummary },
|
|
572
649
|
},
|
|
573
|
-
}
|
|
650
|
+
};
|
|
574
651
|
}
|
|
575
652
|
catch (e) {
|
|
576
|
-
|
|
653
|
+
return {
|
|
577
654
|
functionResponse: {
|
|
578
655
|
name: call.name,
|
|
579
656
|
response: { error: e.message || 'Failed to search the web' },
|
|
580
657
|
},
|
|
581
|
-
}
|
|
658
|
+
};
|
|
582
659
|
}
|
|
583
660
|
}
|
|
584
661
|
else if (call.name === 'find_dependencies') {
|
|
585
662
|
const depResult = await executeTool(workspaceRoot, 'find_dependencies', args);
|
|
586
|
-
|
|
663
|
+
const output = boundToolOutput(depResult.error ? depResult.error : depResult.output);
|
|
664
|
+
return {
|
|
587
665
|
functionResponse: {
|
|
588
666
|
name: call.name,
|
|
589
|
-
response: { output
|
|
667
|
+
response: { output },
|
|
590
668
|
},
|
|
591
|
-
}
|
|
669
|
+
};
|
|
592
670
|
}
|
|
593
671
|
else if (call.name === 'find_recent_changes') {
|
|
594
672
|
const recentRes = await executeTool(workspaceRoot, 'find_recent_changes', args);
|
|
595
|
-
|
|
673
|
+
const output = boundToolOutput(recentRes.error ? recentRes.error : recentRes.output);
|
|
674
|
+
return {
|
|
596
675
|
functionResponse: {
|
|
597
676
|
name: call.name,
|
|
598
|
-
response: { output
|
|
677
|
+
response: { output },
|
|
599
678
|
},
|
|
600
|
-
}
|
|
679
|
+
};
|
|
601
680
|
}
|
|
602
681
|
else if (call.name === 'run_analysis_script') {
|
|
603
|
-
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, {
|
|
604
|
-
|
|
605
|
-
});
|
|
606
|
-
const output = analysisResult.exitCode === 0
|
|
682
|
+
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, { abortSignal });
|
|
683
|
+
const rawOutput = analysisResult.exitCode === 0
|
|
607
684
|
? analysisResult.stdout || '(script produced no output)'
|
|
608
685
|
: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}`;
|
|
609
|
-
|
|
686
|
+
const output = boundToolOutput(rawOutput);
|
|
687
|
+
return {
|
|
610
688
|
functionResponse: {
|
|
611
689
|
name: call.name,
|
|
612
690
|
response: { output },
|
|
613
691
|
},
|
|
614
|
-
}
|
|
692
|
+
};
|
|
615
693
|
}
|
|
616
|
-
|
|
694
|
+
return {
|
|
695
|
+
functionResponse: {
|
|
696
|
+
name: call.name,
|
|
697
|
+
response: { output: `Unknown tool: ${call.name}` },
|
|
698
|
+
},
|
|
699
|
+
};
|
|
700
|
+
}));
|
|
617
701
|
if (isFinished) {
|
|
618
702
|
break;
|
|
619
703
|
}
|