minovative-mind-cli 2.9.1 → 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/README.md +14 -6
- package/dist/services/agent/commandApproval.js +5 -2
- package/dist/services/agent/slashCommands.js +90 -53
- 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 +21 -1
- package/dist/services/ai.js +236 -11
- package/dist/services/chatHistoryService.d.ts +95 -2
- package/dist/services/chatHistoryService.js +236 -9
- package/dist/services/contextAgent.js +196 -81
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.js +101 -84
- package/dist/services/orchestration/investigationCache.d.ts +80 -5
- package/dist/services/orchestration/investigationCache.js +570 -41
- package/dist/services/orchestration/investigationOrchestrator.js +17 -4
- 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 +20 -4
- package/dist/utils/contextPrompts.js +158 -23
- 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 +5 -4
- package/dist/utils/systemPrompts.js +46 -14
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
|
@@ -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) {
|
|
@@ -246,20 +292,49 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
246
292
|
}
|
|
247
293
|
const projectType = primaryProjectType;
|
|
248
294
|
const { lookupInvestigation, saveInvestigation } = await import('./orchestration/investigationCache.js');
|
|
249
|
-
const
|
|
295
|
+
const collector = getMetricCollector();
|
|
296
|
+
const lookupStart = Date.now();
|
|
297
|
+
let cacheHit = null;
|
|
298
|
+
try {
|
|
299
|
+
cacheHit = await lookupInvestigation(workspaceRoot, userRequest, {
|
|
300
|
+
abortSignal,
|
|
301
|
+
onProgress,
|
|
302
|
+
allowSemanticFallback: true,
|
|
303
|
+
});
|
|
304
|
+
if (collector) {
|
|
305
|
+
collector.recordCachePerformance('investigation', Date.now() - lookupStart);
|
|
306
|
+
if (cacheHit) {
|
|
307
|
+
collector.recordCacheHit('investigation');
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
collector.recordCacheMiss('investigation');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
if (err?.name === 'AbortError' || abortSignal?.aborted) {
|
|
316
|
+
throw err;
|
|
317
|
+
}
|
|
318
|
+
debugLog(`Investigation cache lookup error: ${err?.message || err}`);
|
|
319
|
+
}
|
|
250
320
|
if (cacheHit) {
|
|
251
321
|
if (onProgress)
|
|
252
322
|
onProgress(`⚡ Memory Bank HIT — loaded ${cacheHit.entry.relevantFiles.length} files from cache`);
|
|
253
323
|
const cachedFiles = new Map();
|
|
324
|
+
let cumulativeFileTokens = 0;
|
|
254
325
|
for (const filePath of cacheHit.entry.relevantFiles) {
|
|
255
326
|
if (abortSignal.aborted) {
|
|
256
327
|
const err = new Error('Operation aborted');
|
|
257
328
|
err.name = 'AbortError';
|
|
258
329
|
throw err;
|
|
259
330
|
}
|
|
331
|
+
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
332
|
+
break;
|
|
260
333
|
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
261
334
|
if (!readResult.error) {
|
|
262
|
-
|
|
335
|
+
const boundedText = boundFileContent(filePath, readResult.output);
|
|
336
|
+
cachedFiles.set(filePath, { text: boundedText, inlineData: readResult.inlineData });
|
|
337
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
263
338
|
}
|
|
264
339
|
}
|
|
265
340
|
const MAX_TOTAL_FILES = 30;
|
|
@@ -280,10 +355,14 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
280
355
|
continue;
|
|
281
356
|
if (cachedFiles.size + autoDiscovered.size >= MAX_TOTAL_FILES)
|
|
282
357
|
break;
|
|
358
|
+
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
359
|
+
break;
|
|
283
360
|
autoDiscovered.add(dep);
|
|
284
361
|
const depReadRes = await executeTool(resolved.workspaceRoot, 'read_file', { filePath: dep });
|
|
285
362
|
if (!depReadRes.error) {
|
|
286
|
-
|
|
363
|
+
const boundedText = boundFileContent(dep, depReadRes.output);
|
|
364
|
+
cachedFiles.set(dep, { text: boundedText, inlineData: depReadRes.inlineData });
|
|
365
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
287
366
|
}
|
|
288
367
|
}
|
|
289
368
|
}
|
|
@@ -328,10 +407,12 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
328
407
|
let isInvestigationFinished = false;
|
|
329
408
|
let chainedMessages = [];
|
|
330
409
|
let webSearchSummary = '';
|
|
331
|
-
|
|
410
|
+
let cumulativeFileTokens = 0;
|
|
411
|
+
// Initial prompt with history budget management
|
|
332
412
|
let currentMessage = `User Request: "${userRequest}"\n\nProject Type: ${projectType}\n\nProject Structure:\n${projectTree}`;
|
|
333
413
|
if (chatHistory) {
|
|
334
|
-
|
|
414
|
+
const prunedHistory = pruneTextToTokenBudget(chatHistory, MAX_CHAT_HISTORY_INVESTIGATION_TOKENS, { fromStart: true });
|
|
415
|
+
currentMessage = `Previous Conversation Context:\n${prunedHistory}\n\n` + currentMessage;
|
|
335
416
|
}
|
|
336
417
|
currentMessage += `\n\nStart investigating to find relevant files.`;
|
|
337
418
|
const MAX_TURNS = Infinity;
|
|
@@ -379,11 +460,11 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
379
460
|
break;
|
|
380
461
|
}
|
|
381
462
|
let isFinished = false;
|
|
382
|
-
|
|
463
|
+
// Log progress for all requested tool calls in this turn
|
|
383
464
|
for (const call of functionCalls) {
|
|
384
465
|
if (abortSignal.aborted)
|
|
385
466
|
break;
|
|
386
|
-
const args = call.args;
|
|
467
|
+
const args = (call.args || {});
|
|
387
468
|
let logMsg = ` [Context Agent] Executing ${call.name}`;
|
|
388
469
|
if (call.name === 'finish_investigation') {
|
|
389
470
|
const filesToRead = args.relevantFiles || [];
|
|
@@ -410,69 +491,84 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
410
491
|
logMsg = ` [Context Agent] Looking for recently modified files`;
|
|
411
492
|
}
|
|
412
493
|
else if (call.name === 'run_analysis_script') {
|
|
413
|
-
|
|
414
|
-
logMsg = ` [Context Agent] Running analysis script${target}`;
|
|
415
|
-
}
|
|
416
|
-
const cleanMsg = logMsg.trim().replace(/^\[Context Agent\] /, '');
|
|
417
|
-
if (onToolCall) {
|
|
418
|
-
onToolCall(cleanMsg, 'Context Agent');
|
|
494
|
+
logMsg = ` [Context Agent] Running analysis script (${args.language})`;
|
|
419
495
|
}
|
|
420
|
-
|
|
421
|
-
onProgress(
|
|
496
|
+
if (onProgress) {
|
|
497
|
+
onProgress(logMsg.trim());
|
|
422
498
|
}
|
|
423
499
|
else {
|
|
424
500
|
console.log(pc.dim(logMsg));
|
|
425
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 || {});
|
|
426
514
|
if (call.name === 'finish_investigation') {
|
|
515
|
+
isInvestigationFinished = true;
|
|
516
|
+
isFinished = true;
|
|
427
517
|
summary = args.summary || '';
|
|
428
518
|
const filesToRead = args.relevantFiles || [];
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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);
|
|
436
531
|
}
|
|
437
532
|
}
|
|
438
|
-
|
|
439
|
-
isInvestigationFinished = true;
|
|
440
|
-
// ── Auto-trace reverse dependencies ──
|
|
441
|
-
// When the Context Agent finalizes its investigation, we automatically
|
|
442
|
-
// discover files that DEPEND ON the selected files. This ensures the
|
|
443
|
-
// Execution Agent won't break imports when modifying/deleting/renaming.
|
|
444
|
-
const MAX_TOTAL_FILES = 30;
|
|
533
|
+
// Auto-trace reverse dependencies for all selected files to include active importers
|
|
445
534
|
try {
|
|
535
|
+
const { buildDependencyGraph } = await import('../utils/dependencyTracer.js');
|
|
446
536
|
const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
|
|
447
537
|
const autoDiscovered = new Set();
|
|
448
|
-
|
|
538
|
+
await Promise.all(filesToRead.map(async (filePath) => {
|
|
449
539
|
try {
|
|
450
540
|
const resolved = resolveAndValidateMultiWorkspacePath(workspaceRoot, filePath);
|
|
451
541
|
const graph = await buildDependencyGraph(resolved.workspaceRoot);
|
|
452
542
|
const reverseDeps = graph.getImportedBy(resolved.relativePath);
|
|
453
543
|
for (const dep of reverseDeps) {
|
|
454
544
|
const aliasedDep = resolved.alias ? `@${resolved.alias}/${dep}` : dep;
|
|
455
|
-
if (!filesToRead.includes(aliasedDep)
|
|
545
|
+
if (!filesToRead.includes(aliasedDep)) {
|
|
456
546
|
autoDiscovered.add(aliasedDep);
|
|
457
547
|
}
|
|
458
548
|
}
|
|
459
549
|
}
|
|
460
|
-
catch
|
|
550
|
+
catch {
|
|
461
551
|
// Ignore path resolution errors for trace dependencies
|
|
462
552
|
}
|
|
463
|
-
}
|
|
464
|
-
// Merge auto-discovered dependents, respecting
|
|
553
|
+
}));
|
|
554
|
+
// Merge auto-discovered dependents, respecting both file count and token budget caps
|
|
465
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
|
+
}));
|
|
466
563
|
let added = 0;
|
|
467
|
-
for (const dep of
|
|
468
|
-
if (
|
|
564
|
+
for (const { dep, readResult } of depReadResults) {
|
|
565
|
+
if (relevantFiles.size >= MAX_TOTAL_FILES || cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
469
566
|
break;
|
|
470
|
-
if (!
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
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++;
|
|
476
572
|
}
|
|
477
573
|
}
|
|
478
574
|
if (added > 0) {
|
|
@@ -486,46 +582,50 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
486
582
|
catch {
|
|
487
583
|
// Dependency tracing is best-effort — don't block investigation
|
|
488
584
|
}
|
|
489
|
-
|
|
585
|
+
return {
|
|
490
586
|
functionResponse: {
|
|
491
587
|
name: call.name,
|
|
492
588
|
response: { output: 'Investigation finished.' },
|
|
493
589
|
},
|
|
494
|
-
}
|
|
495
|
-
break;
|
|
590
|
+
};
|
|
496
591
|
}
|
|
497
592
|
else if (call.name === 'list_directory') {
|
|
498
593
|
const listRes = await executeTool(workspaceRoot, 'list_directory', args);
|
|
499
|
-
|
|
594
|
+
const output = boundToolOutput(listRes.output);
|
|
595
|
+
return {
|
|
500
596
|
functionResponse: {
|
|
501
597
|
name: call.name,
|
|
502
598
|
response: {
|
|
503
|
-
output
|
|
599
|
+
output,
|
|
504
600
|
...(listRes.error ? { error: listRes.error } : {}),
|
|
505
601
|
},
|
|
506
602
|
},
|
|
507
|
-
}
|
|
603
|
+
};
|
|
508
604
|
}
|
|
509
605
|
else if (call.name === 'search_codebase') {
|
|
510
606
|
const grepRes = await executeTool(workspaceRoot, 'grep_search', { ...args, workspace: 'all' });
|
|
511
|
-
|
|
607
|
+
const output = boundToolOutput(grepRes.error ? grepRes.error : grepRes.output);
|
|
608
|
+
return {
|
|
512
609
|
functionResponse: {
|
|
513
610
|
name: call.name,
|
|
514
|
-
response: { output
|
|
611
|
+
response: { output },
|
|
515
612
|
},
|
|
516
|
-
}
|
|
613
|
+
};
|
|
517
614
|
}
|
|
518
615
|
else if (call.name === 'read_file') {
|
|
519
616
|
const readRes = await executeTool(workspaceRoot, 'read_file', args);
|
|
520
617
|
if (!readRes.error) {
|
|
521
|
-
|
|
618
|
+
const boundedText = boundFileContent(args.filePath, readRes.output);
|
|
619
|
+
relevantFiles.set(args.filePath, { text: boundedText, inlineData: readRes.inlineData });
|
|
620
|
+
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
522
621
|
}
|
|
523
|
-
|
|
622
|
+
const output = boundToolOutput(readRes.error ? readRes.error : readRes.output);
|
|
623
|
+
return {
|
|
524
624
|
functionResponse: {
|
|
525
625
|
name: call.name,
|
|
526
|
-
response: { output
|
|
626
|
+
response: { output },
|
|
527
627
|
},
|
|
528
|
-
}
|
|
628
|
+
};
|
|
529
629
|
}
|
|
530
630
|
else if (call.name === 'perform_web_search') {
|
|
531
631
|
try {
|
|
@@ -540,63 +640,70 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
540
640
|
console.log(pc.dim(` [Web Search] ${webMsg}`));
|
|
541
641
|
}
|
|
542
642
|
const searchSummary = webResult.response.text()?.trim() || 'No relevant information found.';
|
|
543
|
-
|
|
544
|
-
|
|
643
|
+
const boundedSummary = boundToolOutput(searchSummary);
|
|
644
|
+
webSearchSummary += `\nQuery: ${args.query}\nFindings:\n${boundedSummary}\n`;
|
|
645
|
+
return {
|
|
545
646
|
functionResponse: {
|
|
546
647
|
name: call.name,
|
|
547
|
-
response: { output:
|
|
648
|
+
response: { output: boundedSummary },
|
|
548
649
|
},
|
|
549
|
-
}
|
|
650
|
+
};
|
|
550
651
|
}
|
|
551
652
|
catch (e) {
|
|
552
|
-
|
|
653
|
+
return {
|
|
553
654
|
functionResponse: {
|
|
554
655
|
name: call.name,
|
|
555
656
|
response: { error: e.message || 'Failed to search the web' },
|
|
556
657
|
},
|
|
557
|
-
}
|
|
658
|
+
};
|
|
558
659
|
}
|
|
559
660
|
}
|
|
560
661
|
else if (call.name === 'find_dependencies') {
|
|
561
662
|
const depResult = await executeTool(workspaceRoot, 'find_dependencies', args);
|
|
562
|
-
|
|
663
|
+
const output = boundToolOutput(depResult.error ? depResult.error : depResult.output);
|
|
664
|
+
return {
|
|
563
665
|
functionResponse: {
|
|
564
666
|
name: call.name,
|
|
565
|
-
response: { output
|
|
667
|
+
response: { output },
|
|
566
668
|
},
|
|
567
|
-
}
|
|
669
|
+
};
|
|
568
670
|
}
|
|
569
671
|
else if (call.name === 'find_recent_changes') {
|
|
570
672
|
const recentRes = await executeTool(workspaceRoot, 'find_recent_changes', args);
|
|
571
|
-
|
|
673
|
+
const output = boundToolOutput(recentRes.error ? recentRes.error : recentRes.output);
|
|
674
|
+
return {
|
|
572
675
|
functionResponse: {
|
|
573
676
|
name: call.name,
|
|
574
|
-
response: { output
|
|
677
|
+
response: { output },
|
|
575
678
|
},
|
|
576
|
-
}
|
|
679
|
+
};
|
|
577
680
|
}
|
|
578
681
|
else if (call.name === 'run_analysis_script') {
|
|
579
|
-
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, {
|
|
580
|
-
|
|
581
|
-
});
|
|
582
|
-
const output = analysisResult.exitCode === 0
|
|
682
|
+
const analysisResult = await runEphemeralScript(workspaceRoot, args.language, args.code, { abortSignal });
|
|
683
|
+
const rawOutput = analysisResult.exitCode === 0
|
|
583
684
|
? analysisResult.stdout || '(script produced no output)'
|
|
584
685
|
: `Script failed (exit ${analysisResult.exitCode}):\n${analysisResult.stderr}`;
|
|
585
|
-
|
|
686
|
+
const output = boundToolOutput(rawOutput);
|
|
687
|
+
return {
|
|
586
688
|
functionResponse: {
|
|
587
689
|
name: call.name,
|
|
588
690
|
response: { output },
|
|
589
691
|
},
|
|
590
|
-
}
|
|
692
|
+
};
|
|
591
693
|
}
|
|
592
|
-
|
|
694
|
+
return {
|
|
695
|
+
functionResponse: {
|
|
696
|
+
name: call.name,
|
|
697
|
+
response: { output: `Unknown tool: ${call.name}` },
|
|
698
|
+
},
|
|
699
|
+
};
|
|
700
|
+
}));
|
|
593
701
|
if (isFinished) {
|
|
594
702
|
break;
|
|
595
703
|
}
|
|
596
704
|
// Prepare next turn
|
|
597
705
|
currentMessage = functionResponses;
|
|
598
706
|
}
|
|
599
|
-
const collector = getMetricCollector();
|
|
600
707
|
if (collector) {
|
|
601
708
|
collector.recordContextSelectedFiles(Array.from(relevantFiles.keys()));
|
|
602
709
|
if (!isInvestigationFinished || relevantFiles.size === 0) {
|
|
@@ -604,8 +711,16 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
604
711
|
}
|
|
605
712
|
}
|
|
606
713
|
if (isInvestigationFinished && relevantFiles.size > 0) {
|
|
607
|
-
|
|
608
|
-
|
|
714
|
+
try {
|
|
715
|
+
const { saveInvestigation } = await import('./orchestration/investigationCache.js');
|
|
716
|
+
await saveInvestigation(workspaceRoot, userRequest, Array.from(relevantFiles.keys()), summary, undefined, abortSignal);
|
|
717
|
+
}
|
|
718
|
+
catch (err) {
|
|
719
|
+
if (err?.name === 'AbortError' || abortSignal?.aborted) {
|
|
720
|
+
throw err;
|
|
721
|
+
}
|
|
722
|
+
debugLog(`Failed to save investigation to cache: ${err?.message || err}`);
|
|
723
|
+
}
|
|
609
724
|
}
|
|
610
725
|
return {
|
|
611
726
|
contextResult: { projectTree, projectType, relevantFiles, summary, webSearchSummary, isParallel: false },
|
|
@@ -31,7 +31,7 @@ export interface InvestigationComplexityResult {
|
|
|
31
31
|
/**
|
|
32
32
|
* Evaluates whether the investigation phase should be parallelized.
|
|
33
33
|
*
|
|
34
|
-
* Uses `gemini-3.
|
|
34
|
+
* Uses `gemini-3.7-flash` (under auto mode) at temperature 0 to classify the prompt's
|
|
35
35
|
* investigation complexity. The PM dynamically identifies domains and groups
|
|
36
36
|
* them into agent assignments. Agent count = `agentAssignments.length`, which
|
|
37
37
|
* may be fewer than `domains.length` when related domains are batched together.
|
|
@@ -16,7 +16,7 @@ import { debugLog } from '../utils/logger.js';
|
|
|
16
16
|
/**
|
|
17
17
|
* Evaluates whether the investigation phase should be parallelized.
|
|
18
18
|
*
|
|
19
|
-
* Uses `gemini-3.
|
|
19
|
+
* Uses `gemini-3.7-flash` (under auto mode) at temperature 0 to classify the prompt's
|
|
20
20
|
* investigation complexity. The PM dynamically identifies domains and groups
|
|
21
21
|
* them into agent assignments. Agent count = `agentAssignments.length`, which
|
|
22
22
|
* may be fewer than `domains.length` when related domains are batched together.
|