minovative-mind-cli 2.2.3 → 2.2.5
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 +19 -20
- package/dist/services/agent/slashCommands.js +5 -5
- package/dist/services/agent.js +14 -14
- package/dist/services/ai.js +4 -4
- package/dist/services/chatHistoryService.d.ts +55 -0
- package/dist/services/chatHistoryService.js +39 -0
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.d.ts +4 -0
- package/dist/services/orchestration/investigationAgent.js +11 -5
- package/dist/services/orchestration/investigationOrchestrator.js +3 -1
- package/dist/services/orchestration/orchestrator.js +5 -1
- package/dist/services/orchestration/subAgent.d.ts +4 -0
- package/dist/services/orchestration/subAgent.js +11 -5
- package/dist/services/workspace.d.ts +34 -0
- package/dist/services/workspace.js +36 -1
- package/dist/utils/config.d.ts +0 -1
- package/dist/utils/config.js +0 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -86,13 +86,12 @@ Works in any terminal — SSH, Docker, CI pipelines, Vim, Windows Command Prompt
|
|
|
86
86
|
|
|
87
87
|
Hot-swap during a session using `/models`:
|
|
88
88
|
|
|
89
|
-
| Model | Best for
|
|
90
|
-
| ------------------------- |
|
|
89
|
+
| Model | Best for |
|
|
90
|
+
| ------------------------- | ---------------------------------------------- |
|
|
91
91
|
| **Auto** (default) | Automatically selects (3.5 Flash or 2.5 Flash) |
|
|
92
|
-
| **Gemini 3.5 Flash** | Everyday coding — fast and accurate
|
|
93
|
-
| **Gemini
|
|
94
|
-
| **Gemini 3.1
|
|
95
|
-
| **Gemini 3.1 Flash Lite** | Maximum speed and cost efficiency |
|
|
92
|
+
| **Gemini 3.5 Flash** | Everyday coding — fast and accurate |
|
|
93
|
+
| **Gemini 3.1 Pro** | Complex architectural changes |
|
|
94
|
+
| **Gemini 3.1 Flash Lite** | Maximum speed and cost efficiency |
|
|
96
95
|
|
|
97
96
|
> Background tasks (routing, compression, commits) always use lightweight
|
|
98
97
|
> models automatically. You pay for those lightweight model
|
|
@@ -104,21 +103,21 @@ Hot-swap during a session using `/models`:
|
|
|
104
103
|
|
|
105
104
|
## Session Commands
|
|
106
105
|
|
|
107
|
-
| Command | What it does
|
|
108
|
-
| --------------- |
|
|
109
|
-
| `/models` | Hot-swap the active model
|
|
110
|
-
| `/plan` | Toggle plan mode to review implementation strategies
|
|
111
|
-
| `/paste` | Multi-line input mode (cancel with Ctrl+C)
|
|
112
|
-
| `/clear` | Clear conversation history
|
|
113
|
-
| `/debug` | Expose internal agent diagnostics
|
|
114
|
-
| `/auto-approve` | Toggle skipping confirmation prompts for commands
|
|
115
|
-
| `/sub-agents` | Toggle the MMAAK Engine for parallel investigation and execution (concurrency limited to 2)
|
|
116
|
-
| `/stats` | View current session statistics and configuration
|
|
117
|
-
| `/commit` | Generate a conventional commit message from your diff
|
|
118
|
-
| `/revert` | Undo changes from the last turn, or toggle the revert logger
|
|
106
|
+
| Command | What it does |
|
|
107
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `/models` | Hot-swap the active model |
|
|
109
|
+
| `/plan` | Toggle plan mode to review implementation strategies |
|
|
110
|
+
| `/paste` | Multi-line input mode (cancel with Ctrl+C) |
|
|
111
|
+
| `/clear` | Clear conversation history |
|
|
112
|
+
| `/debug` | Expose internal agent diagnostics |
|
|
113
|
+
| `/auto-approve` | Toggle skipping confirmation prompts for commands |
|
|
114
|
+
| `/sub-agents` | Toggle the MMAAK Engine for parallel investigation and execution (concurrency limited to 2) |
|
|
115
|
+
| `/stats` | View current session statistics and configuration |
|
|
116
|
+
| `/commit` | Generate a conventional commit message from your diff |
|
|
117
|
+
| `/revert` | Undo changes from the last turn, or toggle the revert logger |
|
|
119
118
|
| `/chats` | View, resume, or delete previous chat sessions (with token tracking, Git branch, auto-approve status, and sub-agent state) |
|
|
120
|
-
| `/workspaces` | Manage active workspaces and linked cross-repo aliases
|
|
121
|
-
| `stop` | Abort generation immediately
|
|
119
|
+
| `/workspaces` | Manage active workspaces and linked cross-repo aliases |
|
|
120
|
+
| `stop` | Abort generation immediately |
|
|
122
121
|
|
|
123
122
|
---
|
|
124
123
|
|
|
@@ -78,14 +78,14 @@ export async function handleSlashCommand(command, context) {
|
|
|
78
78
|
hint: 'Balanced performance',
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
|
-
value: 'gemini-
|
|
82
|
-
label: 'Gemini
|
|
83
|
-
hint: '
|
|
81
|
+
value: 'gemini-3.1-flash-lite',
|
|
82
|
+
label: 'Gemini 3.1 Flash-Lite',
|
|
83
|
+
hint: 'Ultra-fast and cost-effective',
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
value: 'auto',
|
|
87
|
-
label: 'Auto (
|
|
88
|
-
hint: 'Dynamically routes between Gemini
|
|
87
|
+
label: 'Auto (Flash-Lite / Flash)',
|
|
88
|
+
hint: 'Dynamically routes between Gemini 3.1 Flash-Lite and Gemini 3.5 based on prompt complexity',
|
|
89
89
|
},
|
|
90
90
|
],
|
|
91
91
|
});
|
package/dist/services/agent.js
CHANGED
|
@@ -313,7 +313,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
313
313
|
if (getGlobalActiveModel() === GEMINI_MODELS.AUTO) {
|
|
314
314
|
let selectedModel = GEMINI_MODELS.FLASH_3_5;
|
|
315
315
|
if (effectiveTargetAgent === 'CHAT') {
|
|
316
|
-
selectedModel = GEMINI_MODELS.
|
|
316
|
+
selectedModel = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
317
317
|
}
|
|
318
318
|
else {
|
|
319
319
|
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
@@ -321,7 +321,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
321
321
|
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0);
|
|
322
322
|
spinner.stop();
|
|
323
323
|
process.stdout.write('\x1b[2K\r');
|
|
324
|
-
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.
|
|
324
|
+
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.FLASH_LITE_3_1 : GEMINI_MODELS.FLASH_3_5;
|
|
325
325
|
}
|
|
326
326
|
chat.setModel(selectedModel);
|
|
327
327
|
debugLog(`Auto-routing to model ${selectedModel} based on intent ${effectiveTargetAgent}`);
|
|
@@ -352,7 +352,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
352
352
|
spinner.message('Thinking...');
|
|
353
353
|
}
|
|
354
354
|
}
|
|
355
|
-
if (effectiveTargetAgent === 'EXECUTE' && isSubAgentsEnabled()) {
|
|
355
|
+
if (!isPlanMode && effectiveTargetAgent === 'EXECUTE' && isSubAgentsEnabled()) {
|
|
356
356
|
spinner.stop(); // Clear the spinner before delegating
|
|
357
357
|
process.stdout.write('\x1b[2K\r');
|
|
358
358
|
const orchestrator = new Orchestrator(workspaceRoot, chatSessionState.id, inputHandler);
|
|
@@ -455,7 +455,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
455
455
|
const calls = result.response.functionCalls();
|
|
456
456
|
debugLog(`Initial functionCalls: ${calls && calls.length > 0 ? JSON.stringify(calls) : 'None'}`);
|
|
457
457
|
// Stage 2: Recursive Tool Loops and Automated Self-Correction (delegated to a helper function to avoid nested loop warning)
|
|
458
|
-
const correctionRes = await executeSelfCorrectionLoop(chat, result, workspaceRoot, inputHandler, effectiveTargetAgent, ac.signal, spinner, userInput);
|
|
458
|
+
const correctionRes = await executeSelfCorrectionLoop(chat, result, workspaceRoot, inputHandler, effectiveTargetAgent, ac.signal, spinner, userInput, isPlanMode);
|
|
459
459
|
const finalText = correctionRes.finalText;
|
|
460
460
|
// Update latest usage metadata to reflect all completed turns
|
|
461
461
|
latestUsage = chat.getLatestUsageMetadata() || latestUsage;
|
|
@@ -481,6 +481,10 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
481
481
|
const percentSaved = totalInputTokens > 0 ? Math.round((latestUsage.cachedTokens / totalInputTokens) * 100) : 0;
|
|
482
482
|
p.log.info(`${pc.green('⚡')} ${pc.green('Context Cache Hit:')} ${pc.bold(latestUsage.cachedTokens.toLocaleString())} tokens cached ${pc.dim(`(Saved ~${percentSaved}% of input cost)`)}`);
|
|
483
483
|
}
|
|
484
|
+
const inputTokens = (latestUsage.promptTokens || 0) + (latestUsage.cachedTokens || 0);
|
|
485
|
+
const outputTokens = latestUsage.candidatesTokens || 0;
|
|
486
|
+
const totalTokens = latestUsage.totalTokenCount || inputTokens + outputTokens;
|
|
487
|
+
p.log.info(`${pc.dim('Tokens Used:')} ${pc.cyan(totalTokens.toLocaleString())} ${pc.dim(`(Input: ${inputTokens.toLocaleString()}, Output: ${outputTokens.toLocaleString()})`)}`);
|
|
484
488
|
if (latestUsage.remainingBalance !== undefined) {
|
|
485
489
|
p.log.info(`${pc.dim('Credits Remaining:')} ${pc.cyan(latestUsage.remainingBalance.toLocaleString())}`);
|
|
486
490
|
}
|
|
@@ -494,7 +498,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
494
498
|
}
|
|
495
499
|
let turnTokens = 0;
|
|
496
500
|
if (latestUsage) {
|
|
497
|
-
turnTokens =
|
|
501
|
+
turnTokens =
|
|
502
|
+
(latestUsage.promptTokens || 0) + (latestUsage.cachedTokens || 0) + (latestUsage.candidatesTokens || 0);
|
|
498
503
|
}
|
|
499
504
|
chatSessionState.totalTokens += turnTokens;
|
|
500
505
|
let gitBranch = '';
|
|
@@ -583,11 +588,6 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
583
588
|
label: 'Gemini 3.5 Flash',
|
|
584
589
|
hint: 'Balanced performance',
|
|
585
590
|
},
|
|
586
|
-
{
|
|
587
|
-
value: 'gemini-2.5-flash',
|
|
588
|
-
label: 'Gemini 2.5 Flash',
|
|
589
|
-
hint: 'Google Gemini 2.5 Flash model with built-in reasoning',
|
|
590
|
-
},
|
|
591
591
|
{
|
|
592
592
|
value: 'gemini-3.1-flash-lite',
|
|
593
593
|
label: 'Gemini 3.1 Flash-Lite',
|
|
@@ -595,8 +595,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
595
595
|
},
|
|
596
596
|
{
|
|
597
597
|
value: 'auto',
|
|
598
|
-
label: 'Auto (
|
|
599
|
-
hint: 'Dynamically routes between Gemini
|
|
598
|
+
label: 'Auto (Flash-Lite / Flash)',
|
|
599
|
+
hint: 'Dynamically routes between Gemini 3.1 Flash-Lite and Gemini 3.5 based on prompt complexity',
|
|
600
600
|
},
|
|
601
601
|
],
|
|
602
602
|
});
|
|
@@ -717,7 +717,7 @@ async function compressContextFiles(workspaceRoot, contextResult) {
|
|
|
717
717
|
}
|
|
718
718
|
return compressedFiles;
|
|
719
719
|
}
|
|
720
|
-
async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inputHandler, effectiveTargetAgent, signal, spinner, originalUserInput) {
|
|
720
|
+
async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inputHandler, effectiveTargetAgent, signal, spinner, originalUserInput, isPlanMode) {
|
|
721
721
|
let correctionAttempts = 0;
|
|
722
722
|
const MAX_CORRECTIONS = 5;
|
|
723
723
|
let result = initialResult;
|
|
@@ -761,7 +761,7 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
761
761
|
}
|
|
762
762
|
previousChangeCount = currentChanges.length;
|
|
763
763
|
// ─── Intent Verification Phase ─────────────────────────────────────
|
|
764
|
-
if (effectiveTargetAgent === 'EXECUTE' && !intentVerified) {
|
|
764
|
+
if (effectiveTargetAgent === 'EXECUTE' && !intentVerified && !isPlanMode) {
|
|
765
765
|
spinner.start('Verifying task completion...');
|
|
766
766
|
const intentVerificationPrompt = `SYSTEM CHECK: Please objectively verify that you have fully completed the user's original explicit request:
|
|
767
767
|
|
package/dist/services/ai.js
CHANGED
|
@@ -322,7 +322,7 @@ export async function compressTextUsingFlashLite(text, instruction = 'Summarize
|
|
|
322
322
|
return text;
|
|
323
323
|
let model = getGlobalActiveModel();
|
|
324
324
|
if (model === 'auto')
|
|
325
|
-
model = GEMINI_MODELS.
|
|
325
|
+
model = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
326
326
|
const parts = [{ text }];
|
|
327
327
|
if (inlineData) {
|
|
328
328
|
parts.push({ inlineData });
|
|
@@ -531,7 +531,7 @@ export function createContextAgentSession() {
|
|
|
531
531
|
export function createIntentRouterSession() {
|
|
532
532
|
let model = getGlobalActiveModel();
|
|
533
533
|
if (model === 'auto')
|
|
534
|
-
model = GEMINI_MODELS.
|
|
534
|
+
model = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
535
535
|
return new ProxyChatSession(model, INTENT_ROUTER_SYSTEM_INSTRUCTION, [], // no tools
|
|
536
536
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
537
537
|
}
|
|
@@ -539,7 +539,7 @@ export function createIntentRouterSession() {
|
|
|
539
539
|
export function createExecutionComplexitySession() {
|
|
540
540
|
let model = getGlobalActiveModel();
|
|
541
541
|
if (model === 'auto')
|
|
542
|
-
model = GEMINI_MODELS.
|
|
542
|
+
model = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
543
543
|
return new ProxyChatSession(model, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
544
544
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
545
545
|
}
|
|
@@ -547,7 +547,7 @@ export function createExecutionComplexitySession() {
|
|
|
547
547
|
export function createInvestigationComplexitySession() {
|
|
548
548
|
let model = getGlobalActiveModel();
|
|
549
549
|
if (model === 'auto')
|
|
550
|
-
model = GEMINI_MODELS.
|
|
550
|
+
model = GEMINI_MODELS.FLASH_LITE_3_1;
|
|
551
551
|
return new ProxyChatSession(model, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
552
552
|
{ temperature: 0, responseMimeType: 'application/json' });
|
|
553
553
|
}
|
|
@@ -1,24 +1,79 @@
|
|
|
1
1
|
import type { Content } from '@google/generative-ai';
|
|
2
|
+
/**
|
|
3
|
+
* Represents the structure of a saved chat session.
|
|
4
|
+
* This data is persisted in the local workspace cache to allow users to resume
|
|
5
|
+
* previous conversations with the AI agent.
|
|
6
|
+
*/
|
|
2
7
|
export interface ChatSessionData {
|
|
8
|
+
/** Unique identifier for the chat session, typically a UUID or timestamp-based string. */
|
|
3
9
|
id: string;
|
|
10
|
+
/** User-friendly title or summary of the chat session. */
|
|
4
11
|
title: string;
|
|
12
|
+
/** Epoch timestamp (in milliseconds) indicating when the session was last updated or created. */
|
|
5
13
|
timestamp: number;
|
|
14
|
+
/** The full conversation history, structured as an array of Content objects compatible with the Gemini API. */
|
|
6
15
|
history: Content[];
|
|
16
|
+
/** Optional number of remaining credits for the user's account at the time of the session update. */
|
|
7
17
|
creditsRemaining?: number;
|
|
18
|
+
/** Optional human-readable duration of the last turn (e.g., "1.2s"). */
|
|
8
19
|
lastTurnDuration?: string;
|
|
20
|
+
/** Optional name of the AI model used during this session (e.g., "gemini-1.5-pro"). */
|
|
9
21
|
modelName?: string;
|
|
22
|
+
/** Optional total cumulative tokens consumed during the session. */
|
|
10
23
|
totalTokens?: number;
|
|
24
|
+
/** Optional name of the Git branch active when this session was last updated. */
|
|
11
25
|
gitBranch?: string;
|
|
26
|
+
/** Optional flag indicating whether auto-approval of commands was enabled in this session. */
|
|
12
27
|
autoApprove?: boolean;
|
|
28
|
+
/** Optional flag indicating whether sub-agents were enabled or active during this session. */
|
|
13
29
|
subAgents?: boolean;
|
|
14
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Service responsible for managing the persistence, retrieval, and deletion of chat session histories.
|
|
33
|
+
* It stores session metadata and message history in a local JSON cache (`chat_sessions.json`)
|
|
34
|
+
* within the project's storage directory and enforces a maximum limit of 250 sessions to prevent
|
|
35
|
+
* unbounded storage growth.
|
|
36
|
+
*/
|
|
15
37
|
declare class ChatHistoryService {
|
|
38
|
+
/** The root directory of the currently active workspace. Used to locate the project storage and cache files. */
|
|
16
39
|
private workspaceRoot;
|
|
40
|
+
/** The maximum number of chat sessions allowed in the cache. Oldest sessions are discarded when this limit is exceeded. */
|
|
17
41
|
private readonly MAX_SESSIONS;
|
|
42
|
+
/**
|
|
43
|
+
* Initializes the chat history service with the workspace root path.
|
|
44
|
+
* This must be called before attempting to read, save, or delete sessions.
|
|
45
|
+
*
|
|
46
|
+
* @param workspaceRoot - The absolute path to the workspace root directory.
|
|
47
|
+
*/
|
|
18
48
|
init(workspaceRoot: string): void;
|
|
49
|
+
/**
|
|
50
|
+
* Retrieves all saved chat sessions from the local workspace cache.
|
|
51
|
+
* If the service is not initialized or no sessions exist, an empty array is returned.
|
|
52
|
+
*
|
|
53
|
+
* @returns An array of saved `ChatSessionData` objects, ordered as stored in the cache.
|
|
54
|
+
*/
|
|
19
55
|
getSessions(): ChatSessionData[];
|
|
56
|
+
/**
|
|
57
|
+
* Saves or updates a chat session in the local workspace cache.
|
|
58
|
+
* If a session with the same ID already exists, it is updated in place. Otherwise, it is appended.
|
|
59
|
+
* Enforces the maximum session limit of 250 by removing the oldest session if the limit is exceeded.
|
|
60
|
+
*
|
|
61
|
+
* @param session - The chat session data to be saved.
|
|
62
|
+
* @returns A promise that resolves when the session has been successfully written to the cache.
|
|
63
|
+
*/
|
|
20
64
|
saveSession(session: ChatSessionData): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Deletes a chat session from the local workspace cache and removes its associated archived history file.
|
|
67
|
+
* If the session or its archived history file does not exist, the operation completes without throwing an error.
|
|
68
|
+
*
|
|
69
|
+
* @param id - The unique identifier of the chat session to delete.
|
|
70
|
+
* @returns A promise that resolves when the session and its associated archive have been deleted.
|
|
71
|
+
*/
|
|
21
72
|
deleteSession(id: string): Promise<void>;
|
|
22
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Singleton instance of the `ChatHistoryService` exported for application-wide use.
|
|
76
|
+
* This instance must be initialized with `init(workspaceRoot)` before use.
|
|
77
|
+
*/
|
|
23
78
|
export declare const chatHistoryService: ChatHistoryService;
|
|
24
79
|
export {};
|
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
import { readCache, writeCache } from '../utils/projectStorage.js';
|
|
2
|
+
/**
|
|
3
|
+
* Service responsible for managing the persistence, retrieval, and deletion of chat session histories.
|
|
4
|
+
* It stores session metadata and message history in a local JSON cache (`chat_sessions.json`)
|
|
5
|
+
* within the project's storage directory and enforces a maximum limit of 250 sessions to prevent
|
|
6
|
+
* unbounded storage growth.
|
|
7
|
+
*/
|
|
2
8
|
class ChatHistoryService {
|
|
9
|
+
/** The root directory of the currently active workspace. Used to locate the project storage and cache files. */
|
|
3
10
|
workspaceRoot = '';
|
|
11
|
+
/** The maximum number of chat sessions allowed in the cache. Oldest sessions are discarded when this limit is exceeded. */
|
|
4
12
|
MAX_SESSIONS = 250;
|
|
13
|
+
/**
|
|
14
|
+
* Initializes the chat history service with the workspace root path.
|
|
15
|
+
* This must be called before attempting to read, save, or delete sessions.
|
|
16
|
+
*
|
|
17
|
+
* @param workspaceRoot - The absolute path to the workspace root directory.
|
|
18
|
+
*/
|
|
5
19
|
init(workspaceRoot) {
|
|
6
20
|
this.workspaceRoot = workspaceRoot;
|
|
7
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves all saved chat sessions from the local workspace cache.
|
|
24
|
+
* If the service is not initialized or no sessions exist, an empty array is returned.
|
|
25
|
+
*
|
|
26
|
+
* @returns An array of saved `ChatSessionData` objects, ordered as stored in the cache.
|
|
27
|
+
*/
|
|
8
28
|
getSessions() {
|
|
9
29
|
if (!this.workspaceRoot)
|
|
10
30
|
return [];
|
|
11
31
|
const sessions = readCache(this.workspaceRoot, 'chat_sessions.json');
|
|
12
32
|
return sessions || [];
|
|
13
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Saves or updates a chat session in the local workspace cache.
|
|
36
|
+
* If a session with the same ID already exists, it is updated in place. Otherwise, it is appended.
|
|
37
|
+
* Enforces the maximum session limit of 250 by removing the oldest session if the limit is exceeded.
|
|
38
|
+
*
|
|
39
|
+
* @param session - The chat session data to be saved.
|
|
40
|
+
* @returns A promise that resolves when the session has been successfully written to the cache.
|
|
41
|
+
*/
|
|
14
42
|
async saveSession(session) {
|
|
15
43
|
if (!this.workspaceRoot)
|
|
16
44
|
return;
|
|
@@ -28,6 +56,13 @@ class ChatHistoryService {
|
|
|
28
56
|
}
|
|
29
57
|
await writeCache(this.workspaceRoot, 'chat_sessions.json', sessions);
|
|
30
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Deletes a chat session from the local workspace cache and removes its associated archived history file.
|
|
61
|
+
* If the session or its archived history file does not exist, the operation completes without throwing an error.
|
|
62
|
+
*
|
|
63
|
+
* @param id - The unique identifier of the chat session to delete.
|
|
64
|
+
* @returns A promise that resolves when the session and its associated archive have been deleted.
|
|
65
|
+
*/
|
|
31
66
|
async deleteSession(id) {
|
|
32
67
|
if (!this.workspaceRoot)
|
|
33
68
|
return;
|
|
@@ -48,4 +83,8 @@ class ChatHistoryService {
|
|
|
48
83
|
}
|
|
49
84
|
}
|
|
50
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Singleton instance of the `ChatHistoryService` exported for application-wide use.
|
|
88
|
+
* This instance must be initialized with `init(workspaceRoot)` before use.
|
|
89
|
+
*/
|
|
51
90
|
export const chatHistoryService = new ChatHistoryService();
|
|
@@ -31,7 +31,7 @@ export interface InvestigationComplexityResult {
|
|
|
31
31
|
/**
|
|
32
32
|
* Evaluates whether the investigation phase should be parallelized.
|
|
33
33
|
*
|
|
34
|
-
* Uses `gemini-
|
|
34
|
+
* Uses `gemini-3.1-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-
|
|
19
|
+
* Uses `gemini-3.1-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.
|
|
@@ -32,6 +32,8 @@ export interface InvestigationResult {
|
|
|
32
32
|
webSearchSummary: string;
|
|
33
33
|
/** Total tokens consumed by this agent */
|
|
34
34
|
creditsUsed: number;
|
|
35
|
+
inputTokens?: number;
|
|
36
|
+
outputTokens?: number;
|
|
35
37
|
/** Whether the agent completed successfully */
|
|
36
38
|
success: boolean;
|
|
37
39
|
}
|
|
@@ -55,6 +57,8 @@ export declare class InvestigationAgentRunner {
|
|
|
55
57
|
private chat;
|
|
56
58
|
private lastHeartbeat;
|
|
57
59
|
private creditsUsed;
|
|
60
|
+
private inputTokens;
|
|
61
|
+
private outputTokens;
|
|
58
62
|
/** Shorter stall timeout for investigation agents (120s to allow for heavy vision/web tasks) */
|
|
59
63
|
static readonly STALL_TIMEOUT_MS = 120000;
|
|
60
64
|
constructor(agentLabel: string, domains: string[], workspaceRoot: string, readCache: ReadCache, projectTree: string, projectType: string);
|
|
@@ -38,6 +38,8 @@ export class InvestigationAgentRunner {
|
|
|
38
38
|
chat;
|
|
39
39
|
lastHeartbeat = Date.now();
|
|
40
40
|
creditsUsed = 0;
|
|
41
|
+
inputTokens = 0;
|
|
42
|
+
outputTokens = 0;
|
|
41
43
|
/** Shorter stall timeout for investigation agents (120s to allow for heavy vision/web tasks) */
|
|
42
44
|
static STALL_TIMEOUT_MS = 120_000;
|
|
43
45
|
constructor(agentLabel, domains, workspaceRoot, readCache, projectTree, projectType) {
|
|
@@ -322,7 +324,7 @@ export class InvestigationAgentRunner {
|
|
|
322
324
|
crashed = true;
|
|
323
325
|
summary = 'Investigation aborted by user.';
|
|
324
326
|
}
|
|
325
|
-
debugLog(`InvestigationAgent [${this.agentLabel}]: Finished. Success=${success && !crashed}, Files=${relevantFiles.size}, Tokens=${this.creditsUsed}`);
|
|
327
|
+
debugLog(`InvestigationAgent [${this.agentLabel}]: Finished. Success=${success && !crashed}, Files=${relevantFiles.size}, Tokens=${this.creditsUsed} (In: ${this.inputTokens}, Out: ${this.outputTokens})`);
|
|
326
328
|
return {
|
|
327
329
|
domains: this.domains,
|
|
328
330
|
agentLabel: this.agentLabel,
|
|
@@ -330,6 +332,8 @@ export class InvestigationAgentRunner {
|
|
|
330
332
|
summary,
|
|
331
333
|
webSearchSummary,
|
|
332
334
|
creditsUsed: this.creditsUsed,
|
|
335
|
+
inputTokens: this.inputTokens,
|
|
336
|
+
outputTokens: this.outputTokens,
|
|
333
337
|
success: success && !crashed,
|
|
334
338
|
};
|
|
335
339
|
}
|
|
@@ -339,10 +343,12 @@ export class InvestigationAgentRunner {
|
|
|
339
343
|
updateUsage(result) {
|
|
340
344
|
const usage = result.response.usageMetadata?.();
|
|
341
345
|
if (usage) {
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
+
const input = (usage.promptTokens || 0) + (usage.cachedTokens || 0);
|
|
347
|
+
const output = usage.candidatesTokens || 0;
|
|
348
|
+
const tokens = usage.totalTokenCount || (input + output);
|
|
349
|
+
this.inputTokens += input;
|
|
350
|
+
this.outputTokens += output;
|
|
351
|
+
this.creditsUsed += tokens;
|
|
346
352
|
}
|
|
347
353
|
}
|
|
348
354
|
}
|
|
@@ -98,12 +98,14 @@ export class InvestigationOrchestrator {
|
|
|
98
98
|
// 7. Log summary
|
|
99
99
|
const cacheStats = readCache.getStats();
|
|
100
100
|
const totalTokens = results.reduce((sum, r) => sum + r.creditsUsed, 0);
|
|
101
|
+
const totalInputTokens = results.reduce((sum, r) => sum + (r.inputTokens || 0), 0);
|
|
102
|
+
const totalOutputTokens = results.reduce((sum, r) => sum + (r.outputTokens || 0), 0);
|
|
101
103
|
const totalFilesBeforeDedup = results.reduce((sum, r) => sum + r.relevantFiles.size, 0);
|
|
102
104
|
p.log.info(`${pc.green('✓')} Parallel Investigation complete.\n` +
|
|
103
105
|
` Agents: ${agentCount} (${failedResults.length} failed) | ` +
|
|
104
106
|
`Files: ${mergedResult.relevantFiles.size} (deduped from ${totalFilesBeforeDedup}) | ` +
|
|
105
107
|
`Cache hits: ${cacheStats.hitCount}\n` +
|
|
106
|
-
` Duration: ${duration}s | Tokens: ${totalTokens.toLocaleString()}`);
|
|
108
|
+
` Duration: ${duration}s | Tokens: ${totalTokens.toLocaleString()} ${pc.dim(`(Input: ${totalInputTokens.toLocaleString()}, Output: ${totalOutputTokens.toLocaleString()})`)}`);
|
|
107
109
|
return mergedResult;
|
|
108
110
|
}
|
|
109
111
|
/**
|
|
@@ -226,10 +226,14 @@ export class Orchestrator {
|
|
|
226
226
|
p.log.step(pc.cyan('Orchestrator: Reconciling results...'));
|
|
227
227
|
const stats = this.bus.getStats();
|
|
228
228
|
let totalTokens = 0;
|
|
229
|
+
let inputTokens = 0;
|
|
230
|
+
let outputTokens = 0;
|
|
229
231
|
let failedTasks = 0;
|
|
230
232
|
let rawSummaryData = '';
|
|
231
233
|
for (const [taskId, res] of this.agentResults.entries()) {
|
|
232
234
|
totalTokens += res.creditsUsed;
|
|
235
|
+
inputTokens += (res.inputTokens || 0);
|
|
236
|
+
outputTokens += (res.outputTokens || 0);
|
|
233
237
|
if (!res.success)
|
|
234
238
|
failedTasks++;
|
|
235
239
|
debugLog(`Task ${taskId} summary: ${res.summary.substring(0, 100)}...`);
|
|
@@ -249,7 +253,7 @@ export class Orchestrator {
|
|
|
249
253
|
p.log.info(`${pc.green('✓')} Sub-agent execution complete.\n` +
|
|
250
254
|
` Total tasks: ${graph.tasks.length} (${failedTasks} failed)\n` +
|
|
251
255
|
` Bus Activity: ${stats.activityCount} actions, ${stats.signalCount} signals\n` +
|
|
252
|
-
` Total Tokens: ${totalTokens.toLocaleString()}`);
|
|
256
|
+
` Total Tokens: ${totalTokens.toLocaleString()} ${pc.dim(`(Input: ${inputTokens.toLocaleString()}, Output: ${outputTokens.toLocaleString()})`)}`);
|
|
253
257
|
// Clear the bus persistence now that orchestration is done
|
|
254
258
|
await this.bus.cleanup();
|
|
255
259
|
this.locks.shutdown();
|
|
@@ -16,6 +16,8 @@ export interface SubAgentResult {
|
|
|
16
16
|
summary: string;
|
|
17
17
|
/** Total credits (tokens) consumed by this agent */
|
|
18
18
|
creditsUsed: number;
|
|
19
|
+
inputTokens?: number;
|
|
20
|
+
outputTokens?: number;
|
|
19
21
|
/** Did the agent hit a stall timeout or crash? */
|
|
20
22
|
crashed: boolean;
|
|
21
23
|
}
|
|
@@ -34,6 +36,8 @@ export declare class SubAgentRunner {
|
|
|
34
36
|
private chat;
|
|
35
37
|
private lastHeartbeat;
|
|
36
38
|
private creditsUsed;
|
|
39
|
+
private inputTokens;
|
|
40
|
+
private outputTokens;
|
|
37
41
|
/** Max time without a tool call or response before the agent is considered stalled */
|
|
38
42
|
static readonly STALL_TIMEOUT_MS = 60000;
|
|
39
43
|
constructor(taskId: string, intent: string, workspaceRoot: string, bus: MessageBus, locks: FileLockRegistry, globalContext: string, onProgress?: ((msg: string) => void) | undefined);
|
|
@@ -24,6 +24,8 @@ export class SubAgentRunner {
|
|
|
24
24
|
chat;
|
|
25
25
|
lastHeartbeat = Date.now();
|
|
26
26
|
creditsUsed = 0;
|
|
27
|
+
inputTokens = 0;
|
|
28
|
+
outputTokens = 0;
|
|
27
29
|
/** Max time without a tool call or response before the agent is considered stalled */
|
|
28
30
|
static STALL_TIMEOUT_MS = 60_000;
|
|
29
31
|
constructor(taskId, intent, workspaceRoot, bus, locks, globalContext, onProgress) {
|
|
@@ -180,11 +182,13 @@ export class SubAgentRunner {
|
|
|
180
182
|
crashed = true;
|
|
181
183
|
finalSummary = 'Aborted by orchestrator.';
|
|
182
184
|
}
|
|
183
|
-
debugLog(`SubAgent [${this.taskId}]: Finished. Success=${success}, Crashed=${crashed}, Tokens=${this.creditsUsed}`);
|
|
185
|
+
debugLog(`SubAgent [${this.taskId}]: Finished. Success=${success}, Crashed=${crashed}, Tokens=${this.creditsUsed} (In: ${this.inputTokens}, Out: ${this.outputTokens})`);
|
|
184
186
|
return {
|
|
185
187
|
success: success && !crashed,
|
|
186
188
|
summary: finalSummary,
|
|
187
189
|
creditsUsed: this.creditsUsed,
|
|
190
|
+
inputTokens: this.inputTokens,
|
|
191
|
+
outputTokens: this.outputTokens,
|
|
188
192
|
crashed,
|
|
189
193
|
};
|
|
190
194
|
});
|
|
@@ -195,10 +199,12 @@ export class SubAgentRunner {
|
|
|
195
199
|
updateUsage(result) {
|
|
196
200
|
const usage = result.response.usageMetadata?.();
|
|
197
201
|
if (usage) {
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
const input = (usage.promptTokens || 0) + (usage.cachedTokens || 0);
|
|
203
|
+
const output = usage.candidatesTokens || 0;
|
|
204
|
+
const tokens = usage.totalTokenCount || (input + output);
|
|
205
|
+
this.inputTokens += input;
|
|
206
|
+
this.outputTokens += output;
|
|
207
|
+
this.creditsUsed += tokens;
|
|
202
208
|
}
|
|
203
209
|
}
|
|
204
210
|
}
|
|
@@ -1 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synchronizes local workspace metadata with the remote Firestore database.
|
|
3
|
+
* This function tracks user project activity by updating a "last used" timestamp
|
|
4
|
+
* and project details in Firestore whenever a workspace is accessed.
|
|
5
|
+
*
|
|
6
|
+
* ### Internal Logic & Flow:
|
|
7
|
+
* 1. **JWT Decoding**:
|
|
8
|
+
* - Splits the provided `idToken` (JWT) to extract the payload segment (second part).
|
|
9
|
+
* - Decodes the payload from Base64 to a UTF-8 string and parses it as JSON.
|
|
10
|
+
* - Extracts the `user_id` (UID) representing the authenticated user.
|
|
11
|
+
* - If the token is invalid or does not contain a `user_id`, the function exits early.
|
|
12
|
+
*
|
|
13
|
+
* 2. **ID Generation**:
|
|
14
|
+
* - Encodes the absolute `workspaceRoot` path into a Base64 string.
|
|
15
|
+
* - Transforms the Base64 string to be URL-safe by replacing `/` with `_`, `+` with `-`, and removing `=` padding.
|
|
16
|
+
* - This URL-safe string serves as a unique, deterministic document identifier for the workspace in Firestore.
|
|
17
|
+
*
|
|
18
|
+
* 3. **Firestore Integration**:
|
|
19
|
+
* - Constructs a `PATCH` request URL targeting the user's specific project document in Firestore.
|
|
20
|
+
* - Prepares a payload containing:
|
|
21
|
+
* - `name`: The base name of the workspace directory.
|
|
22
|
+
* - `path`: The absolute path to the workspace.
|
|
23
|
+
* - `workspacePath`: The absolute path to the workspace.
|
|
24
|
+
* - `lastUsed`: An ISO 8601 timestamp representing the current time.
|
|
25
|
+
* - Sends the payload via a `PATCH` request to the Firestore REST API, passing the `idToken` in the `Authorization` header.
|
|
26
|
+
*
|
|
27
|
+
* 4. **Error Handling & Logging**:
|
|
28
|
+
* - If the Firestore REST API returns a non-OK status, the response body is read and logged using `debugLog`.
|
|
29
|
+
* - Any thrown exceptions (such as network failures or JSON parsing errors) are caught and logged using `debugLog`.
|
|
30
|
+
*
|
|
31
|
+
* @param idToken - The Firebase ID token (JWT) of the authenticated user.
|
|
32
|
+
* @param workspaceRoot - The absolute file system path of the workspace root.
|
|
33
|
+
* @returns A promise that resolves when the update attempt is complete.
|
|
34
|
+
*/
|
|
1
35
|
export declare function updateWorkspaceStatus(idToken: string, workspaceRoot: string): Promise<void>;
|
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import * as path from 'path';
|
|
2
2
|
import { debugLog } from '../utils/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Synchronizes local workspace metadata with the remote Firestore database.
|
|
5
|
+
* This function tracks user project activity by updating a "last used" timestamp
|
|
6
|
+
* and project details in Firestore whenever a workspace is accessed.
|
|
7
|
+
*
|
|
8
|
+
* ### Internal Logic & Flow:
|
|
9
|
+
* 1. **JWT Decoding**:
|
|
10
|
+
* - Splits the provided `idToken` (JWT) to extract the payload segment (second part).
|
|
11
|
+
* - Decodes the payload from Base64 to a UTF-8 string and parses it as JSON.
|
|
12
|
+
* - Extracts the `user_id` (UID) representing the authenticated user.
|
|
13
|
+
* - If the token is invalid or does not contain a `user_id`, the function exits early.
|
|
14
|
+
*
|
|
15
|
+
* 2. **ID Generation**:
|
|
16
|
+
* - Encodes the absolute `workspaceRoot` path into a Base64 string.
|
|
17
|
+
* - Transforms the Base64 string to be URL-safe by replacing `/` with `_`, `+` with `-`, and removing `=` padding.
|
|
18
|
+
* - This URL-safe string serves as a unique, deterministic document identifier for the workspace in Firestore.
|
|
19
|
+
*
|
|
20
|
+
* 3. **Firestore Integration**:
|
|
21
|
+
* - Constructs a `PATCH` request URL targeting the user's specific project document in Firestore.
|
|
22
|
+
* - Prepares a payload containing:
|
|
23
|
+
* - `name`: The base name of the workspace directory.
|
|
24
|
+
* - `path`: The absolute path to the workspace.
|
|
25
|
+
* - `workspacePath`: The absolute path to the workspace.
|
|
26
|
+
* - `lastUsed`: An ISO 8601 timestamp representing the current time.
|
|
27
|
+
* - Sends the payload via a `PATCH` request to the Firestore REST API, passing the `idToken` in the `Authorization` header.
|
|
28
|
+
*
|
|
29
|
+
* 4. **Error Handling & Logging**:
|
|
30
|
+
* - If the Firestore REST API returns a non-OK status, the response body is read and logged using `debugLog`.
|
|
31
|
+
* - Any thrown exceptions (such as network failures or JSON parsing errors) are caught and logged using `debugLog`.
|
|
32
|
+
*
|
|
33
|
+
* @param idToken - The Firebase ID token (JWT) of the authenticated user.
|
|
34
|
+
* @param workspaceRoot - The absolute file system path of the workspace root.
|
|
35
|
+
* @returns A promise that resolves when the update attempt is complete.
|
|
36
|
+
*/
|
|
3
37
|
export async function updateWorkspaceStatus(idToken, workspaceRoot) {
|
|
4
38
|
try {
|
|
5
39
|
// 1. Decode JWT to get user_id (uid)
|
|
@@ -14,7 +48,8 @@ export async function updateWorkspaceStatus(idToken, workspaceRoot) {
|
|
|
14
48
|
// 2. Generate a URL-safe ID for the workspace based on its path
|
|
15
49
|
const workspaceId = Buffer.from(workspaceRoot)
|
|
16
50
|
.toString('base64')
|
|
17
|
-
|
|
51
|
+
// eslint-disable-next-line prefer-regex-literals
|
|
52
|
+
.replace(new RegExp('/', 'g'), '_')
|
|
18
53
|
.replace(/\+/g, '-')
|
|
19
54
|
.replace(/=/g, '');
|
|
20
55
|
// 3. Prepare the Firestore REST API payload
|
package/dist/utils/config.d.ts
CHANGED
|
@@ -16,7 +16,6 @@ export declare const GITHUB_CLIENT_ID = "Ov23linFYFfjO3JILG7r";
|
|
|
16
16
|
export declare const GEMINI_MODELS: {
|
|
17
17
|
readonly PRO_3_1: "gemini-3.1-pro-preview";
|
|
18
18
|
readonly FLASH_3_5: "gemini-3.5-flash";
|
|
19
|
-
readonly FLASH_2_5: "gemini-2.5-flash";
|
|
20
19
|
readonly FLASH_LITE_3_1: "gemini-3.1-flash-lite";
|
|
21
20
|
readonly AUTO: "auto";
|
|
22
21
|
};
|
package/dist/utils/config.js
CHANGED
|
@@ -16,7 +16,6 @@ export const GITHUB_CLIENT_ID = 'Ov23linFYFfjO3JILG7r';
|
|
|
16
16
|
export const GEMINI_MODELS = {
|
|
17
17
|
PRO_3_1: 'gemini-3.1-pro-preview',
|
|
18
18
|
FLASH_3_5: 'gemini-3.5-flash',
|
|
19
|
-
FLASH_2_5: 'gemini-2.5-flash',
|
|
20
19
|
FLASH_LITE_3_1: 'gemini-3.1-flash-lite',
|
|
21
20
|
AUTO: 'auto',
|
|
22
21
|
};
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED