minovative-mind-cli 2.5.2 → 2.6.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 +28 -25
- package/dist/commands/chat.js +1 -1
- package/dist/services/agent/inputHandler.d.ts +9 -0
- package/dist/services/agent/inputHandler.js +34 -0
- package/dist/services/agent/slashCommands.js +102 -26
- package/dist/services/agent/syntaxAgent.d.ts +40 -0
- package/dist/services/agent/syntaxAgent.js +237 -23
- package/dist/services/agent/toolLoop.js +10 -1
- package/dist/services/agent/types.d.ts +1 -0
- package/dist/services/agent-tools.d.ts +156 -1
- package/dist/services/agent-tools.js +259 -67
- package/dist/services/agent.d.ts +74 -0
- package/dist/services/agent.js +192 -30
- package/dist/services/ai.d.ts +5 -0
- package/dist/services/ai.js +80 -87
- package/dist/services/chatHistoryService.d.ts +11 -0
- package/dist/services/chatHistoryService.js +20 -1
- package/dist/services/contextAgent.d.ts +1 -1
- package/dist/services/contextAgent.js +9 -29
- package/dist/services/orchestration/investigationAgent.d.ts +2 -1
- package/dist/services/orchestration/investigationAgent.js +7 -2
- package/dist/services/orchestration/investigationOrchestrator.d.ts +1 -1
- package/dist/services/orchestration/investigationOrchestrator.js +13 -2
- package/dist/services/orchestration/orchestrator.js +21 -4
- package/dist/services/orchestration/subAgent.d.ts +2 -1
- package/dist/services/orchestration/subAgent.js +12 -6
- package/dist/utils/analysisRunner.d.ts +27 -4
- package/dist/utils/analysisRunner.js +100 -20
- package/dist/utils/config.d.ts +2 -0
- package/dist/utils/config.js +2 -0
- package/dist/utils/fuzzyMatch.d.ts +32 -0
- package/dist/utils/fuzzyMatch.js +215 -27
- package/dist/utils/localSyntaxValidator.d.ts +2 -2
- package/dist/utils/localSyntaxValidator.js +280 -81
- package/dist/utils/performanceAuditor.d.ts +2 -7
- package/dist/utils/performanceAuditor.js +541 -89
- package/dist/utils/projectStorage.js +9 -0
- package/dist/utils/systemPrompts.d.ts +3 -2
- package/dist/utils/systemPrompts.js +29 -5
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
package/dist/services/ai.js
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
1
|
+
import { GoogleGenerativeAI, SchemaType, } from '@google/generative-ai';
|
|
2
2
|
import { GEMINI_MODELS, DEFAULT_MODEL, MAX_OUTPUT_TOKENS, isByokEnabled } from '../utils/config.js';
|
|
3
3
|
import { getToolDeclarations } from './agent-tools.js';
|
|
4
4
|
import { getMetricCollector } from './metrics.js';
|
|
5
5
|
import { getAuthorizedIdToken } from './auth.js';
|
|
6
6
|
import { debugLog } from '../utils/logger.js';
|
|
7
7
|
import { readCache, writeCache } from '../utils/projectStorage.js';
|
|
8
|
-
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
8
|
+
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
9
9
|
import { workspaceRegistry } from './workspaceRegistry.js';
|
|
10
10
|
import { loadCredentials } from '../utils/credentialStore.js';
|
|
11
11
|
import { ProxyClient, accumulateTurnUsage } from './proxyClient.js';
|
|
12
12
|
function getMultiWorkspaceBlock() {
|
|
13
13
|
const summary = workspaceRegistry.buildPromptSummary();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
${summary}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
When the user asks to "transfer", "sync", or "port" features between projects, read the source files from one workspace and apply the changes to the target.
|
|
23
|
-
</multi_workspace>`;
|
|
14
|
+
const primaryRoot = process.cwd();
|
|
15
|
+
const primaryName = primaryRoot.split(/[/\\]/).pop() || 'Primary';
|
|
16
|
+
let block = `<workspace_context>\nYour current primary workspace (the default "./" root) is:\n- ./ (Workspace: ${primaryName}) → ${primaryRoot}\n`;
|
|
17
|
+
if (summary) {
|
|
18
|
+
block += `\nThe user has also registered external workspaces that you can access using the @alias/ prefix:\n${summary}\n\nTo read, modify, or search files in an external workspace, prefix the file path with the alias (e.g., "@backend/src/routes.ts"). To search across ALL workspaces, use grep_search with workspace="all".\n\nWhen the user asks to "transfer", "sync", or "port" features between projects, read the source files from one workspace and apply the changes to the target.\n`;
|
|
19
|
+
}
|
|
20
|
+
block += `</workspace_context>`;
|
|
21
|
+
return block;
|
|
24
22
|
}
|
|
25
23
|
// ─── Model Overrides (for Regression Testing) ────────────────────────
|
|
26
24
|
let contextModelOverride = null;
|
|
@@ -57,7 +55,7 @@ export function getGlobalActiveModel() {
|
|
|
57
55
|
const proxyClient = new ProxyClient();
|
|
58
56
|
// ─── History Limits ──────────────────────────────────────────────────
|
|
59
57
|
/** Maximum number of Content entries to keep in the sliding history window. */
|
|
60
|
-
const MAX_HISTORY_ENTRIES =
|
|
58
|
+
const MAX_HISTORY_ENTRIES = 500;
|
|
61
59
|
/**
|
|
62
60
|
* Maximum character length for a single Part's text content.
|
|
63
61
|
* Anything beyond this is truncated with an ellipsis marker so the proxy
|
|
@@ -157,7 +155,7 @@ export class ProxyChatSession {
|
|
|
157
155
|
const pruned = this.history.slice(0, trimCount);
|
|
158
156
|
this.history = this.history.slice(trimCount);
|
|
159
157
|
if (this.sessionId && this.workspaceRoot) {
|
|
160
|
-
const archiveFile = `archived_history_${this.sessionId}.json`;
|
|
158
|
+
const archiveFile = `archives/archived_history_${this.sessionId}.json`;
|
|
161
159
|
const existingArchive = readCache(this.workspaceRoot, archiveFile) || [];
|
|
162
160
|
await writeCache(this.workspaceRoot, archiveFile, [...existingArchive, ...pruned]);
|
|
163
161
|
}
|
|
@@ -359,9 +357,7 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
359
357
|
const idToken = await getAuthorizedIdToken();
|
|
360
358
|
if (!idToken)
|
|
361
359
|
return text;
|
|
362
|
-
let model =
|
|
363
|
-
if (model === 'auto')
|
|
364
|
-
model = GEMINI_MODELS.FLASH_LITE;
|
|
360
|
+
let model = GEMINI_MODELS.FLASH_LITE;
|
|
365
361
|
const parts = [{ text }];
|
|
366
362
|
if (inlineData) {
|
|
367
363
|
parts.push({ inlineData });
|
|
@@ -414,7 +410,10 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
414
410
|
}
|
|
415
411
|
catch (error) {
|
|
416
412
|
debugLog(`Failed to compress text using flash-lite: ${error}`);
|
|
417
|
-
if (error?.status === 401 ||
|
|
413
|
+
if (error?.status === 401 ||
|
|
414
|
+
error?.status === 403 ||
|
|
415
|
+
error?.message?.includes('API_KEY_INVALID') ||
|
|
416
|
+
error?.message?.includes('quota')) {
|
|
418
417
|
throw new Error('AI_AUTH_ERROR: Your API key or quota is invalid. Please run /config-key or re-login.');
|
|
419
418
|
}
|
|
420
419
|
return text; // fallback to raw text if compression fails
|
|
@@ -430,30 +429,14 @@ export function getContextToolDeclarations() {
|
|
|
430
429
|
return [
|
|
431
430
|
{
|
|
432
431
|
functionDeclarations: [
|
|
433
|
-
{
|
|
434
|
-
name: 'select_files',
|
|
435
|
-
description: 'Select files to investigate further. WARNING: This reads the entire file into your context window, consuming tokens. Only use this if you MUST read the contents to find other dependencies or understand architecture. If you already know the file is relevant to the task, do NOT use this tool—simply add it to finish_investigation.',
|
|
436
|
-
parameters: {
|
|
437
|
-
type: 'OBJECT',
|
|
438
|
-
properties: {
|
|
439
|
-
files: {
|
|
440
|
-
type: 'ARRAY',
|
|
441
|
-
items: { type: 'STRING' },
|
|
442
|
-
description: 'Array of file paths to read',
|
|
443
|
-
},
|
|
444
|
-
reasoning: { type: 'STRING', description: 'Why you selected these files' },
|
|
445
|
-
},
|
|
446
|
-
required: ['files', 'reasoning'],
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
432
|
{
|
|
450
433
|
name: 'search_codebase',
|
|
451
434
|
description: 'Search the codebase for a pattern.',
|
|
452
435
|
parameters: {
|
|
453
|
-
type:
|
|
436
|
+
type: SchemaType.OBJECT,
|
|
454
437
|
properties: {
|
|
455
|
-
pattern: { type:
|
|
456
|
-
fileGlob: { type:
|
|
438
|
+
pattern: { type: SchemaType.STRING, description: 'Pattern to search for' },
|
|
439
|
+
fileGlob: { type: SchemaType.STRING, description: 'Optional glob to filter files' },
|
|
457
440
|
},
|
|
458
441
|
required: ['pattern'],
|
|
459
442
|
},
|
|
@@ -462,14 +445,14 @@ export function getContextToolDeclarations() {
|
|
|
462
445
|
name: 'read_file',
|
|
463
446
|
description: 'Read a single file. Supports text files and native parsing of .pdf files (including math and diagrams). Use startLine and endLine to read specific chunks of massive text files. WARNING: Reading files consumes tokens. Only read if you MUST understand the file contents or find dependencies. If you already know it is relevant, simply add it to finish_investigation without reading it.',
|
|
464
447
|
parameters: {
|
|
465
|
-
type:
|
|
448
|
+
type: SchemaType.OBJECT,
|
|
466
449
|
properties: {
|
|
467
|
-
filePath: { type:
|
|
468
|
-
startLine: { type:
|
|
469
|
-
endLine: { type:
|
|
450
|
+
filePath: { type: SchemaType.STRING, description: 'Path to the file' },
|
|
451
|
+
startLine: { type: SchemaType.NUMBER, description: 'Optional. 1-indexed starting line number.' },
|
|
452
|
+
endLine: { type: SchemaType.NUMBER, description: 'Optional. 1-indexed ending line number (inclusive).' },
|
|
470
453
|
targetElements: {
|
|
471
|
-
type:
|
|
472
|
-
items: { type:
|
|
454
|
+
type: SchemaType.ARRAY,
|
|
455
|
+
items: { type: SchemaType.STRING },
|
|
473
456
|
description: 'Optional. An array of specific function names, class names, or variables to extract. The tool will intelligently locate and return only the blocks defining these elements.',
|
|
474
457
|
},
|
|
475
458
|
},
|
|
@@ -480,10 +463,10 @@ export function getContextToolDeclarations() {
|
|
|
480
463
|
name: 'list_directory',
|
|
481
464
|
description: 'List the contents of a directory. Returns files, folders, and their sizes.',
|
|
482
465
|
parameters: {
|
|
483
|
-
type:
|
|
466
|
+
type: SchemaType.OBJECT,
|
|
484
467
|
properties: {
|
|
485
|
-
dirPath: { type:
|
|
486
|
-
maxDepth: { type:
|
|
468
|
+
dirPath: { type: SchemaType.STRING, description: 'Path to the directory to list (e.g., "src/components")' },
|
|
469
|
+
maxDepth: { type: SchemaType.NUMBER, description: 'Maximum depth to traverse (default 1)' },
|
|
487
470
|
},
|
|
488
471
|
required: ['dirPath'],
|
|
489
472
|
},
|
|
@@ -492,15 +475,15 @@ export function getContextToolDeclarations() {
|
|
|
492
475
|
name: 'find_dependencies',
|
|
493
476
|
description: 'Trace the dependency graph for a file. Returns what the file imports (forward) and what files import it (reverse). Critical for understanding the blast radius before modifying, deleting, or renaming a file.',
|
|
494
477
|
parameters: {
|
|
495
|
-
type:
|
|
478
|
+
type: SchemaType.OBJECT,
|
|
496
479
|
properties: {
|
|
497
|
-
filePath: { type:
|
|
480
|
+
filePath: { type: SchemaType.STRING, description: 'Relative path to the file to trace dependencies for.' },
|
|
498
481
|
direction: {
|
|
499
|
-
type:
|
|
482
|
+
type: SchemaType.STRING,
|
|
500
483
|
description: 'Direction to trace: "both" (default), "forward", or "reverse".',
|
|
501
484
|
},
|
|
502
485
|
maxDepth: {
|
|
503
|
-
type:
|
|
486
|
+
type: SchemaType.NUMBER,
|
|
504
487
|
description: 'Maximum traversal depth (default 3, max 5).',
|
|
505
488
|
},
|
|
506
489
|
},
|
|
@@ -511,18 +494,18 @@ export function getContextToolDeclarations() {
|
|
|
511
494
|
name: 'find_recent_changes',
|
|
512
495
|
description: 'Find files that have been modified recently within the workspace. Useful for understanding what the user was just working on if they ask vague questions like "why is it failing?". Automatically ignores .git, node_modules, etc.',
|
|
513
496
|
parameters: {
|
|
514
|
-
type:
|
|
497
|
+
type: SchemaType.OBJECT,
|
|
515
498
|
properties: {
|
|
516
499
|
dirPath: {
|
|
517
|
-
type:
|
|
500
|
+
type: SchemaType.STRING,
|
|
518
501
|
description: 'Relative path to directory to search from. Defaults to workspace root ".".',
|
|
519
502
|
},
|
|
520
503
|
minutes: {
|
|
521
|
-
type:
|
|
504
|
+
type: SchemaType.NUMBER,
|
|
522
505
|
description: 'Look for files modified within this many minutes. Defaults to 60.',
|
|
523
506
|
},
|
|
524
507
|
maxDepth: {
|
|
525
|
-
type:
|
|
508
|
+
type: SchemaType.NUMBER,
|
|
526
509
|
description: 'Maximum depth to traverse. Defaults to 5.',
|
|
527
510
|
},
|
|
528
511
|
},
|
|
@@ -530,35 +513,20 @@ export function getContextToolDeclarations() {
|
|
|
530
513
|
},
|
|
531
514
|
{
|
|
532
515
|
name: 'run_analysis_script',
|
|
533
|
-
description:
|
|
534
|
-
'Use this to get exact line ranges for functions, classes, and variables by leveraging the ' +
|
|
535
|
-
"language's native AST parser (e.g., TypeScript compiler API, Python ast module, go/parser). " +
|
|
536
|
-
'You can also use this to probe the development environment — detecting available runtimes ' +
|
|
537
|
-
'(e.g., node --version, python3 --version), checking if ports are in use, identifying project ' +
|
|
538
|
-
'type (monorepo, package manager), or diagnosing system-level issues (disk space, memory) that ' +
|
|
539
|
-
'may affect execution. ' +
|
|
540
|
-
'For complex investigation tasks, you can write lightweight ML scripts (e.g., TF-IDF cosine similarity ' +
|
|
541
|
-
'to rank file relevance, Z-score outlier detection for anomalous log lines, K-Means clustering, or Naive ' +
|
|
542
|
-
'Bayes classification). Default to "node" for generic math/analysis as a safe baseline, but act like ' +
|
|
543
|
-
'a native inhabitant of the host environment — if Python, Go, Rust, or specialized libraries are available ' +
|
|
544
|
-
'in the project context, leverage the host\'s native runtimes and standard libraries for maximum efficiency. ' +
|
|
545
|
-
'The script is executed from a temporary directory and automatically cleaned up after execution. ' +
|
|
546
|
-
'Output should be structured JSON to stdout. ' +
|
|
547
|
-
'Use the results to make precise read_file calls with exact startLine/endLine instead of guessing. ' +
|
|
548
|
-
'CRITICAL: Do not use this tool on binary, document, or non-code files (e.g. PDF, image, audio, docx).',
|
|
516
|
+
description: "Write and execute a disposable analysis script to structurally map code in the workspace. Use this to get exact line ranges for functions, classes, and variables by leveraging the language's native AST parser (e.g., TypeScript compiler API, Python ast module, go/parser). You can also use this to probe the development environment — detecting available runtimes (e.g., node --version, python3 --version), checking if ports are in use, identifying project type (monorepo, package manager), or diagnosing system-level issues (disk space, memory) that may affect execution. For complex investigation tasks, you can write lightweight ML scripts (e.g., TF-IDF cosine similarity to rank file relevance, Z-score outlier detection for anomalous log lines, K-Means clustering, or Naive Bayes classification). Default to \"node\" for generic math/analysis as a safe baseline, but act like a native inhabitant of the host environment — if Python, Go, Rust, or specialized libraries are available in the project context, leverage the host's native runtimes and standard libraries for maximum efficiency. The script is executed from a temporary directory and automatically cleaned up after execution. Output should be structured JSON to stdout. Use the results to make precise read_file calls with exact startLine/endLine instead of guessing. CRITICAL: Do not use this tool on binary, document, or non-code files (e.g. PDF, image, audio, docx).",
|
|
549
517
|
parameters: {
|
|
550
|
-
type:
|
|
518
|
+
type: SchemaType.OBJECT,
|
|
551
519
|
properties: {
|
|
552
520
|
language: {
|
|
553
|
-
type:
|
|
521
|
+
type: SchemaType.STRING,
|
|
554
522
|
description: 'The runtime to use: "node", "ts-node", "python", "bash", "go", or "rust".',
|
|
555
523
|
},
|
|
556
524
|
code: {
|
|
557
|
-
type:
|
|
525
|
+
type: SchemaType.STRING,
|
|
558
526
|
description: 'The analysis script code. Should output structured JSON to stdout with structural information (name, type, startLine, endLine for each code element).',
|
|
559
527
|
},
|
|
560
528
|
targetFile: {
|
|
561
|
-
type:
|
|
529
|
+
type: SchemaType.STRING,
|
|
562
530
|
description: 'The workspace file being analyzed. Used for logging and context only.',
|
|
563
531
|
},
|
|
564
532
|
},
|
|
@@ -569,12 +537,12 @@ export function getContextToolDeclarations() {
|
|
|
569
537
|
name: 'finish_investigation',
|
|
570
538
|
description: 'Call this when you have gathered enough context.',
|
|
571
539
|
parameters: {
|
|
572
|
-
type:
|
|
540
|
+
type: SchemaType.OBJECT,
|
|
573
541
|
properties: {
|
|
574
|
-
summary: { type:
|
|
542
|
+
summary: { type: SchemaType.STRING, description: 'Summary of your findings' },
|
|
575
543
|
relevantFiles: {
|
|
576
|
-
type:
|
|
577
|
-
items: { type:
|
|
544
|
+
type: SchemaType.ARRAY,
|
|
545
|
+
items: { type: SchemaType.STRING },
|
|
578
546
|
description: 'Paths of files that are relevant to the user request',
|
|
579
547
|
},
|
|
580
548
|
},
|
|
@@ -585,9 +553,9 @@ export function getContextToolDeclarations() {
|
|
|
585
553
|
name: 'perform_web_search',
|
|
586
554
|
description: 'Search the web for documentation, solutions, or real-time information.',
|
|
587
555
|
parameters: {
|
|
588
|
-
type:
|
|
556
|
+
type: SchemaType.OBJECT,
|
|
589
557
|
properties: {
|
|
590
|
-
query: { type:
|
|
558
|
+
query: { type: SchemaType.STRING, description: 'The search query to look up on the web' },
|
|
591
559
|
},
|
|
592
560
|
required: ['query'],
|
|
593
561
|
},
|
|
@@ -599,7 +567,7 @@ export function getContextToolDeclarations() {
|
|
|
599
567
|
export function createContextAgentSession() {
|
|
600
568
|
const contextTools = getContextToolDeclarations();
|
|
601
569
|
let model = contextModelOverride || getGlobalActiveModel();
|
|
602
|
-
if (model === 'auto')
|
|
570
|
+
if (model === 'auto' || model.includes('claude'))
|
|
603
571
|
model = GEMINI_MODELS.FLASH;
|
|
604
572
|
return new ProxyChatSession(model, CONTEXT_SYSTEM_INSTRUCTION.replace('{{MULTI_WORKSPACE_BLOCK}}', getMultiWorkspaceBlock()), contextTools, {
|
|
605
573
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
@@ -611,7 +579,7 @@ export function createContextAgentSession() {
|
|
|
611
579
|
// ─── Intent Router Service ───────────────────────────────────────────
|
|
612
580
|
export function createIntentRouterSession() {
|
|
613
581
|
let model = getGlobalActiveModel();
|
|
614
|
-
if (model === 'auto')
|
|
582
|
+
if (model === 'auto' || model.includes('claude'))
|
|
615
583
|
model = GEMINI_MODELS.FLASH_LITE;
|
|
616
584
|
return new ProxyChatSession(model, INTENT_ROUTER_SYSTEM_INSTRUCTION, [], // no tools
|
|
617
585
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
@@ -619,7 +587,7 @@ export function createIntentRouterSession() {
|
|
|
619
587
|
// ─── Execution Complexity Router Service ──────────────────────────────
|
|
620
588
|
export function createExecutionComplexitySession() {
|
|
621
589
|
let model = getGlobalActiveModel();
|
|
622
|
-
if (model === 'auto')
|
|
590
|
+
if (model === 'auto' || model.includes('claude'))
|
|
623
591
|
model = GEMINI_MODELS.FLASH_LITE;
|
|
624
592
|
return new ProxyChatSession(model, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
625
593
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
@@ -627,7 +595,7 @@ export function createExecutionComplexitySession() {
|
|
|
627
595
|
// ─── Investigation Complexity Router Service ─────────────────────────
|
|
628
596
|
export function createInvestigationComplexitySession() {
|
|
629
597
|
let model = getGlobalActiveModel();
|
|
630
|
-
if (model === 'auto')
|
|
598
|
+
if (model === 'auto' || model.includes('claude'))
|
|
631
599
|
model = GEMINI_MODELS.FLASH_LITE;
|
|
632
600
|
return new ProxyChatSession(model, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
633
601
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
@@ -635,7 +603,7 @@ export function createInvestigationComplexitySession() {
|
|
|
635
603
|
// ─── Web Search Agent Service ───────────────────────────────────────────
|
|
636
604
|
export function createWebSearchAgentSession() {
|
|
637
605
|
let model = getGlobalActiveModel();
|
|
638
|
-
if (model === 'auto')
|
|
606
|
+
if (model === 'auto' || model.includes('claude'))
|
|
639
607
|
model = GEMINI_MODELS.FLASH;
|
|
640
608
|
return new ProxyChatSession(model, WEB_SEARCH_SYSTEM_INSTRUCTION, [{ googleSearch: {} }], {
|
|
641
609
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
@@ -644,6 +612,31 @@ export function createWebSearchAgentSession() {
|
|
|
644
612
|
topK: 40,
|
|
645
613
|
});
|
|
646
614
|
}
|
|
615
|
+
// ─── History Summarizer Agent Service ─────────────────────────────────
|
|
616
|
+
export function createHistorySummarizerSession() {
|
|
617
|
+
let model = getGlobalActiveModel();
|
|
618
|
+
if (model === 'auto' || model.includes('claude'))
|
|
619
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
620
|
+
return new ProxyChatSession(model, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, [], { temperature: 0.2, maxOutputTokens: MAX_OUTPUT_TOKENS });
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Summarizes an array of Content history entries using Gemini Flash Lite.
|
|
624
|
+
*/
|
|
625
|
+
export async function summarizeChatHistory(history) {
|
|
626
|
+
if (!history || history.length === 0) {
|
|
627
|
+
return '';
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
const session = createHistorySummarizerSession();
|
|
631
|
+
session.loadRawHistory(JSON.parse(JSON.stringify(history)));
|
|
632
|
+
const result = await session.sendMessage('Summarize the preceding conversation history following your compression rules.');
|
|
633
|
+
return result.response.text() || '';
|
|
634
|
+
}
|
|
635
|
+
catch (error) {
|
|
636
|
+
debugLog(`Failed to summarize chat history: ${error?.message || error}`);
|
|
637
|
+
return '';
|
|
638
|
+
}
|
|
639
|
+
}
|
|
647
640
|
/**
|
|
648
641
|
* Generates a concise title for a chat session based on the user's first message.
|
|
649
642
|
*/
|
|
@@ -658,7 +651,7 @@ export async function generateChatTitle(firstMessage) {
|
|
|
658
651
|
const instruction = "<identity>\nYou are a helpful assistant that generates extremely concise chat titles based on a user's first message.\n</identity>\n\n<directives>\nOutput ONLY the title, no quotes, no markdown, no punctuation.\n</directives>";
|
|
659
652
|
const contents = [{ role: 'user', parts: [{ text: firstMessage.substring(0, 500) }] }];
|
|
660
653
|
let model = getGlobalActiveModel();
|
|
661
|
-
if (model === 'auto')
|
|
654
|
+
if (model === 'auto' || model.includes('claude'))
|
|
662
655
|
model = GEMINI_MODELS.FLASH_LITE;
|
|
663
656
|
const byokEnabled = await isByokEnabled();
|
|
664
657
|
let result;
|
|
@@ -702,7 +695,7 @@ export async function generateChatTitle(firstMessage) {
|
|
|
702
695
|
}
|
|
703
696
|
}
|
|
704
697
|
title = result.thought || title;
|
|
705
|
-
title = title.replace(/
|
|
698
|
+
title = title.replace(/"/g, '').replace(/'/g, '').trim();
|
|
706
699
|
return title ? title : firstMessage.substring(0, maxLength);
|
|
707
700
|
}
|
|
708
701
|
catch (error) {
|
|
@@ -25,6 +25,8 @@ export interface ChatSessionData {
|
|
|
25
25
|
totalInputTokens?: number;
|
|
26
26
|
/** Optional total cumulative output tokens consumed during the session. */
|
|
27
27
|
totalOutputTokens?: number;
|
|
28
|
+
/** Optional total cumulative credits used during the session. */
|
|
29
|
+
totalCreditsUsed?: number;
|
|
28
30
|
/** Optional name of the Git branch active when this session was last updated. */
|
|
29
31
|
gitBranch?: string;
|
|
30
32
|
/** Optional flag indicating whether auto-approval of commands was enabled in this session. */
|
|
@@ -72,6 +74,15 @@ declare class ChatHistoryService {
|
|
|
72
74
|
* @returns A promise that resolves when the session has been successfully written to the cache.
|
|
73
75
|
*/
|
|
74
76
|
saveSession(session: ChatSessionData): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Updates the user-friendly title of an existing chat session in the local workspace cache.
|
|
79
|
+
* If the session with the matching ID exists, its title is updated and persisted to `chat_sessions.json`.
|
|
80
|
+
*
|
|
81
|
+
* @param id - The unique identifier of the chat session to update.
|
|
82
|
+
* @param title - The new title string to set for the chat session.
|
|
83
|
+
* @returns A promise that resolves to `true` if the session title was updated and saved, or `false` if the session was not found or the service is uninitialized.
|
|
84
|
+
*/
|
|
85
|
+
updateSessionTitle(id: string, title: string): Promise<boolean>;
|
|
75
86
|
/**
|
|
76
87
|
* Deletes a chat session from the local workspace cache and removes its associated archived history file.
|
|
77
88
|
* If the session or its archived history file does not exist, the operation completes without throwing an error.
|
|
@@ -56,6 +56,25 @@ class ChatHistoryService {
|
|
|
56
56
|
}
|
|
57
57
|
await writeCache(this.workspaceRoot, 'chat_sessions.json', sessions);
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Updates the user-friendly title of an existing chat session in the local workspace cache.
|
|
61
|
+
* If the session with the matching ID exists, its title is updated and persisted to `chat_sessions.json`.
|
|
62
|
+
*
|
|
63
|
+
* @param id - The unique identifier of the chat session to update.
|
|
64
|
+
* @param title - The new title string to set for the chat session.
|
|
65
|
+
* @returns A promise that resolves to `true` if the session title was updated and saved, or `false` if the session was not found or the service is uninitialized.
|
|
66
|
+
*/
|
|
67
|
+
async updateSessionTitle(id, title) {
|
|
68
|
+
if (!this.workspaceRoot || !id)
|
|
69
|
+
return false;
|
|
70
|
+
const sessions = this.getSessions();
|
|
71
|
+
const session = sessions.find((s) => s.id === id);
|
|
72
|
+
if (!session)
|
|
73
|
+
return false;
|
|
74
|
+
session.title = title;
|
|
75
|
+
await writeCache(this.workspaceRoot, 'chat_sessions.json', sessions);
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
59
78
|
/**
|
|
60
79
|
* Deletes a chat session from the local workspace cache and removes its associated archived history file.
|
|
61
80
|
* If the session or its archived history file does not exist, the operation completes without throwing an error.
|
|
@@ -87,7 +106,7 @@ class ChatHistoryService {
|
|
|
87
106
|
const storageDir = getProjectStorageDir(this.workspaceRoot);
|
|
88
107
|
await Promise.all(ids.map(async (id) => {
|
|
89
108
|
try {
|
|
90
|
-
const archivePath = path.join(storageDir, `archived_history_${id}.json`);
|
|
109
|
+
const archivePath = path.join(storageDir, 'archives', `archived_history_${id}.json`);
|
|
91
110
|
await fs.unlink(archivePath);
|
|
92
111
|
}
|
|
93
112
|
catch {
|
|
@@ -19,7 +19,7 @@ export declare function evaluateExecutionComplexity(userRequest: string, investi
|
|
|
19
19
|
export declare function gatherContext(workspaceRoot: string, userRequest: string, chatHistory: string | undefined, inputHandler: {
|
|
20
20
|
getAndClear: () => string;
|
|
21
21
|
waitForPrompt: () => Promise<void>;
|
|
22
|
-
}, abortSignal: AbortSignal, onProgress?: (msg: string) => void): Promise<{
|
|
22
|
+
}, abortSignal: AbortSignal, onProgress?: (msg: string) => void, onToolCall?: (msg: string, label: string) => void): Promise<{
|
|
23
23
|
contextResult: ContextAgentResult | null;
|
|
24
24
|
targetAgent: 'CHAT' | 'EXECUTE';
|
|
25
25
|
chainedMessages: string[];
|
|
@@ -181,7 +181,7 @@ Number of Relevant Files: ${numRelevantFiles}`;
|
|
|
181
181
|
return 'HARD';
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
|
-
export async function gatherContext(workspaceRoot, userRequest, chatHistory = '', inputHandler, abortSignal, onProgress) {
|
|
184
|
+
export async function gatherContext(workspaceRoot, userRequest, chatHistory = '', inputHandler, abortSignal, onProgress, onToolCall) {
|
|
185
185
|
// Always skip slash commands for zero latency
|
|
186
186
|
if (userRequest.startsWith('/')) {
|
|
187
187
|
return { contextResult: null, targetAgent: 'EXECUTE', chainedMessages: [] };
|
|
@@ -268,7 +268,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
268
268
|
const complexity = await evaluateInvestigationComplexity(userRequest, projectType, approxFiles, chatHistory);
|
|
269
269
|
if (complexity.strategy === 'PARALLEL' && complexity.agentAssignments.length > 0) {
|
|
270
270
|
const orchestrator = new InvestigationOrchestrator();
|
|
271
|
-
const parallelResult = await orchestrator.runParallelInvestigation(userRequest, complexity.agentAssignments, workspaceRoot, projectTree, projectType, chatHistory, abortSignal, onProgress);
|
|
271
|
+
const parallelResult = await orchestrator.runParallelInvestigation(userRequest, complexity.agentAssignments, workspaceRoot, projectTree, projectType, chatHistory, abortSignal, onProgress, onToolCall);
|
|
272
272
|
if (parallelResult !== null) {
|
|
273
273
|
// If parallel investigation succeeded, we are done. Return immediately.
|
|
274
274
|
parallelResult.isParallel = true;
|
|
@@ -338,10 +338,6 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
338
338
|
const filesToRead = args.relevantFiles || [];
|
|
339
339
|
logMsg = ` [Context Agent] Finished investigation (Selected ${filesToRead.length} files)`;
|
|
340
340
|
}
|
|
341
|
-
else if (call.name === 'select_files') {
|
|
342
|
-
const filesToRead = args.files || [];
|
|
343
|
-
logMsg = ` [Context Agent] Selected ${filesToRead.length} files to read`;
|
|
344
|
-
}
|
|
345
341
|
else if (call.name === 'search_codebase') {
|
|
346
342
|
logMsg = ` [Context Agent] Searching codebase for: "${args.pattern}"`;
|
|
347
343
|
}
|
|
@@ -366,8 +362,12 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
366
362
|
const target = args.targetFile ? ` for: ${args.targetFile}` : '';
|
|
367
363
|
logMsg = ` [Context Agent] Running analysis script${target}`;
|
|
368
364
|
}
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
const cleanMsg = logMsg.trim().replace(/^\[Context Agent\] /, '');
|
|
366
|
+
if (onToolCall) {
|
|
367
|
+
onToolCall(cleanMsg, 'Context Agent');
|
|
368
|
+
}
|
|
369
|
+
else if (onProgress) {
|
|
370
|
+
onProgress(cleanMsg);
|
|
371
371
|
}
|
|
372
372
|
else {
|
|
373
373
|
console.log(pc.dim(logMsg));
|
|
@@ -443,26 +443,6 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
443
443
|
});
|
|
444
444
|
break;
|
|
445
445
|
}
|
|
446
|
-
else if (call.name === 'select_files') {
|
|
447
|
-
const filesToRead = args.files || [];
|
|
448
|
-
let output = '';
|
|
449
|
-
for (const filePath of filesToRead) {
|
|
450
|
-
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
451
|
-
if (!readResult.error) {
|
|
452
|
-
relevantFiles.set(filePath, { text: readResult.output, inlineData: readResult.inlineData });
|
|
453
|
-
output += `\n--- File: ${filePath} ---\n${readResult.output}\n`;
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
output += `\n--- File: ${filePath} ---\nError: ${readResult.error}\n`;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
functionResponses.push({
|
|
460
|
-
functionResponse: {
|
|
461
|
-
name: call.name,
|
|
462
|
-
response: { output: output || 'No files read.' },
|
|
463
|
-
},
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
446
|
else if (call.name === 'list_directory') {
|
|
467
447
|
const listRes = await executeTool(workspaceRoot, 'list_directory', args);
|
|
468
448
|
functionResponses.push({
|
|
@@ -499,7 +479,7 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
499
479
|
else if (call.name === 'perform_web_search') {
|
|
500
480
|
try {
|
|
501
481
|
const webSession = createWebSearchAgentSession();
|
|
502
|
-
const webResult = await webSession.sendMessage(`Please search the web for the following query and summarize your findings:\n"${args.query}"
|
|
482
|
+
const webResult = await webSession.sendMessage(`Please search the web for the following query and summarize your findings:\n"${args.query}"`, undefined, abortSignal);
|
|
503
483
|
const grounding = webResult.response.groundingMetadata?.();
|
|
504
484
|
if (grounding?.webSearchQueries && grounding.webSearchQueries.length > 0) {
|
|
505
485
|
const webMsg = `Web Search: ${grounding.webSearchQueries.map((q) => `"${q}"`).join(', ')}`;
|
|
@@ -79,9 +79,10 @@ export declare class InvestigationAgentRunner {
|
|
|
79
79
|
* @param chatHistory - Recent conversation history for context.
|
|
80
80
|
* @param abortSignal - Signal to cancel execution.
|
|
81
81
|
* @param onProgress - Callback for spinner/terminal updates.
|
|
82
|
+
* @param onTool - Callback for tool invocation logging.
|
|
82
83
|
* @returns The investigation result with discovered files and summary.
|
|
83
84
|
*/
|
|
84
|
-
execute(userRequest: string, chatHistory: string, abortSignal: AbortSignal, onProgress?: (msg: string) => void): Promise<InvestigationResult>;
|
|
85
|
+
execute(userRequest: string, chatHistory: string, abortSignal: AbortSignal, onProgress?: (msg: string) => void, onTool?: (msg: string) => void): Promise<InvestigationResult>;
|
|
85
86
|
/**
|
|
86
87
|
* Accumulates token usage from the chat session.
|
|
87
88
|
*/
|
|
@@ -17,6 +17,7 @@ import { CONTEXT_SYSTEM_INSTRUCTION } from '../../utils/systemPrompts.js';
|
|
|
17
17
|
import { debugLog } from '../../utils/logger.js';
|
|
18
18
|
import { listDirectory, grepSearch, readFile, traceDependencies, findRecentChanges } from '../agent-tools.js';
|
|
19
19
|
import { runEphemeralScript } from '../../utils/analysisRunner.js';
|
|
20
|
+
import { formatToolCall } from '../agent/toolLoop.js';
|
|
20
21
|
// ─── Investigation Agent Runner ──────────────────────────────────────
|
|
21
22
|
/**
|
|
22
23
|
* Executes a read-only investigation within a scoped set of domains.
|
|
@@ -50,7 +51,7 @@ export class InvestigationAgentRunner {
|
|
|
50
51
|
this.projectTree = projectTree;
|
|
51
52
|
this.projectType = projectType;
|
|
52
53
|
let model = getGlobalActiveModel();
|
|
53
|
-
if (model === GEMINI_MODELS.AUTO)
|
|
54
|
+
if (model === GEMINI_MODELS.AUTO || model.includes('claude'))
|
|
54
55
|
model = GEMINI_MODELS.FLASH;
|
|
55
56
|
this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), getContextToolDeclarations(), {
|
|
56
57
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
@@ -88,9 +89,10 @@ export class InvestigationAgentRunner {
|
|
|
88
89
|
* @param chatHistory - Recent conversation history for context.
|
|
89
90
|
* @param abortSignal - Signal to cancel execution.
|
|
90
91
|
* @param onProgress - Callback for spinner/terminal updates.
|
|
92
|
+
* @param onTool - Callback for tool invocation logging.
|
|
91
93
|
* @returns The investigation result with discovered files and summary.
|
|
92
94
|
*/
|
|
93
|
-
async execute(userRequest, chatHistory, abortSignal, onProgress) {
|
|
95
|
+
async execute(userRequest, chatHistory, abortSignal, onProgress, onTool) {
|
|
94
96
|
debugLog(`InvestigationAgent [${this.agentLabel}]: Starting execution for domains: ${this.domains.join(', ')}`);
|
|
95
97
|
this.lastHeartbeat = Date.now();
|
|
96
98
|
const relevantFiles = new Map();
|
|
@@ -140,6 +142,9 @@ export class InvestigationAgentRunner {
|
|
|
140
142
|
this.pingHeartbeat();
|
|
141
143
|
const args = call.args;
|
|
142
144
|
const logPrefix = `[${this.agentLabel}]`;
|
|
145
|
+
if (onTool) {
|
|
146
|
+
onTool(formatToolCall(call.name, args));
|
|
147
|
+
}
|
|
143
148
|
if (call.name === 'finish_investigation') {
|
|
144
149
|
summary = args.summary || '';
|
|
145
150
|
const filesToRead = args.relevantFiles || [];
|
|
@@ -35,7 +35,7 @@ export declare class InvestigationOrchestrator {
|
|
|
35
35
|
* @param onProgress - Callback for spinner/terminal updates.
|
|
36
36
|
* @returns Merged context result, or null if all agents failed.
|
|
37
37
|
*/
|
|
38
|
-
runParallelInvestigation(userRequest: string, agentAssignments: AgentAssignment[], workspaceRoot: string, projectTree: string, projectType: string, chatHistory: string, abortSignal: AbortSignal, onProgress?: (msg: string) => void): Promise<ContextAgentResult | null>;
|
|
38
|
+
runParallelInvestigation(userRequest: string, agentAssignments: AgentAssignment[], workspaceRoot: string, projectTree: string, projectType: string, chatHistory: string, abortSignal: AbortSignal, onProgress?: (msg: string) => void, onToolCallCallback?: (msg: string, label: string) => void): Promise<ContextAgentResult | null>;
|
|
39
39
|
/**
|
|
40
40
|
* Merges multiple `InvestigationResult` objects into a single `ContextAgentResult`.
|
|
41
41
|
*
|
|
@@ -44,7 +44,7 @@ export class InvestigationOrchestrator {
|
|
|
44
44
|
* @param onProgress - Callback for spinner/terminal updates.
|
|
45
45
|
* @returns Merged context result, or null if all agents failed.
|
|
46
46
|
*/
|
|
47
|
-
async runParallelInvestigation(userRequest, agentAssignments, workspaceRoot, projectTree, projectType, chatHistory, abortSignal, onProgress) {
|
|
47
|
+
async runParallelInvestigation(userRequest, agentAssignments, workspaceRoot, projectTree, projectType, chatHistory, abortSignal, onProgress, onToolCallCallback) {
|
|
48
48
|
const agentCount = agentAssignments.length;
|
|
49
49
|
p.log.info(pc.cyan(`🔍 Parallel Investigation (${agentCount} agents)`) +
|
|
50
50
|
'\n' +
|
|
@@ -62,13 +62,24 @@ export class InvestigationOrchestrator {
|
|
|
62
62
|
const results = [];
|
|
63
63
|
for (let i = 0; i < agents.length; i += MAX_CONCURRENT) {
|
|
64
64
|
const chunk = agents.slice(i, i + MAX_CONCURRENT);
|
|
65
|
+
const chunkAssignments = agentAssignments.slice(i, i + MAX_CONCURRENT);
|
|
65
66
|
const chunkPromises = chunk.map((agent, chunkIndex) => {
|
|
66
67
|
const globalIndex = i + chunkIndex;
|
|
68
|
+
const assignment = chunkAssignments[chunkIndex];
|
|
69
|
+
const onToolCall = (msg) => {
|
|
70
|
+
if (onToolCallCallback) {
|
|
71
|
+
onToolCallCallback(msg, assignment.agentLabel);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
process.stdout.write('\x1b[2K\r'); // Clear the spinner line temporarily
|
|
75
|
+
p.log.step(`${pc.dim(`[${assignment.agentLabel}]`)} ${msg}`);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
67
78
|
return agent.execute(userRequest, chatHistory, abortSignal, (msg) => {
|
|
68
79
|
if (onProgress) {
|
|
69
80
|
onProgress(`Agent ${globalIndex + 1}/${agentCount}: ${msg}`);
|
|
70
81
|
}
|
|
71
|
-
});
|
|
82
|
+
}, onToolCall);
|
|
72
83
|
});
|
|
73
84
|
const chunkResults = await Promise.all(chunkPromises);
|
|
74
85
|
results.push(...chunkResults);
|