minovative-mind-cli 2.2.5 → 2.3.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/LICENSE.md +2 -0
- package/README.md +8 -8
- package/dist/commands/logout.js +1 -1
- package/dist/services/agent/slashCommands.js +53 -14
- package/dist/services/agent/toolLoop.js +4 -1
- package/dist/services/agent/types.d.ts +4 -0
- package/dist/services/agent-tools.js +55 -27
- package/dist/services/agent.d.ts +4 -0
- package/dist/services/agent.js +77 -60
- package/dist/services/ai.d.ts +1 -2
- package/dist/services/ai.js +21 -19
- package/dist/services/auth.d.ts +1 -1
- package/dist/services/auth.js +7 -28
- package/dist/services/chatHistoryService.d.ts +8 -0
- package/dist/services/contextAgent.js +18 -1
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.d.ts +2 -2
- package/dist/services/orchestration/investigationAgent.js +25 -6
- package/dist/services/orchestration/orchestrator.d.ts +1 -1
- package/dist/services/orchestration/orchestrator.js +37 -18
- package/dist/services/orchestration/subAgent.d.ts +1 -1
- package/dist/services/orchestration/subAgent.js +23 -13
- package/dist/services/proxyClient.d.ts +16 -0
- package/dist/services/proxyClient.js +32 -0
- package/dist/utils/analysisRunner.js +2 -2
- package/dist/utils/config.d.ts +3 -3
- package/dist/utils/config.js +3 -3
- package/dist/utils/credentialStore.d.ts +30 -0
- package/dist/utils/credentialStore.js +540 -0
- package/dist/utils/projectStorage.js +70 -0
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +4 -4
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
package/dist/services/agent.js
CHANGED
|
@@ -27,7 +27,8 @@ const execAsync = promisify(exec);
|
|
|
27
27
|
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
28
28
|
import { ensureProjectStorage, ensureIgnored, readCache, writeCache, invalidateCacheForDependents, } from '../utils/projectStorage.js';
|
|
29
29
|
import { GEMINI_MODELS } from '../utils/config.js';
|
|
30
|
-
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, setGlobalActiveModel,
|
|
30
|
+
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, setGlobalActiveModel, } from './ai.js';
|
|
31
|
+
import { getAndResetTurnUsage } from './proxyClient.js';
|
|
31
32
|
import { changeLogger } from './changeLogger.js';
|
|
32
33
|
import { chatHistoryService } from './chatHistoryService.js';
|
|
33
34
|
import { gatherContext, routeIntent, evaluateExecutionComplexity } from './contextAgent.js';
|
|
@@ -85,7 +86,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
85
86
|
chatHistoryService.init(workspaceRoot);
|
|
86
87
|
const chat = createSharedChatSession();
|
|
87
88
|
const inputHandler = new AsyncInputHandler();
|
|
88
|
-
const chatSessionState = { id: crypto.randomUUID(), title: '', totalTokens: 0 };
|
|
89
|
+
const chatSessionState = { id: crypto.randomUUID(), title: '', totalTokens: 0, totalInputTokens: 0, totalOutputTokens: 0 };
|
|
89
90
|
chat.setSessionInfo(chatSessionState.id, workspaceRoot);
|
|
90
91
|
const sessionInputHistory = [];
|
|
91
92
|
let isRawPasteMode = false;
|
|
@@ -112,6 +113,8 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
112
113
|
chatSessionState.id = crypto.randomUUID();
|
|
113
114
|
chatSessionState.title = '';
|
|
114
115
|
chatSessionState.totalTokens = 0;
|
|
116
|
+
chatSessionState.totalInputTokens = 0;
|
|
117
|
+
chatSessionState.totalOutputTokens = 0;
|
|
115
118
|
chat.setSessionInfo(chatSessionState.id, workspaceRoot);
|
|
116
119
|
}
|
|
117
120
|
inputHandler.stop();
|
|
@@ -131,15 +134,15 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
131
134
|
message: 'Command Menu',
|
|
132
135
|
options: [
|
|
133
136
|
{ value: '/models', label: '/models', hint: 'Change the active AI model' },
|
|
134
|
-
{ value: '/plan', label: '/plan', hint:
|
|
137
|
+
{ value: '/plan', label: '/plan', hint: `Toggle plan mode (build a plan without executing) ${isPlanMode ? pc.green('(ON)') : pc.red('(OFF)')}` },
|
|
135
138
|
{ value: '/paste', label: '/paste', hint: 'Paste large text directly into the CLI (Press Ctrl+D to submit)' },
|
|
136
139
|
{ value: '/clear', label: '/clear', hint: 'Clear chat session history' },
|
|
137
|
-
{ value: '/debug', label: '/debug', hint:
|
|
138
|
-
{ value: '/auto-approve', label: '/auto-approve', hint:
|
|
140
|
+
{ value: '/debug', label: '/debug', hint: `Toggle internal debug logs ${isDebugOn() ? pc.green('(ON)') : pc.red('(OFF)')}` },
|
|
141
|
+
{ value: '/auto-approve', label: '/auto-approve', hint: `Approve all future terminal commands ${getApprovalMode() === 'skip-all' ? pc.green('(ON)') : pc.red('(OFF)')}` },
|
|
139
142
|
{
|
|
140
143
|
value: '/sub-agents',
|
|
141
144
|
label: '/sub-agents',
|
|
142
|
-
hint:
|
|
145
|
+
hint: `Toggle the MMAAK Engine for parallel investigation and execution ${isSubAgentsEnabled() ? pc.green('(ON)') : pc.red('(OFF)')}`,
|
|
143
146
|
},
|
|
144
147
|
{
|
|
145
148
|
value: '/workspaces',
|
|
@@ -311,9 +314,9 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
311
314
|
// Apply the dynamic prompt updates and tool registrations to the active chat session
|
|
312
315
|
chat.setAgentConfig(dynamicSystemInstruction, config.tools);
|
|
313
316
|
if (getGlobalActiveModel() === GEMINI_MODELS.AUTO) {
|
|
314
|
-
let selectedModel = GEMINI_MODELS.
|
|
317
|
+
let selectedModel = GEMINI_MODELS.FLASH;
|
|
315
318
|
if (effectiveTargetAgent === 'CHAT') {
|
|
316
|
-
selectedModel = GEMINI_MODELS.
|
|
319
|
+
selectedModel = GEMINI_MODELS.FLASH_LITE;
|
|
317
320
|
}
|
|
318
321
|
else {
|
|
319
322
|
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
@@ -321,7 +324,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
321
324
|
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0);
|
|
322
325
|
spinner.stop();
|
|
323
326
|
process.stdout.write('\x1b[2K\r');
|
|
324
|
-
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.
|
|
327
|
+
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.FLASH_LITE : GEMINI_MODELS.FLASH;
|
|
325
328
|
}
|
|
326
329
|
chat.setModel(selectedModel);
|
|
327
330
|
debugLog(`Auto-routing to model ${selectedModel} based on intent ${effectiveTargetAgent}`);
|
|
@@ -330,21 +333,12 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
330
333
|
chat.setModel(getGlobalActiveModel());
|
|
331
334
|
}
|
|
332
335
|
if (gatherRes.contextResult) {
|
|
333
|
-
if (!
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
spinner.start('Thinking...');
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
p.log.success(pc.green('🔍 Investigation complete.'));
|
|
340
|
-
}
|
|
336
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
337
|
+
spinner.stop(pc.green('🔍 Investigation complete.'));
|
|
338
|
+
spinner.start('Thinking...');
|
|
341
339
|
}
|
|
342
340
|
else {
|
|
343
|
-
|
|
344
|
-
if (!inputHandler.isCurrentlyPrompting()) {
|
|
345
|
-
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
346
|
-
spinner.start('Thinking...');
|
|
347
|
-
}
|
|
341
|
+
p.log.success(pc.green('🔍 Investigation complete.'));
|
|
348
342
|
}
|
|
349
343
|
}
|
|
350
344
|
else {
|
|
@@ -352,6 +346,12 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
352
346
|
spinner.message('Thinking...');
|
|
353
347
|
}
|
|
354
348
|
}
|
|
349
|
+
let gitBranch = '';
|
|
350
|
+
try {
|
|
351
|
+
const { stdout } = await execAsync('git branch --show-current', { cwd: workspaceRoot });
|
|
352
|
+
gitBranch = stdout.trim();
|
|
353
|
+
}
|
|
354
|
+
catch { }
|
|
355
355
|
if (!isPlanMode && effectiveTargetAgent === 'EXECUTE' && isSubAgentsEnabled()) {
|
|
356
356
|
spinner.stop(); // Clear the spinner before delegating
|
|
357
357
|
process.stdout.write('\x1b[2K\r');
|
|
@@ -376,16 +376,19 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
376
376
|
console.log(marked.parse(cleanText));
|
|
377
377
|
}
|
|
378
378
|
// Print Usage Stats
|
|
379
|
-
const usage =
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
379
|
+
const usage = getAndResetTurnUsage();
|
|
380
|
+
chatSessionState.previousUsageMetadata = chatSessionState.latestUsageMetadata;
|
|
381
|
+
chatSessionState.latestUsageMetadata = usage;
|
|
382
|
+
chatSessionState.totalTokens += usage.totalTokenCount;
|
|
383
|
+
chatSessionState.totalInputTokens += (usage.promptTokens || 0) + (usage.cachedTokens || 0);
|
|
384
|
+
chatSessionState.totalOutputTokens += usage.candidatesTokens || 0;
|
|
385
|
+
if (usage.cachedTokens && usage.cachedTokens > 0) {
|
|
386
|
+
const totalInputTokens = (usage.promptTokens || 0) + usage.cachedTokens;
|
|
387
|
+
const percentSaved = totalInputTokens > 0 ? Math.round((usage.cachedTokens / totalInputTokens) * 100) : 0;
|
|
388
|
+
p.log.info(`${pc.green('⚡')} ${pc.green('Context Cache Hit:')} ${pc.bold(usage.cachedTokens.toLocaleString())} tokens cached ${pc.dim(`(Saved ~${percentSaved}% of input cost)`)}`);
|
|
389
|
+
}
|
|
390
|
+
if (usage.remainingBalance !== undefined) {
|
|
391
|
+
p.log.info(`${pc.dim('Credits Remaining:')} ${pc.cyan(usage.remainingBalance.toLocaleString())}`);
|
|
389
392
|
}
|
|
390
393
|
// Post-execution steps for the UI (saving history, invalidate cache)
|
|
391
394
|
const postExecutionChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
@@ -419,6 +422,17 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
419
422
|
title: chatSessionState.title,
|
|
420
423
|
timestamp: Date.now(),
|
|
421
424
|
history,
|
|
425
|
+
creditsRemaining: usage.remainingBalance,
|
|
426
|
+
lastTurnDuration: turnDuration,
|
|
427
|
+
modelName: chat.getModel(),
|
|
428
|
+
totalTokens: chatSessionState.totalTokens,
|
|
429
|
+
totalInputTokens: chatSessionState.totalInputTokens,
|
|
430
|
+
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
431
|
+
gitBranch: gitBranch || undefined,
|
|
432
|
+
autoApprove: getApprovalMode() === 'skip-all',
|
|
433
|
+
subAgents: isSubAgentsEnabled(),
|
|
434
|
+
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
435
|
+
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
422
436
|
})
|
|
423
437
|
.catch((e) => debugLog('Failed to auto-save session: ' + e));
|
|
424
438
|
}
|
|
@@ -458,7 +472,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
458
472
|
const correctionRes = await executeSelfCorrectionLoop(chat, result, workspaceRoot, inputHandler, effectiveTargetAgent, ac.signal, spinner, userInput, isPlanMode);
|
|
459
473
|
const finalText = correctionRes.finalText;
|
|
460
474
|
// Update latest usage metadata to reflect all completed turns
|
|
461
|
-
|
|
475
|
+
const usage = getAndResetTurnUsage();
|
|
462
476
|
const postExecutionChanges = changeLogger.getCurrentChangeSet()?.changes || [];
|
|
463
477
|
const modifiedFiles = postExecutionChanges
|
|
464
478
|
.filter((c) => c.action === 'modify' || c.action === 'create' || c.action === 'delete')
|
|
@@ -475,18 +489,18 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
475
489
|
console.log(marked.parse(cleanText));
|
|
476
490
|
}
|
|
477
491
|
}
|
|
478
|
-
if (
|
|
479
|
-
if (
|
|
480
|
-
const totalInputTokens = (
|
|
481
|
-
const percentSaved = totalInputTokens > 0 ? Math.round((
|
|
482
|
-
p.log.info(`${pc.green('⚡')} ${pc.green('Context Cache Hit:')} ${pc.bold(
|
|
492
|
+
if (usage) {
|
|
493
|
+
if (usage.cachedTokens && usage.cachedTokens > 0) {
|
|
494
|
+
const totalInputTokens = (usage.promptTokens || 0) + usage.cachedTokens;
|
|
495
|
+
const percentSaved = totalInputTokens > 0 ? Math.round((usage.cachedTokens / totalInputTokens) * 100) : 0;
|
|
496
|
+
p.log.info(`${pc.green('⚡')} ${pc.green('Context Cache Hit:')} ${pc.bold(usage.cachedTokens.toLocaleString())} tokens cached ${pc.dim(`(Saved ~${percentSaved}% of input cost)`)}`);
|
|
483
497
|
}
|
|
484
|
-
const inputTokens = (
|
|
485
|
-
const outputTokens =
|
|
486
|
-
const totalTokens =
|
|
498
|
+
const inputTokens = (usage.promptTokens || 0) + (usage.cachedTokens || 0);
|
|
499
|
+
const outputTokens = usage.candidatesTokens || 0;
|
|
500
|
+
const totalTokens = inputTokens + outputTokens;
|
|
487
501
|
p.log.info(`${pc.dim('Tokens Used:')} ${pc.cyan(totalTokens.toLocaleString())} ${pc.dim(`(Input: ${inputTokens.toLocaleString()}, Output: ${outputTokens.toLocaleString()})`)}`);
|
|
488
|
-
if (
|
|
489
|
-
p.log.info(`${pc.dim('Credits Remaining:')} ${pc.cyan(
|
|
502
|
+
if (usage.remainingBalance !== undefined) {
|
|
503
|
+
p.log.info(`${pc.dim('Credits Remaining:')} ${pc.cyan(usage.remainingBalance.toLocaleString())}`);
|
|
490
504
|
}
|
|
491
505
|
}
|
|
492
506
|
const turnEndTime = Date.now();
|
|
@@ -496,18 +510,13 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
496
510
|
if (finalText !== '[Generation stopped.]') {
|
|
497
511
|
changeLogger.markComplete();
|
|
498
512
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
513
|
+
if (usage) {
|
|
514
|
+
chatSessionState.previousUsageMetadata = chatSessionState.latestUsageMetadata;
|
|
515
|
+
chatSessionState.latestUsageMetadata = usage;
|
|
516
|
+
chatSessionState.totalTokens += usage.totalTokenCount || 0;
|
|
517
|
+
chatSessionState.totalInputTokens += (usage.promptTokens || 0) + (usage.cachedTokens || 0);
|
|
518
|
+
chatSessionState.totalOutputTokens += usage.candidatesTokens || 0;
|
|
503
519
|
}
|
|
504
|
-
chatSessionState.totalTokens += turnTokens;
|
|
505
|
-
let gitBranch = '';
|
|
506
|
-
try {
|
|
507
|
-
const { stdout } = await execAsync('git branch --show-current', { cwd: workspaceRoot });
|
|
508
|
-
gitBranch = stdout.trim();
|
|
509
|
-
}
|
|
510
|
-
catch { }
|
|
511
520
|
// Auto-save chat history
|
|
512
521
|
const history = chat.getRawHistory();
|
|
513
522
|
if (history.length > 0) {
|
|
@@ -525,9 +534,13 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
525
534
|
lastTurnDuration: turnDuration,
|
|
526
535
|
modelName: chat.getModel(),
|
|
527
536
|
totalTokens: chatSessionState.totalTokens,
|
|
537
|
+
totalInputTokens: chatSessionState.totalInputTokens,
|
|
538
|
+
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
528
539
|
gitBranch: gitBranch || undefined,
|
|
529
540
|
autoApprove: getApprovalMode() === 'skip-all',
|
|
530
541
|
subAgents: isSubAgentsEnabled(),
|
|
542
|
+
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
543
|
+
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
531
544
|
});
|
|
532
545
|
}
|
|
533
546
|
catch (e) {
|
|
@@ -544,9 +557,13 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
544
557
|
lastTurnDuration: turnDuration,
|
|
545
558
|
modelName: chat.getModel(),
|
|
546
559
|
totalTokens: chatSessionState.totalTokens,
|
|
560
|
+
totalInputTokens: chatSessionState.totalInputTokens,
|
|
561
|
+
totalOutputTokens: chatSessionState.totalOutputTokens,
|
|
547
562
|
gitBranch: gitBranch || undefined,
|
|
548
563
|
autoApprove: getApprovalMode() === 'skip-all',
|
|
549
564
|
subAgents: isSubAgentsEnabled(),
|
|
565
|
+
latestUsageMetadata: chatSessionState.latestUsageMetadata,
|
|
566
|
+
previousUsageMetadata: chatSessionState.previousUsageMetadata,
|
|
550
567
|
})
|
|
551
568
|
.catch((e) => debugLog('Failed to auto-save session: ' + e));
|
|
552
569
|
}
|
|
@@ -584,19 +601,19 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
584
601
|
hint: 'The Pro model for complex logic',
|
|
585
602
|
},
|
|
586
603
|
{
|
|
587
|
-
value: 'gemini-3.
|
|
588
|
-
label: 'Gemini 3.
|
|
604
|
+
value: 'gemini-3.6-flash',
|
|
605
|
+
label: 'Gemini 3.6 Flash',
|
|
589
606
|
hint: 'Balanced performance',
|
|
590
607
|
},
|
|
591
608
|
{
|
|
592
|
-
value: 'gemini-3.
|
|
593
|
-
label: 'Gemini 3.
|
|
594
|
-
hint: '
|
|
609
|
+
value: 'gemini-3.5-flash-lite',
|
|
610
|
+
label: 'Gemini 3.5 Flash-Lite',
|
|
611
|
+
hint: 'Best for speed and cost efficiency',
|
|
595
612
|
},
|
|
596
613
|
{
|
|
597
614
|
value: 'auto',
|
|
598
615
|
label: 'Auto (Flash-Lite / Flash)',
|
|
599
|
-
hint: 'Dynamically routes between Gemini 3.
|
|
616
|
+
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.6 based on prompt complexity',
|
|
600
617
|
},
|
|
601
618
|
],
|
|
602
619
|
});
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ export declare function setModelOverride(agent: 'context' | 'execution', overrid
|
|
|
3
3
|
export declare function clearModelOverrides(): void;
|
|
4
4
|
export declare function setGlobalActiveModel(model: string): void;
|
|
5
5
|
export declare function getGlobalActiveModel(): string;
|
|
6
|
-
export declare function getGlobalLatestUsageMetadata(): any;
|
|
7
6
|
export declare class ProxyChatSession {
|
|
8
7
|
private history;
|
|
9
8
|
private modelName;
|
|
@@ -74,7 +73,7 @@ export declare function getPlanModeConfig(): {
|
|
|
74
73
|
tools: never[];
|
|
75
74
|
};
|
|
76
75
|
/**
|
|
77
|
-
* Compresses a large string of text using gemini-3.
|
|
76
|
+
* Compresses a large string of text using gemini-3.5-flash-lite.
|
|
78
77
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
79
78
|
*/
|
|
80
79
|
export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any): Promise<string>;
|
package/dist/services/ai.js
CHANGED
|
@@ -52,10 +52,6 @@ export function setGlobalActiveModel(model) {
|
|
|
52
52
|
export function getGlobalActiveModel() {
|
|
53
53
|
return globalActiveModel;
|
|
54
54
|
}
|
|
55
|
-
let globalLatestUsageMetadata = undefined;
|
|
56
|
-
export function getGlobalLatestUsageMetadata() {
|
|
57
|
-
return globalLatestUsageMetadata;
|
|
58
|
-
}
|
|
59
55
|
const proxyClient = new ProxyClient();
|
|
60
56
|
// ─── History Limits ──────────────────────────────────────────────────
|
|
61
57
|
/** Maximum number of Content entries to keep in the sliding history window. */
|
|
@@ -214,9 +210,6 @@ export class ProxyChatSession {
|
|
|
214
210
|
this.systemInstruction, effectiveGenerationConfig, onChunk ? { onChunk } : undefined, // streamCallbacks
|
|
215
211
|
abortSignal);
|
|
216
212
|
this.latestUsageMetadata = result.usageMetadata;
|
|
217
|
-
if (result.usageMetadata) {
|
|
218
|
-
globalLatestUsageMetadata = result.usageMetadata;
|
|
219
|
-
}
|
|
220
213
|
// Track token usage metrics
|
|
221
214
|
if (result.usageMetadata) {
|
|
222
215
|
const collector = getMetricCollector();
|
|
@@ -283,7 +276,7 @@ export class ProxyChatSession {
|
|
|
283
276
|
export function createSharedChatSession() {
|
|
284
277
|
let model = executionModelOverride || getGlobalActiveModel();
|
|
285
278
|
if (model === 'auto')
|
|
286
|
-
model = GEMINI_MODELS.
|
|
279
|
+
model = GEMINI_MODELS.FLASH; // Will be overridden per-turn in executeSingleTurn
|
|
287
280
|
return new ProxyChatSession(model, GENERAL_CHAT_INSTRUCTION, [], {
|
|
288
281
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
289
282
|
temperature: executionTempOverride !== null ? executionTempOverride : 1,
|
|
@@ -310,10 +303,10 @@ export function getPlanModeConfig() {
|
|
|
310
303
|
};
|
|
311
304
|
}
|
|
312
305
|
/**
|
|
313
|
-
* Compresses a large string of text using gemini-3.
|
|
306
|
+
* Compresses a large string of text using gemini-3.5-flash-lite.
|
|
314
307
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
315
308
|
*/
|
|
316
|
-
export async function compressTextUsingFlashLite(text, instruction =
|
|
309
|
+
export async function compressTextUsingFlashLite(text, instruction = "<directives>\nSummarize the following text concisely. Preserve the most critical technical details, function names, and architecture logic. Make sure it's understandable without the fluff.\n</directives>", inlineData) {
|
|
317
310
|
if (!text || (text.length < 1000 && !inlineData))
|
|
318
311
|
return text; // Don't compress tiny texts
|
|
319
312
|
try {
|
|
@@ -322,7 +315,7 @@ export async function compressTextUsingFlashLite(text, instruction = 'Summarize
|
|
|
322
315
|
return text;
|
|
323
316
|
let model = getGlobalActiveModel();
|
|
324
317
|
if (model === 'auto')
|
|
325
|
-
model = GEMINI_MODELS.
|
|
318
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
326
319
|
const parts = [{ text }];
|
|
327
320
|
if (inlineData) {
|
|
328
321
|
parts.push({ inlineData });
|
|
@@ -461,8 +454,17 @@ export function getContextToolDeclarations() {
|
|
|
461
454
|
description: 'Write and execute a disposable analysis script to structurally map code in the workspace. ' +
|
|
462
455
|
'Use this to get exact line ranges for functions, classes, and variables by leveraging the ' +
|
|
463
456
|
"language's native AST parser (e.g., TypeScript compiler API, Python ast module, go/parser). " +
|
|
457
|
+
'You can also use this to probe the development environment — detecting available runtimes ' +
|
|
458
|
+
'(e.g., node --version, python3 --version), checking if ports are in use, identifying project ' +
|
|
459
|
+
'type (monorepo, package manager), or diagnosing system-level issues (disk space, memory) that ' +
|
|
460
|
+
'may affect execution. ' +
|
|
461
|
+
'For complex investigation tasks, you can write lightweight ML scripts (e.g., TF-IDF cosine similarity ' +
|
|
462
|
+
'to rank file relevance, Z-score outlier detection for anomalous log lines, K-Means clustering, or Naive ' +
|
|
463
|
+
'Bayes classification). Default to "node" for generic math/analysis as a safe baseline, but act like ' +
|
|
464
|
+
'a native inhabitant of the host environment — if Python, Go, Rust, or specialized libraries are available ' +
|
|
465
|
+
'in the project context, leverage the host\'s native runtimes and standard libraries for maximum efficiency. ' +
|
|
464
466
|
'The script is executed from a temporary directory and automatically cleaned up after execution. ' +
|
|
465
|
-
'Output should be structured JSON to stdout
|
|
467
|
+
'Output should be structured JSON to stdout. ' +
|
|
466
468
|
'Use the results to make precise read_file calls with exact startLine/endLine instead of guessing. ' +
|
|
467
469
|
'CRITICAL: Do not use this tool on binary, document, or non-code files (e.g. PDF, image, audio, docx).',
|
|
468
470
|
parameters: {
|
|
@@ -519,7 +521,7 @@ export function createContextAgentSession() {
|
|
|
519
521
|
const contextTools = getContextToolDeclarations();
|
|
520
522
|
let model = contextModelOverride || getGlobalActiveModel();
|
|
521
523
|
if (model === 'auto')
|
|
522
|
-
model = GEMINI_MODELS.
|
|
524
|
+
model = GEMINI_MODELS.FLASH;
|
|
523
525
|
return new ProxyChatSession(model, CONTEXT_SYSTEM_INSTRUCTION.replace('{{MULTI_WORKSPACE_BLOCK}}', getMultiWorkspaceBlock()), contextTools, {
|
|
524
526
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
525
527
|
temperature: contextTempOverride !== null ? contextTempOverride : 1,
|
|
@@ -531,7 +533,7 @@ export function createContextAgentSession() {
|
|
|
531
533
|
export function createIntentRouterSession() {
|
|
532
534
|
let model = getGlobalActiveModel();
|
|
533
535
|
if (model === 'auto')
|
|
534
|
-
model = GEMINI_MODELS.
|
|
536
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
535
537
|
return new ProxyChatSession(model, INTENT_ROUTER_SYSTEM_INSTRUCTION, [], // no tools
|
|
536
538
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
537
539
|
}
|
|
@@ -539,7 +541,7 @@ export function createIntentRouterSession() {
|
|
|
539
541
|
export function createExecutionComplexitySession() {
|
|
540
542
|
let model = getGlobalActiveModel();
|
|
541
543
|
if (model === 'auto')
|
|
542
|
-
model = GEMINI_MODELS.
|
|
544
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
543
545
|
return new ProxyChatSession(model, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
544
546
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
545
547
|
}
|
|
@@ -547,7 +549,7 @@ export function createExecutionComplexitySession() {
|
|
|
547
549
|
export function createInvestigationComplexitySession() {
|
|
548
550
|
let model = getGlobalActiveModel();
|
|
549
551
|
if (model === 'auto')
|
|
550
|
-
model = GEMINI_MODELS.
|
|
552
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
551
553
|
return new ProxyChatSession(model, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
552
554
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
553
555
|
}
|
|
@@ -555,7 +557,7 @@ export function createInvestigationComplexitySession() {
|
|
|
555
557
|
export function createWebSearchAgentSession() {
|
|
556
558
|
let model = getGlobalActiveModel();
|
|
557
559
|
if (model === 'auto')
|
|
558
|
-
model = GEMINI_MODELS.
|
|
560
|
+
model = GEMINI_MODELS.FLASH;
|
|
559
561
|
return new ProxyChatSession(model, WEB_SEARCH_SYSTEM_INSTRUCTION, [{ googleSearch: {} }], {
|
|
560
562
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
561
563
|
temperature: 1,
|
|
@@ -574,11 +576,11 @@ export async function generateChatTitle(firstMessage) {
|
|
|
574
576
|
const idToken = await getAuthorizedIdToken();
|
|
575
577
|
if (!idToken)
|
|
576
578
|
return firstMessage.substring(0, maxLength);
|
|
577
|
-
const instruction = "
|
|
579
|
+
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>";
|
|
578
580
|
const contents = [{ role: 'user', parts: [{ text: firstMessage.substring(0, 500) }] }];
|
|
579
581
|
let model = getGlobalActiveModel();
|
|
580
582
|
if (model === 'auto')
|
|
581
|
-
model = GEMINI_MODELS.
|
|
583
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
582
584
|
const result = await proxyClient.generateFunctionCallViaProxy(idToken, model, contents, [], // no tools
|
|
583
585
|
undefined, instruction, { temperature: 0.2 });
|
|
584
586
|
let title = '';
|
package/dist/services/auth.d.ts
CHANGED
package/dist/services/auth.js
CHANGED
|
@@ -1,23 +1,5 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as os from 'os';
|
|
4
1
|
import { FIREBASE_API_KEY, GITHUB_CLIENT_ID } from '../utils/config.js';
|
|
5
|
-
|
|
6
|
-
function getAuthData() {
|
|
7
|
-
try {
|
|
8
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
9
|
-
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
catch (err) {
|
|
13
|
-
// ignore
|
|
14
|
-
}
|
|
15
|
-
return {};
|
|
16
|
-
}
|
|
17
|
-
function saveAuthData(data) {
|
|
18
|
-
const current = getAuthData();
|
|
19
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify({ ...current, ...data }, null, 2));
|
|
20
|
-
}
|
|
2
|
+
import { saveCredentials, loadCredentials, clearCredentials } from '../utils/credentialStore.js';
|
|
21
3
|
export async function login() {
|
|
22
4
|
try {
|
|
23
5
|
// 1. Start GitHub Device Flow
|
|
@@ -98,8 +80,8 @@ export async function login() {
|
|
|
98
80
|
if (!firebaseTokenResponse.idToken) {
|
|
99
81
|
throw new Error('Failed to get Firebase ID token.');
|
|
100
82
|
}
|
|
101
|
-
// 5. Store the Firebase token
|
|
102
|
-
|
|
83
|
+
// 5. Store the Firebase token in the OS-native secure credential store
|
|
84
|
+
await saveCredentials({
|
|
103
85
|
idToken: firebaseTokenResponse.idToken,
|
|
104
86
|
refreshToken: firebaseTokenResponse.refreshToken,
|
|
105
87
|
idTokenExpiry: Date.now() + 50 * 60 * 1000, // 50 mins
|
|
@@ -139,14 +121,11 @@ async function exchangeGithubTokenForFirebase(githubAccessToken) {
|
|
|
139
121
|
}
|
|
140
122
|
return await signInResponse.json();
|
|
141
123
|
}
|
|
142
|
-
export function logout() {
|
|
143
|
-
|
|
144
|
-
fs.unlinkSync(CONFIG_FILE);
|
|
145
|
-
}
|
|
146
|
-
console.log('Successfully signed out.');
|
|
124
|
+
export async function logout() {
|
|
125
|
+
await clearCredentials();
|
|
147
126
|
}
|
|
148
127
|
export async function getAuthorizedIdToken() {
|
|
149
|
-
const data =
|
|
128
|
+
const data = await loadCredentials();
|
|
150
129
|
if (!data.idToken)
|
|
151
130
|
return undefined;
|
|
152
131
|
if (data.idTokenExpiry && Date.now() < data.idTokenExpiry) {
|
|
@@ -168,7 +147,7 @@ export async function getAuthorizedIdToken() {
|
|
|
168
147
|
const refreshData = (await response.json());
|
|
169
148
|
const newIdToken = refreshData.id_token;
|
|
170
149
|
if (newIdToken) {
|
|
171
|
-
|
|
150
|
+
await saveCredentials({
|
|
172
151
|
idToken: newIdToken,
|
|
173
152
|
refreshToken: refreshData.refresh_token || data.refreshToken,
|
|
174
153
|
idTokenExpiry: Date.now() + 50 * 60 * 1000,
|
|
@@ -21,12 +21,20 @@ export interface ChatSessionData {
|
|
|
21
21
|
modelName?: string;
|
|
22
22
|
/** Optional total cumulative tokens consumed during the session. */
|
|
23
23
|
totalTokens?: number;
|
|
24
|
+
/** Optional total cumulative input tokens (prompt + cached) consumed during the session. */
|
|
25
|
+
totalInputTokens?: number;
|
|
26
|
+
/** Optional total cumulative output tokens consumed during the session. */
|
|
27
|
+
totalOutputTokens?: number;
|
|
24
28
|
/** Optional name of the Git branch active when this session was last updated. */
|
|
25
29
|
gitBranch?: string;
|
|
26
30
|
/** Optional flag indicating whether auto-approval of commands was enabled in this session. */
|
|
27
31
|
autoApprove?: boolean;
|
|
28
32
|
/** Optional flag indicating whether sub-agents were enabled or active during this session. */
|
|
29
33
|
subAgents?: boolean;
|
|
34
|
+
/** Optional metadata about the token usage from the very last turn. */
|
|
35
|
+
latestUsageMetadata?: any;
|
|
36
|
+
/** Optional metadata about the token usage from the turn prior to the last turn. */
|
|
37
|
+
previousUsageMetadata?: any;
|
|
30
38
|
}
|
|
31
39
|
/**
|
|
32
40
|
* Service responsible for managing the persistence, retrieval, and deletion of chat session histories.
|
|
@@ -232,6 +232,8 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
232
232
|
let summary = 'No relevant context found.';
|
|
233
233
|
let isInvestigationFinished = false;
|
|
234
234
|
const visitedToolCalls = new Set();
|
|
235
|
+
let consecutiveDuplicates = 0;
|
|
236
|
+
const MAX_CONSECUTIVE_DUPLICATES = 3;
|
|
235
237
|
let chainedMessages = [];
|
|
236
238
|
let webSearchSummary = '';
|
|
237
239
|
// Initial prompt
|
|
@@ -286,14 +288,29 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
286
288
|
// Deduplicate identical tool calls
|
|
287
289
|
const callSignature = `${call.name}:${JSON.stringify(args)}`;
|
|
288
290
|
if (call.name !== 'finish_investigation' && visitedToolCalls.has(callSignature)) {
|
|
291
|
+
consecutiveDuplicates++;
|
|
292
|
+
if (consecutiveDuplicates >= MAX_CONSECUTIVE_DUPLICATES) {
|
|
293
|
+
// Force-finish: the model is stuck in a loop, finalize with whatever context we have
|
|
294
|
+
const forceMsg = `Investigation auto-completed: the model repeated the same tool call ${MAX_CONSECUTIVE_DUPLICATES} times consecutively.`;
|
|
295
|
+
debugLog(`[Context Agent] ${forceMsg}`);
|
|
296
|
+
if (onProgress)
|
|
297
|
+
onProgress(forceMsg);
|
|
298
|
+
if (!summary || summary === 'No relevant context found.') {
|
|
299
|
+
summary = 'Investigation was auto-completed due to repeated duplicate tool calls. Review the gathered files for context.';
|
|
300
|
+
}
|
|
301
|
+
isFinished = true;
|
|
302
|
+
isInvestigationFinished = relevantFiles.size > 0;
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
289
305
|
functionResponses.push({
|
|
290
306
|
functionResponse: {
|
|
291
307
|
name: call.name,
|
|
292
|
-
response: { error:
|
|
308
|
+
response: { error: `DUPLICATE CALL BLOCKED (attempt ${consecutiveDuplicates}/${MAX_CONSECUTIVE_DUPLICATES}): You already executed this exact tool call. Do NOT retry it. Use the results you already have and call finish_investigation now, or try a DIFFERENT tool call with different parameters.` }
|
|
293
309
|
}
|
|
294
310
|
});
|
|
295
311
|
continue;
|
|
296
312
|
}
|
|
313
|
+
consecutiveDuplicates = 0;
|
|
297
314
|
visitedToolCalls.add(callSignature);
|
|
298
315
|
let logMsg = ` [Context Agent] Executing ${call.name}`;
|
|
299
316
|
if (call.name === 'finish_investigation') {
|
|
@@ -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.5-flash-lite` (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.5-flash-lite` (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.
|
|
@@ -59,8 +59,8 @@ export declare class InvestigationAgentRunner {
|
|
|
59
59
|
private creditsUsed;
|
|
60
60
|
private inputTokens;
|
|
61
61
|
private outputTokens;
|
|
62
|
-
/**
|
|
63
|
-
static readonly STALL_TIMEOUT_MS =
|
|
62
|
+
/** Stall timeout for investigation agents (300s to allow for heavy vision/web tasks) */
|
|
63
|
+
static readonly STALL_TIMEOUT_MS = 300000;
|
|
64
64
|
constructor(agentLabel: string, domains: string[], workspaceRoot: string, readCache: ReadCache, projectTree: string, projectType: string);
|
|
65
65
|
/**
|
|
66
66
|
* Builds a domain-scoped system instruction. Extends the base Context Agent
|