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 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 2.5 Flash** | Cost-efficient reasoning and multimodal tasks |
94
- | **Gemini 3.1 Pro** | Complex architectural changes |
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-2.5-flash',
82
- label: 'Gemini 2.5 Flash',
83
- hint: 'Google Gemini 2.5 Flash model with built-in reasoning',
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 (Gemini 2.5 / Gemini 3.5)',
88
- hint: 'Dynamically routes between Gemini 2.5 and Gemini 3.5 based on prompt complexity',
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
  });
@@ -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.FLASH_2_5;
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.FLASH_2_5 : GEMINI_MODELS.FLASH_3_5;
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 = (latestUsage.promptTokens || 0) + (latestUsage.cachedTokens || 0) + (latestUsage.candidatesTokens || 0);
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 (Gemini 2.5 / Gemini 3.5)',
599
- hint: 'Dynamically routes between Gemini 2.5 and Gemini 3.5 based on prompt complexity',
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
 
@@ -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.FLASH_2_5;
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.FLASH_2_5;
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.FLASH_2_5;
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.FLASH_2_5;
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-2.5-flash` (under auto mode) at temperature 0 to classify the prompt's
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-2.5-flash` (under auto mode) at temperature 0 to classify the prompt's
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 tokens = usage.totalTokenCount || ((usage.promptTokens || 0) + (usage.candidatesTokens || 0) + (usage.cachedTokens || 0));
343
- if (tokens) {
344
- this.creditsUsed += tokens;
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 tokens = usage.totalTokenCount || ((usage.promptTokens || 0) + (usage.candidatesTokens || 0) + (usage.cachedTokens || 0));
199
- if (tokens) {
200
- this.creditsUsed += tokens;
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
- .replace(/\//g, '_')
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
@@ -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
  };
@@ -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
  };
@@ -65,5 +65,5 @@
65
65
  ]
66
66
  }
67
67
  },
68
- "version": "2.2.3"
68
+ "version": "2.2.5"
69
69
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "minovative-mind-cli",
3
3
  "description": "An automated AI agent powered by Vertex AI that helps you write software",
4
- "version": "2.2.3",
4
+ "version": "2.2.5",
5
5
  "author": "Daniel Ward",
6
6
  "bin": {
7
7
  "minovative-mind-cli": "bin/run.js"