minovative-mind-cli 2.2.4 → 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.
Files changed (36) hide show
  1. package/LICENSE.md +2 -0
  2. package/README.md +19 -20
  3. package/dist/commands/logout.js +1 -1
  4. package/dist/services/agent/slashCommands.js +54 -15
  5. package/dist/services/agent/toolLoop.js +4 -1
  6. package/dist/services/agent/types.d.ts +4 -0
  7. package/dist/services/agent-tools.js +55 -27
  8. package/dist/services/agent.d.ts +4 -0
  9. package/dist/services/agent.js +79 -62
  10. package/dist/services/ai.d.ts +1 -2
  11. package/dist/services/ai.js +21 -19
  12. package/dist/services/auth.d.ts +1 -1
  13. package/dist/services/auth.js +7 -28
  14. package/dist/services/chatHistoryService.d.ts +8 -0
  15. package/dist/services/contextAgent.js +18 -1
  16. package/dist/services/investigationComplexity.d.ts +1 -1
  17. package/dist/services/investigationComplexity.js +1 -1
  18. package/dist/services/orchestration/investigationAgent.d.ts +6 -2
  19. package/dist/services/orchestration/investigationAgent.js +35 -10
  20. package/dist/services/orchestration/investigationOrchestrator.js +3 -1
  21. package/dist/services/orchestration/orchestrator.d.ts +1 -1
  22. package/dist/services/orchestration/orchestrator.js +39 -16
  23. package/dist/services/orchestration/subAgent.d.ts +5 -1
  24. package/dist/services/orchestration/subAgent.js +33 -17
  25. package/dist/services/proxyClient.d.ts +16 -0
  26. package/dist/services/proxyClient.js +32 -0
  27. package/dist/utils/analysisRunner.js +2 -2
  28. package/dist/utils/config.d.ts +3 -4
  29. package/dist/utils/config.js +3 -4
  30. package/dist/utils/credentialStore.d.ts +30 -0
  31. package/dist/utils/credentialStore.js +540 -0
  32. package/dist/utils/projectStorage.js +70 -0
  33. package/dist/utils/systemPrompts.d.ts +3 -3
  34. package/dist/utils/systemPrompts.js +4 -4
  35. package/oclif.manifest.json +1 -1
  36. package/package.json +2 -1
@@ -38,8 +38,10 @@ export class InvestigationAgentRunner {
38
38
  chat;
39
39
  lastHeartbeat = Date.now();
40
40
  creditsUsed = 0;
41
- /** Shorter stall timeout for investigation agents (120s to allow for heavy vision/web tasks) */
42
- static STALL_TIMEOUT_MS = 120_000;
41
+ inputTokens = 0;
42
+ outputTokens = 0;
43
+ /** Stall timeout for investigation agents (300s to allow for heavy vision/web tasks) */
44
+ static STALL_TIMEOUT_MS = 300_000;
43
45
  constructor(agentLabel, domains, workspaceRoot, readCache, projectTree, projectType) {
44
46
  this.agentLabel = agentLabel;
45
47
  this.domains = domains;
@@ -49,7 +51,7 @@ export class InvestigationAgentRunner {
49
51
  this.projectType = projectType;
50
52
  let model = getGlobalActiveModel();
51
53
  if (model === GEMINI_MODELS.AUTO)
52
- model = GEMINI_MODELS.FLASH_3_5;
54
+ model = GEMINI_MODELS.FLASH;
53
55
  this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), getContextToolDeclarations(), {
54
56
  maxOutputTokens: MAX_OUTPUT_TOKENS,
55
57
  temperature: 1,
@@ -112,6 +114,8 @@ export class InvestigationAgentRunner {
112
114
  currentMessage += `\n\nStart investigating to find relevant files within your assigned domains.`;
113
115
  let isFinished = false;
114
116
  const visitedToolCalls = new Set();
117
+ let consecutiveDuplicates = 0;
118
+ const MAX_CONSECUTIVE_DUPLICATES = 3;
115
119
  // Tool loop — mirrors the existing context agent loop in contextAgent.ts
116
120
  while (!crashed && !abortSignal.aborted && !isFinished) {
117
121
  this.pingHeartbeat();
@@ -142,14 +146,31 @@ export class InvestigationAgentRunner {
142
146
  // Deduplicate identical tool calls
143
147
  const callSignature = `${call.name}:${JSON.stringify(args)}`;
144
148
  if (call.name !== 'finish_investigation' && visitedToolCalls.has(callSignature)) {
149
+ consecutiveDuplicates++;
150
+ if (consecutiveDuplicates >= MAX_CONSECUTIVE_DUPLICATES) {
151
+ // Force-finish: the model is stuck in a loop
152
+ const forceMsg = `Investigation auto-completed: the model repeated the same tool call ${MAX_CONSECUTIVE_DUPLICATES} times consecutively.`;
153
+ debugLog(`InvestigationAgent [${this.agentLabel}]: ${forceMsg}`);
154
+ if (onProgress)
155
+ onProgress(`[${this.agentLabel}] ${forceMsg}`);
156
+ if (!summary || summary === 'No relevant context found.') {
157
+ summary = 'Investigation was auto-completed due to repeated duplicate tool calls. Review the gathered files for context.';
158
+ }
159
+ isFinished = true;
160
+ success = relevantFiles.size > 0;
161
+ break;
162
+ }
145
163
  functionResponses.push({
146
164
  functionResponse: {
147
165
  name: call.name,
148
- response: { error: 'You have already made this exact tool call previously. Please review your context history or try a different action.' }
149
- }
166
+ response: {
167
+ 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.`,
168
+ },
169
+ },
150
170
  });
151
171
  continue;
152
172
  }
173
+ consecutiveDuplicates = 0;
153
174
  visitedToolCalls.add(callSignature);
154
175
  if (call.name === 'finish_investigation') {
155
176
  summary = args.summary || '';
@@ -322,7 +343,7 @@ export class InvestigationAgentRunner {
322
343
  crashed = true;
323
344
  summary = 'Investigation aborted by user.';
324
345
  }
325
- debugLog(`InvestigationAgent [${this.agentLabel}]: Finished. Success=${success && !crashed}, Files=${relevantFiles.size}, Tokens=${this.creditsUsed}`);
346
+ debugLog(`InvestigationAgent [${this.agentLabel}]: Finished. Success=${success && !crashed}, Files=${relevantFiles.size}, Tokens=${this.creditsUsed} (In: ${this.inputTokens}, Out: ${this.outputTokens})`);
326
347
  return {
327
348
  domains: this.domains,
328
349
  agentLabel: this.agentLabel,
@@ -330,6 +351,8 @@ export class InvestigationAgentRunner {
330
351
  summary,
331
352
  webSearchSummary,
332
353
  creditsUsed: this.creditsUsed,
354
+ inputTokens: this.inputTokens,
355
+ outputTokens: this.outputTokens,
333
356
  success: success && !crashed,
334
357
  };
335
358
  }
@@ -339,10 +362,12 @@ export class InvestigationAgentRunner {
339
362
  updateUsage(result) {
340
363
  const usage = result.response.usageMetadata?.();
341
364
  if (usage) {
342
- const tokens = usage.totalTokenCount || ((usage.promptTokens || 0) + (usage.candidatesTokens || 0) + (usage.cachedTokens || 0));
343
- if (tokens) {
344
- this.creditsUsed += tokens;
345
- }
365
+ const input = (usage.promptTokens || 0) + (usage.cachedTokens || 0);
366
+ const output = usage.candidatesTokens || 0;
367
+ const tokens = usage.totalTokenCount || input + output;
368
+ this.inputTokens += input;
369
+ this.outputTokens += output;
370
+ this.creditsUsed += tokens;
346
371
  }
347
372
  }
348
373
  }
@@ -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
  /**
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Main Orchestrator for Sub-Agent Dispatch and Coordination.
3
3
  *
4
4
  * The orchestrator acts as the "PM Kernel", responsible for:
5
- * 1. Task Decomposition (using gemini-3.5-flash)
5
+ * 1. Task Decomposition (using gemini-3.6-flash)
6
6
  * 2. Graph Validation (Cycle detection via Kahn's algorithm)
7
7
  * 3. Lock Ordering (Conflict resolution across parallel waves)
8
8
  * 4. Parallel Dispatch (Executing waves sequentially, agents in parallel)
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Main Orchestrator for Sub-Agent Dispatch and Coordination.
3
3
  *
4
4
  * The orchestrator acts as the "PM Kernel", responsible for:
5
- * 1. Task Decomposition (using gemini-3.5-flash)
5
+ * 1. Task Decomposition (using gemini-3.6-flash)
6
6
  * 2. Graph Validation (Cycle detection via Kahn's algorithm)
7
7
  * 3. Lock Ordering (Conflict resolution across parallel waves)
8
8
  * 4. Parallel Dispatch (Executing waves sequentially, agents in parallel)
@@ -11,6 +11,7 @@
11
11
  import * as p from '@clack/prompts';
12
12
  import pc from 'picocolors';
13
13
  import { ProxyChatSession, getGlobalActiveModel, compressTextUsingFlashLite } from '../ai.js';
14
+ import { peekTurnUsage } from '../proxyClient.js';
14
15
  import { GEMINI_MODELS, MAX_OUTPUT_TOKENS } from '../../utils/config.js';
15
16
  import { debugLog } from '../../utils/logger.js';
16
17
  import { MessageBus } from './messageBus.js';
@@ -19,19 +20,22 @@ import { SubAgentRunner } from './subAgent.js';
19
20
  import { validateTaskGraph, computeExecutionWaves, detectFileConflicts, resolveFileConflicts, buildCycleCorrectionPrompt, CyclicDependencyError, } from './taskGraph.js';
20
21
  import { trackTask } from '../../utils/taskVisualizer.js';
21
22
  // ─── Constants ───────────────────────────────────────────────────────
22
- const PM_SYSTEM_INSTRUCTION = `You are the PM Agent (Project Manager).
23
+ const PM_SYSTEM_INSTRUCTION = `<identity>
24
+ You are the PM Agent (Project Manager).
23
25
  Your job is to decompose the user's objective into a parallelizable Task Graph for sub-agents.
24
26
  You must output ONLY raw JSON representing the TaskGraph object. Do not wrap in markdown code blocks.
27
+ </identity>
25
28
 
26
- Requirements:
29
+ <requirements>
27
30
  - Break the work down logically based on file isolation and dependency chains.
28
31
  - Tasks that don't depend on each other will run in parallel.
29
32
  - If a task depends on another, it must list its ID in "dependsOn".
30
33
  - Do NOT create cyclic dependencies (A -> B -> A).
31
34
  - If the entire objective is very simple and only requires modifying 1-2 files sequentially, output a graph with exactly 1 task.
32
35
  - Ensure 'targetFiles' lists all files the task will modify.
36
+ </requirements>
33
37
 
34
- JSON Schema:
38
+ <json_schema>
35
39
  {
36
40
  "objective": "string",
37
41
  "tasks": [
@@ -46,7 +50,7 @@ JSON Schema:
46
50
  ],
47
51
  "constraints": ["string"]
48
52
  }
49
- `;
53
+ </json_schema>`;
50
54
  // ─── Orchestrator Implementation ─────────────────────────────────────
51
55
  export class Orchestrator {
52
56
  workspaceRoot;
@@ -64,7 +68,7 @@ export class Orchestrator {
64
68
  this.locks = new FileLockRegistry();
65
69
  let model = getGlobalActiveModel();
66
70
  if (model === GEMINI_MODELS.AUTO)
67
- model = GEMINI_MODELS.FLASH_3_5;
71
+ model = GEMINI_MODELS.FLASH;
68
72
  this.pmChat = new ProxyChatSession(model, PM_SYSTEM_INSTRUCTION, [], {
69
73
  maxOutputTokens: MAX_OUTPUT_TOKENS,
70
74
  temperature: 0.1,
@@ -97,10 +101,12 @@ export class Orchestrator {
97
101
  for (const wave of waves) {
98
102
  if (signal.aborted)
99
103
  break;
100
- const taskDescriptions = wave.taskIds.map(taskId => {
101
- const taskDef = graph.tasks.find(t => t.id === taskId);
104
+ const taskDescriptions = wave.taskIds
105
+ .map((taskId) => {
106
+ const taskDef = graph.tasks.find((t) => t.id === taskId);
102
107
  return ` - ${pc.cyan(taskDef.id)}: ${pc.dim(taskDef.intent)}`;
103
- }).join('\n');
108
+ })
109
+ .join('\n');
104
110
  p.log.step(pc.magenta(`Starting Wave ${wave.depth + 1} (${wave.taskIds.length} tasks):\n${taskDescriptions}`));
105
111
  const s = p.spinner();
106
112
  s.start(`Executing Wave ${wave.depth + 1}...`);
@@ -116,8 +122,8 @@ export class Orchestrator {
116
122
  const MAX_CONCURRENT = 2;
117
123
  for (let i = 0; i < wave.taskIds.length; i += MAX_CONCURRENT) {
118
124
  const chunk = wave.taskIds.slice(i, i + MAX_CONCURRENT);
119
- const wavePromises = chunk.map(taskId => {
120
- const taskDef = graph.tasks.find(t => t.id === taskId);
125
+ const wavePromises = chunk.map((taskId) => {
126
+ const taskDef = graph.tasks.find((t) => t.id === taskId);
121
127
  const globalContext = `Objective:\n${objective}\n\nContext:\n${contextInjection}`;
122
128
  agentStatuses.set(taskDef.id, 'Starting...');
123
129
  updateSpinner();
@@ -131,7 +137,7 @@ export class Orchestrator {
131
137
  }
132
138
  s.stop(`Wave ${wave.depth + 1} execution finished.`);
133
139
  // Post-wave evaluation
134
- const failedCount = results.filter(r => !r.success).length;
140
+ const failedCount = results.filter((r) => !r.success).length;
135
141
  if (failedCount > 0) {
136
142
  p.log.warn(pc.yellow(`Wave ${wave.depth + 1} finished with ${failedCount} failure(s).`));
137
143
  // Phase 3 implementation note: Right now, we continue and let downstream
@@ -226,30 +232,47 @@ export class Orchestrator {
226
232
  p.log.step(pc.cyan('Orchestrator: Reconciling results...'));
227
233
  const stats = this.bus.getStats();
228
234
  let totalTokens = 0;
235
+ let inputTokens = 0;
236
+ let outputTokens = 0;
229
237
  let failedTasks = 0;
230
238
  let rawSummaryData = '';
231
239
  for (const [taskId, res] of this.agentResults.entries()) {
232
240
  totalTokens += res.creditsUsed;
241
+ inputTokens += res.inputTokens || 0;
242
+ outputTokens += res.outputTokens || 0;
233
243
  if (!res.success)
234
244
  failedTasks++;
235
245
  debugLog(`Task ${taskId} summary: ${res.summary.substring(0, 100)}...`);
236
246
  rawSummaryData += `Task: ${taskId} | Status: ${res.success ? 'Success' : 'Failed'}\nChanges:\n${res.summary}\n\n`;
237
247
  }
238
248
  p.log.step(pc.cyan('Orchestrator: Synthesizing final changes overview...'));
239
- let finalSummary = '[Sub-Agent Orchestration Completed]\n\n';
249
+ let finalSummary = '';
240
250
  try {
241
- const instruction = 'You are an expert technical orchestrator. The user has delegated a complex task to multiple sub-agents. Below are their individual task completion summaries. Synthesize these summaries into ONE cohesive, unified overview of all changes made. DO NOT list them by task name or separate them by agent. Just provide a single seamless summary of what was accomplished overall. Use clear bullet points and markdown. Be concise but complete.';
242
- const synthesized = await compressTextUsingFlashLite(rawSummaryData, instruction);
251
+ const payloadWithContext = `<original_request>\n${graph.objective}\n</original_request>\n\n<task_summaries>\n${rawSummaryData}\n</task_summaries>`;
252
+ const instruction = '<identity>\n' +
253
+ "You are Mino, a Senior software developer. You just delegated the user's complex request to multiple parallel sub-agents, and they have finished. " +
254
+ "Below is the user's original request, followed by the raw task completion summaries from the sub-agents.\n" +
255
+ '</identity>\n\n' +
256
+ '<directives>\n' +
257
+ 'Synthesize these summaries into a natural, conversational response back to the user. ' +
258
+ 'Address their original request directly (e.g., "I have completed your request to..."). ' +
259
+ 'Provide ONE cohesive, unified overview of all changes made, using clear bullet points. ' +
260
+ 'DO NOT list changes by task name or separate them by agent. ' +
261
+ 'Be concise, helpful, and conclude by asking if they need any further adjustments.\n' +
262
+ '</directives>';
263
+ const synthesized = await compressTextUsingFlashLite(payloadWithContext, instruction);
243
264
  finalSummary += synthesized + '\n\n';
244
265
  }
245
266
  catch (e) {
246
267
  debugLog(`Synthesis failed, falling back to raw output: ${e}`);
247
268
  finalSummary += rawSummaryData;
248
269
  }
270
+ const currentUsage = peekTurnUsage();
271
+ const totalInputTokens = (currentUsage.promptTokens || 0) + (currentUsage.cachedTokens || 0);
249
272
  p.log.info(`${pc.green('✓')} Sub-agent execution complete.\n` +
250
273
  ` Total tasks: ${graph.tasks.length} (${failedTasks} failed)\n` +
251
274
  ` Bus Activity: ${stats.activityCount} actions, ${stats.signalCount} signals\n` +
252
- ` Total Tokens: ${totalTokens.toLocaleString()}`);
275
+ ` Total Tokens: ${currentUsage.totalTokenCount.toLocaleString()} ${pc.dim(`(Input: ${totalInputTokens.toLocaleString()}, Output: ${currentUsage.candidatesTokens.toLocaleString()})`)}`);
253
276
  // Clear the bus persistence now that orchestration is done
254
277
  await this.bus.cleanup();
255
278
  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,8 +36,10 @@ 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
- static readonly STALL_TIMEOUT_MS = 60000;
42
+ static readonly STALL_TIMEOUT_MS = 300000;
39
43
  constructor(taskId: string, intent: string, workspaceRoot: string, bus: MessageBus, locks: FileLockRegistry, globalContext: string, onProgress?: ((msg: string) => void) | undefined);
40
44
  /**
41
45
  * Constructs the base system instruction for this specific agent.
@@ -24,8 +24,10 @@ 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
- static STALL_TIMEOUT_MS = 60_000;
30
+ static STALL_TIMEOUT_MS = 300_000;
29
31
  constructor(taskId, intent, workspaceRoot, bus, locks, globalContext, onProgress) {
30
32
  this.taskId = taskId;
31
33
  this.intent = intent;
@@ -36,8 +38,8 @@ export class SubAgentRunner {
36
38
  this.onProgress = onProgress;
37
39
  let model = getGlobalActiveModel();
38
40
  if (model === GEMINI_MODELS.AUTO)
39
- model = GEMINI_MODELS.FLASH_3_5;
40
- // Sub-agents default to flash-3.5 for better reasoning capabilities
41
+ model = GEMINI_MODELS.FLASH;
42
+ // Sub-agents default to flash-3.6 for better reasoning capabilities
41
43
  this.chat = new ProxyChatSession(model, this.buildSystemInstruction(), [{ functionDeclarations: getScopedToolDeclarations() }], {
42
44
  maxOutputTokens: MAX_OUTPUT_TOKENS,
43
45
  temperature: 0.3, // Lower temperature for more focused execution
@@ -49,18 +51,24 @@ export class SubAgentRunner {
49
51
  * Constructs the base system instruction for this specific agent.
50
52
  */
51
53
  buildSystemInstruction() {
52
- return (`You are an autonomous sub-agent executing a specific portion of a larger task.\n` +
54
+ return (`<identity>\n` +
55
+ `You are an autonomous Senior software developer sub-agent executing a specific portion of a larger task.\n` +
56
+ `</identity>\n\n` +
57
+ `<task_info>\n` +
53
58
  `Your task ID is: ${this.taskId}\n` +
54
- `Your objective: ${this.intent}\n\n` +
55
- `=== REFERENCE CONTEXT (DO NOT IMPLEMENT THIS FULL REQUEST) ===\n` +
59
+ `Your objective: ${this.intent}\n` +
60
+ `</task_info>\n\n` +
61
+ `<reference_context>\n` +
62
+ `DO NOT IMPLEMENT THIS FULL REQUEST. THIS IS JUST FOR CONTEXT.\n\n` +
56
63
  `${this.globalContext}\n` +
57
- `==============================================================\n\n` +
58
- `CRITICAL GUIDELINES:\n` +
64
+ `</reference_context>\n\n` +
65
+ `<critical_guidelines>\n` +
59
66
  `1. You are ONE worker in a team. You MUST ONLY focus on your specific objective: "${this.intent}".\n` +
60
67
  `2. DO NOT attempt to fulfill the entire original user request in the reference context. Other agents are handling the other parts.\n` +
61
68
  `3. You are part of a parallelized system. Use 'post_message' to coordinate if you discover breaking changes.\n` +
62
69
  `4. When you have completed your objective, stop using tools and provide a final summary of your work.\n` +
63
- `5. If you encounter an insurmountable error, provide a summary of what went wrong so the orchestrator can re-assign or fix it.`);
70
+ `5. If you encounter an insurmountable error, provide a summary of what went wrong so the orchestrator can re-assign or fix it.\n` +
71
+ `</critical_guidelines>`);
64
72
  }
65
73
  /**
66
74
  * Updates the heartbeat. Passed to `executeScopedTool` to ensure the agent
@@ -124,8 +132,10 @@ export class SubAgentRunner {
124
132
  toolResponses.push({
125
133
  functionResponse: {
126
134
  name: call.name,
127
- response: { error: 'You have already made this exact tool call previously. Please review your context history or try a different action.' }
128
- }
135
+ response: {
136
+ error: 'You have already made this exact tool call previously. Please review your context history or try a different action.',
137
+ },
138
+ },
129
139
  });
130
140
  continue;
131
141
  }
@@ -149,7 +159,9 @@ export class SubAgentRunner {
149
159
  output: responseData.output !== undefined ? responseData.output : responseData,
150
160
  ...(responseData.error ? { error: responseData.error } : {}),
151
161
  ...(responseData.inlineData ? { inlineData: responseData.inlineData } : {}),
152
- ...(responseData._orchestrationWarning ? { _orchestrationWarning: responseData._orchestrationWarning } : {}),
162
+ ...(responseData._orchestrationWarning
163
+ ? { _orchestrationWarning: responseData._orchestrationWarning }
164
+ : {}),
153
165
  },
154
166
  },
155
167
  });
@@ -180,11 +192,13 @@ export class SubAgentRunner {
180
192
  crashed = true;
181
193
  finalSummary = 'Aborted by orchestrator.';
182
194
  }
183
- debugLog(`SubAgent [${this.taskId}]: Finished. Success=${success}, Crashed=${crashed}, Tokens=${this.creditsUsed}`);
195
+ debugLog(`SubAgent [${this.taskId}]: Finished. Success=${success}, Crashed=${crashed}, Tokens=${this.creditsUsed} (In: ${this.inputTokens}, Out: ${this.outputTokens})`);
184
196
  return {
185
197
  success: success && !crashed,
186
198
  summary: finalSummary,
187
199
  creditsUsed: this.creditsUsed,
200
+ inputTokens: this.inputTokens,
201
+ outputTokens: this.outputTokens,
188
202
  crashed,
189
203
  };
190
204
  });
@@ -195,10 +209,12 @@ export class SubAgentRunner {
195
209
  updateUsage(result) {
196
210
  const usage = result.response.usageMetadata?.();
197
211
  if (usage) {
198
- const tokens = usage.totalTokenCount || ((usage.promptTokens || 0) + (usage.candidatesTokens || 0) + (usage.cachedTokens || 0));
199
- if (tokens) {
200
- this.creditsUsed += tokens;
201
- }
212
+ const input = (usage.promptTokens || 0) + (usage.cachedTokens || 0);
213
+ const output = usage.candidatesTokens || 0;
214
+ const tokens = usage.totalTokenCount || input + output;
215
+ this.inputTokens += input;
216
+ this.outputTokens += output;
217
+ this.creditsUsed += tokens;
202
218
  }
203
219
  }
204
220
  }
@@ -28,6 +28,22 @@ export interface ProxyUsageMetadata {
28
28
  /** The remaining balance left in the user's account. */
29
29
  remainingBalance: number;
30
30
  }
31
+ export declare function getAndResetTurnUsage(): {
32
+ promptTokens: number;
33
+ candidatesTokens: number;
34
+ cachedTokens: number;
35
+ totalTokenCount: number;
36
+ creditsUsed: number;
37
+ remainingBalance: number | undefined;
38
+ };
39
+ export declare function peekTurnUsage(): {
40
+ promptTokens: number;
41
+ candidatesTokens: number;
42
+ cachedTokens: number;
43
+ totalTokenCount: number;
44
+ creditsUsed: number;
45
+ remainingBalance: number | undefined;
46
+ };
31
47
  /**
32
48
  * Client service interacting directly with the serverless Gemini proxy endpoint.
33
49
  * Ensures authorization via Firebase token passing and parses streamed content.
@@ -1,4 +1,27 @@
1
1
  import { debugLog } from '../utils/logger.js';
2
+ let globalSessionAccumulatedUsage = {
3
+ promptTokens: 0,
4
+ candidatesTokens: 0,
5
+ cachedTokens: 0,
6
+ totalTokenCount: 0,
7
+ creditsUsed: 0,
8
+ remainingBalance: undefined
9
+ };
10
+ export function getAndResetTurnUsage() {
11
+ const current = { ...globalSessionAccumulatedUsage };
12
+ globalSessionAccumulatedUsage = {
13
+ promptTokens: 0,
14
+ candidatesTokens: 0,
15
+ cachedTokens: 0,
16
+ totalTokenCount: 0,
17
+ creditsUsed: 0,
18
+ remainingBalance: undefined
19
+ };
20
+ return current;
21
+ }
22
+ export function peekTurnUsage() {
23
+ return { ...globalSessionAccumulatedUsage };
24
+ }
2
25
  /**
3
26
  * Client service interacting directly with the serverless Gemini proxy endpoint.
4
27
  * Ensures authorization via Firebase token passing and parses streamed content.
@@ -111,6 +134,15 @@ export class ProxyClient {
111
134
  else if (data.type === 'done') {
112
135
  if (data.usage) {
113
136
  usageMetadata = data.usage;
137
+ globalSessionAccumulatedUsage.promptTokens += data.usage.promptTokens || 0;
138
+ globalSessionAccumulatedUsage.candidatesTokens += data.usage.candidatesTokens || 0;
139
+ globalSessionAccumulatedUsage.cachedTokens += data.usage.cachedTokens || 0;
140
+ globalSessionAccumulatedUsage.creditsUsed += data.usage.creditsUsed || 0;
141
+ globalSessionAccumulatedUsage.totalTokenCount +=
142
+ (data.usage.promptTokens || 0) + (data.usage.cachedTokens || 0) + (data.usage.candidatesTokens || 0);
143
+ if (data.usage.remainingBalance !== undefined) {
144
+ globalSessionAccumulatedUsage.remainingBalance = data.usage.remainingBalance;
145
+ }
114
146
  }
115
147
  if (data.groundingMetadata) {
116
148
  groundingMetadata = data.groundingMetadata;
@@ -70,8 +70,8 @@ function truncateOutput(text, max) {
70
70
  * @returns Captured stdout, stderr, and exit code.
71
71
  */
72
72
  export async function runEphemeralScript(workspaceRoot, language, code, options) {
73
- const timeoutMs = options?.timeoutMs ?? 10_000;
74
- const maxOutputChars = options?.maxOutputChars ?? 30_000;
73
+ const timeoutMs = options?.timeoutMs ?? 60_000;
74
+ const maxOutputChars = options?.maxOutputChars ?? 100_000;
75
75
  const ext = LANGUAGE_EXTENSIONS[language.toLowerCase()];
76
76
  if (!ext) {
77
77
  return {
@@ -14,10 +14,9 @@ export declare const GITHUB_CLIENT_ID = "Ov23linFYFfjO3JILG7r";
14
14
  * Supported Gemini AI models.
15
15
  */
16
16
  export declare const GEMINI_MODELS: {
17
- readonly PRO_3_1: "gemini-3.1-pro-preview";
18
- readonly FLASH_3_5: "gemini-3.5-flash";
19
- readonly FLASH_2_5: "gemini-2.5-flash";
20
- readonly FLASH_LITE_3_1: "gemini-3.1-flash-lite";
17
+ readonly PRO: "gemini-3.1-pro-preview";
18
+ readonly FLASH: "gemini-3.6-flash";
19
+ readonly FLASH_LITE: "gemini-3.5-flash-lite";
21
20
  readonly AUTO: "auto";
22
21
  };
23
22
  /** Default Gemini model for the coding agent. */
@@ -14,10 +14,9 @@ export const GITHUB_CLIENT_ID = 'Ov23linFYFfjO3JILG7r';
14
14
  * Supported Gemini AI models.
15
15
  */
16
16
  export const GEMINI_MODELS = {
17
- PRO_3_1: 'gemini-3.1-pro-preview',
18
- FLASH_3_5: 'gemini-3.5-flash',
19
- FLASH_2_5: 'gemini-2.5-flash',
20
- FLASH_LITE_3_1: 'gemini-3.1-flash-lite',
17
+ PRO: 'gemini-3.1-pro-preview',
18
+ FLASH: 'gemini-3.6-flash',
19
+ FLASH_LITE: 'gemini-3.5-flash-lite',
21
20
  AUTO: 'auto',
22
21
  };
23
22
  /** Default Gemini model for the coding agent. */
@@ -0,0 +1,30 @@
1
+ /** Legacy plaintext file path (for migration). */
2
+ export declare const LEGACY_CONFIG_FILE: string;
3
+ export interface StoredCredentials {
4
+ idToken?: string;
5
+ refreshToken?: string;
6
+ idTokenExpiry?: number;
7
+ }
8
+ /**
9
+ * Persists authentication credentials to the most secure available store.
10
+ *
11
+ * Strategy (tried in order):
12
+ * 1. macOS Keychain (`security` CLI)
13
+ * 2. Linux libsecret (`secret-tool` CLI)
14
+ * 3. Windows DPAPI (PowerShell)
15
+ * 4. AES-256-GCM encrypted file with 0600 permissions
16
+ */
17
+ export declare function saveCredentials(data: StoredCredentials): Promise<void>;
18
+ /**
19
+ * Loads authentication credentials from the secure store.
20
+ * Returns an empty object if no credentials are found.
21
+ *
22
+ * On first run after upgrade, silently migrates any legacy plaintext
23
+ * `~/.minovative-mind-cli.json` into the secure store and deletes the old file.
24
+ */
25
+ export declare function loadCredentials(): Promise<StoredCredentials>;
26
+ /**
27
+ * Removes all stored credentials from the secure store.
28
+ * Also cleans up any legacy plaintext file if it still exists.
29
+ */
30
+ export declare function clearCredentials(): Promise<void>;