minovative-mind-cli 2.8.1 → 2.8.2

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
@@ -112,6 +112,7 @@ If you prefer to use your own API key instead of credits, you can configure it v
112
112
 
113
113
  | Command | What it does |
114
114
  | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | `/config-key` | Configure custom Google AI Studio API key (BYOK mode) |
115
116
  | `/models` | Hot-swap the active model |
116
117
  | `/plan` | Toggle plan mode to review implementation strategies |
117
118
  | `/paste` | Multi-line input mode (cancel with Ctrl+C) |
@@ -3,13 +3,15 @@ import { Command } from '@oclif/core';
3
3
  /**
4
4
  * @class DefaultCommand
5
5
  * @extends Command
6
- * @description The entry point for the "chat" CLI command. Manages user authentication,
7
- * environment initialization, and initializes the interactive agent loop session.
6
+ * @description Entry point for the "chat" CLI command. Manages user authentication,
7
+ * environment initialization, workspace setup, update notifications, and launches the interactive AI agent session.
8
8
  */
9
9
  export default class DefaultCommand extends Command {
10
10
  /**
11
11
  * The description displayed in the CLI help output.
12
- * Details the available slash commands, chat controls, and functionality.
12
+ * Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
13
+ * /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
14
+ * along with interactive chat controls.
13
15
  */
14
16
  static description: string;
15
17
  /**
@@ -11,31 +11,33 @@ import { updateWorkspaceStatus } from '../services/workspace.js';
11
11
  /**
12
12
  * @class DefaultCommand
13
13
  * @extends Command
14
- * @description The entry point for the "chat" CLI command. Manages user authentication,
15
- * environment initialization, and initializes the interactive agent loop session.
14
+ * @description Entry point for the "chat" CLI command. Manages user authentication,
15
+ * environment initialization, workspace setup, update notifications, and launches the interactive AI agent session.
16
16
  */
17
17
  export default class DefaultCommand extends Command {
18
18
  /**
19
19
  * The description displayed in the CLI help output.
20
- * Details the available slash commands, chat controls, and functionality.
20
+ * Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
21
+ * /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
22
+ * along with interactive chat controls.
21
23
  */
22
24
  static description = `Start an interactive AI coding agent session powered by Vertex AI.
23
25
 
24
26
  Inside the chat session, you can use the following commands in the slash menu:
25
- /models - Select the active model
26
- /plan - Toggle plan mode to review implementation strategies
27
- /paste - Enter multi-line paste mode for long snippets
28
- /clear - Clear conversation history
29
- /debug - Debug tests or command execution in a sandbox loop
30
- /auto-approve - Toggle automatic approval of tool/command runs
31
- /sub-agents - Toggle the MMAAK Engine for parallel investigation and execution
32
- /workspaces - Manage external workspaces for cross-project development
33
- /config-key - BYOK Use your own Google AI Studio API key
34
- /stats - View current session statistics and configuration
35
- /commit - Commit current workspace changes to Git
36
- /revert - Revert the last file modification made by the agent
37
- /chats - View, resume, edit titles, or delete previous chat sessions (includes bulk delete)
38
-
27
+ /config-key - BYOK Configure, clear, or view status of custom Google AI Studio API key
28
+ /paste - Enter multi-line paste mode for long code snippets and prompts
29
+ /plan - Toggle plan mode to review step-by-step implementation strategies
30
+ /clear - Clear conversation history, reset terminal screen, and display logo
31
+ /models - Select or hot-swap the active Gemini model for the session
32
+ /debug - Toggle internal agent telemetry, diagnostic logging, and execution state
33
+ /auto-approve - Toggle automatic execution approval for shell commands and tools
34
+ /sub-agents - Toggle the MMAAK Engine for parallel sub-agent task orchestration
35
+ /stats - View session token usage, credits, active settings, and configurations
36
+ /revert - Undo recent file modifications made by the agent or toggle change logging
37
+ /chats - Resume, list, search, rename, or delete saved chat sessions
38
+ /workspaces - Register and manage cross-repository aliases and active workspace paths
39
+ /commit - Auto-generate Conventional Commit messages from git diff and commit
40
+
39
41
  Chat Controls:
40
42
  - Multi-line Input: End a line with \\ to continue on the next line
41
43
  - Stop/Abort: Type "stop" to immediately interrupt agent generation
@@ -190,6 +190,7 @@ export class AsyncInputHandler {
190
190
  if (process.stdin.isTTY) {
191
191
  process.stdin.setRawMode(this.originalRawMode);
192
192
  }
193
+ process.stdin.resume();
193
194
  this.spinner = null;
194
195
  }
195
196
  /**
@@ -1,6 +1,9 @@
1
1
  import type { SlashCommandContext, SlashCommandResult } from './types.js';
2
2
  /**
3
- * Handles all slash command operations (/paste, /clear, /models, /debug, /auto-approve, /revert, /commit).
4
- * Returns control state to the caller loop (such as whether to continue/skip, or if a text-override occurred).
3
+ * Handles all interactive agent slash commands and updates session control state.
4
+ *
5
+ * @param command - The slash command string entered by the user (e.g. `/paste`, `/clear`, `/models`, `/config-key`).
6
+ * @param context - The execution context containing session state, active chat, input handler, and workspace paths.
7
+ * @returns Promise resolving to a {@link SlashCommandResult} indicating loop continuation, input overrides, or mode toggles.
5
8
  */
6
9
  export declare function handleSlashCommand(command: string, context: SlashCommandContext): Promise<SlashCommandResult>;
@@ -12,56 +12,42 @@ import { printLogo, brandBg, brandFg } from '../../utils/logo.js';
12
12
  import { readPaste } from '../../utils/paste.js';
13
13
  import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled } from '../agent-tools.js';
14
14
  import { ProxyClient, getAndResetTurnUsage } from '../proxyClient.js';
15
- import { getAuthorizedIdToken } from '../auth.js';
16
15
  import { GEMINI_MODELS, isByokEnabled } from '../../utils/config.js';
17
16
  import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
18
17
  import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '../ai.js';
18
+ /**
19
+ * @file slashCommands.ts
20
+ * @description Interactive slash command handler module for Minovative Mind CLI.
21
+ *
22
+ * Supported slash commands:
23
+ * - `/config-key` : Configure, validate, toggle, or clear Bring Your Own Key (BYOK) Google AI Studio API credentials.
24
+ * - `/paste` : Enter multi-line paste mode using EOF tracking (`Ctrl+D` submission).
25
+ * - `/plan` : Toggle AI step-by-step implementation planning mode.
26
+ * - `/clear` : Clear conversation history, wipe terminal screen, and reset CLI header.
27
+ * - `/models` : Hot-swap the active generative AI model (Gemini 3.1 Pro, Gemini 3.6 Flash, Gemini 3.5 Flash-Lite, Auto routing).
28
+ * - `/debug` : Toggle internal agent telemetry and diagnostic logging.
29
+ * - `/auto-approve`: Toggle automatic confirmation skipping for terminal shell execution commands.
30
+ * - `/sub-agents` : Toggle MMAAK Engine for parallel investigation sub-agent orchestration.
31
+ * - `/stats` : Display session and all-time token usage, credit balances, memory bank stats, and active configurations.
32
+ * - `/revert` : Interactively inspect, revert, or toggle tracking of session file modifications via `changeLogger`.
33
+ * - `/chats` : Interactive management menu to view, resume, rename, delete, or bulk-delete saved chat sessions.
34
+ * - `/workspaces` : Manage primary profiles, external sub-workspaces, and `@alias/` cross-repository path mapping.
35
+ * - `/commit` : Automate Conventional Commit generation using `git diff` analysis via Gemini 3.5 Flash-Lite.
36
+ */
19
37
  const execAsync = promisify(exec);
20
38
  /**
21
- * Handles all slash command operations (/paste, /clear, /models, /debug, /auto-approve, /revert, /commit).
22
- * Returns control state to the caller loop (such as whether to continue/skip, or if a text-override occurred).
39
+ * Handles all interactive agent slash commands and updates session control state.
40
+ *
41
+ * @param command - The slash command string entered by the user (e.g. `/paste`, `/clear`, `/models`, `/config-key`).
42
+ * @param context - The execution context containing session state, active chat, input handler, and workspace paths.
43
+ * @returns Promise resolving to a {@link SlashCommandResult} indicating loop continuation, input overrides, or mode toggles.
23
44
  */
24
45
  export async function handleSlashCommand(command, context) {
25
46
  const { chat, inputHandler, workspaceRoot, version, chatSessionState } = context;
26
47
  const lowerCommand = command.toLowerCase();
27
- if (lowerCommand === '/config-key') {
28
- const creds = await loadCredentials();
29
- const action = await p['select']({
30
- message: 'BYOK (Bring Your Own Key) Configuration — Google AI Studio keys only',
31
- options: [
32
- { value: 'status', label: 'Status' },
33
- { value: 'toggle', label: creds.useByok ? 'Disable BYOK' : 'Enable BYOK' },
34
- { value: 'set', label: 'Set API Key' },
35
- { value: 'clear', label: 'Clear API Key' },
36
- { value: 'cancel', label: 'Cancel' },
37
- ],
38
- });
39
- if (action === 'status') {
40
- const enabled = await isByokEnabled();
41
- p.log.info(`BYOK Status: ${enabled ? pc.green('Enabled') : pc.red('Disabled')}\n` +
42
- `API Key: ${creds.geminiApiKey ? pc.green('Set') : pc.red('Not Set')}`);
43
- }
44
- else if (action === 'toggle') {
45
- await updateCredentialField('useByok', !creds.useByok);
46
- p.log.success(`BYOK ${!creds.useByok ? 'enabled' : 'disabled'}.`);
47
- }
48
- else if (action === 'set') {
49
- const key = await p['text']({
50
- message: 'Enter your Google AI Studio API Key (aistudio.google.com):',
51
- validate: (value) => (value ? undefined : 'API Key is required'),
52
- });
53
- if (key && typeof key === 'string') {
54
- await updateCredentialField('geminiApiKey', key);
55
- p.log.success('API Key updated.');
56
- }
57
- }
58
- else if (action === 'clear') {
59
- await updateCredentialField('geminiApiKey', '');
60
- await updateCredentialField('useByok', false);
61
- p.log.success('API Key cleared and BYOK disabled.');
62
- }
63
- return { shouldContinue: true };
64
- }
48
+ /**
49
+ * `/paste` - Activates multi-line text capture using readline EOF tracking (`Ctrl+D` on an empty line).
50
+ */
65
51
  if (lowerCommand === '/paste') {
66
52
  p.log.info(pc.cyan('Paste mode activated. Paste your text below, then press Ctrl+D on an empty line to submit. (Ctrl+C to cancel)'));
67
53
  // Add space at the bottom to prevent terminal UI flickering when the cursor is at the absolute bottom
@@ -84,6 +70,9 @@ export async function handleSlashCommand(command, context) {
84
70
  return { shouldContinue: true, isRawPasteMode: false };
85
71
  }
86
72
  }
73
+ /**
74
+ * `/plan` - Toggles step-by-step AI implementation planning mode.
75
+ */
87
76
  if (lowerCommand === '/plan') {
88
77
  const newPlanMode = !context.isPlanMode;
89
78
  if (newPlanMode) {
@@ -94,6 +83,9 @@ export async function handleSlashCommand(command, context) {
94
83
  }
95
84
  return { shouldContinue: true, isPlanModeOverride: newPlanMode };
96
85
  }
86
+ /**
87
+ * `/clear` - Resets chat conversation history, clears the terminal screen, and reprints CLI header.
88
+ */
97
89
  if (lowerCommand === '/clear') {
98
90
  chat.clearHistory();
99
91
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
@@ -105,6 +97,9 @@ export async function handleSlashCommand(command, context) {
105
97
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
106
98
  return { shouldContinue: true };
107
99
  }
100
+ /**
101
+ * `/models` - Hot-swaps the active generative AI model or enables dynamic auto-routing.
102
+ */
108
103
  if (lowerCommand === '/models') {
109
104
  const currentModel = getGlobalActiveModel();
110
105
  const byokEnabled = await isByokEnabled();
@@ -152,6 +147,9 @@ export async function handleSlashCommand(command, context) {
152
147
  }
153
148
  return { shouldContinue: true };
154
149
  }
150
+ /**
151
+ * `/debug` - Toggles diagnostic logging and verbose agent telemetry.
152
+ */
155
153
  if (lowerCommand === '/debug') {
156
154
  const debugMode = toggleDebugMode();
157
155
  if (debugMode) {
@@ -162,6 +160,9 @@ export async function handleSlashCommand(command, context) {
162
160
  }
163
161
  return { shouldContinue: true };
164
162
  }
163
+ /**
164
+ * `/auto-approve` - Toggles automatic confirmation for tool shell command execution.
165
+ */
165
166
  if (lowerCommand === '/auto-approve') {
166
167
  if (getApprovalMode() === 'skip-all') {
167
168
  setApprovalMode('ask');
@@ -173,6 +174,9 @@ export async function handleSlashCommand(command, context) {
173
174
  }
174
175
  return { shouldContinue: true };
175
176
  }
177
+ /**
178
+ * `/sub-agents` - Toggles the MMAAK parallel sub-agent orchestration engine.
179
+ */
176
180
  if (lowerCommand === '/sub-agents') {
177
181
  if (isSubAgentsEnabled()) {
178
182
  setSubAgentsEnabled(false);
@@ -184,6 +188,9 @@ export async function handleSlashCommand(command, context) {
184
188
  }
185
189
  return { shouldContinue: true };
186
190
  }
191
+ /**
192
+ * `/stats` - Displays token metrics, credit balances, session history, memory bank stats, and configuration status.
193
+ */
187
194
  if (lowerCommand === '/stats') {
188
195
  const latestUsage = context.chatSessionState?.latestUsageMetadata;
189
196
  const prevUsage = context.chatSessionState?.previousUsageMetadata;
@@ -331,6 +338,9 @@ export async function handleSlashCommand(command, context) {
331
338
  console.log(pc.dim('----------------------------------------\n'));
332
339
  return { shouldContinue: true };
333
340
  }
341
+ /**
342
+ * `/revert` - Interactively restores disk file snapshots or toggles change logging.
343
+ */
334
344
  if (lowerCommand === '/revert') {
335
345
  const isEnabled = changeLogger.getIsEnabled();
336
346
  const history = changeLogger.getHistory();
@@ -434,6 +444,9 @@ export async function handleSlashCommand(command, context) {
434
444
  }
435
445
  return { shouldContinue: true };
436
446
  }
447
+ /**
448
+ * `/chats` - Opens an interactive session manager to view, resume, rename, delete, or bulk-delete chat histories.
449
+ */
437
450
  if (lowerCommand === '/chats') {
438
451
  const sessions = chatHistoryService.getSessions() || [];
439
452
  const hasSessions = sessions.length > 0;
@@ -724,6 +737,9 @@ export async function handleSlashCommand(command, context) {
724
737
  }
725
738
  return { shouldContinue: true };
726
739
  }
740
+ /**
741
+ * `/workspaces` - Manages master profiles and linked external workspaces with cross-repo `@alias/` references.
742
+ */
727
743
  if (lowerCommand === '/workspaces') {
728
744
  const { workspaceRegistry } = await import('../workspaceRegistry.js');
729
745
  while (true) {
@@ -967,6 +983,9 @@ export async function handleSlashCommand(command, context) {
967
983
  }
968
984
  return { shouldContinue: true };
969
985
  }
986
+ /**
987
+ * `/commit` - Stages changes, generates a Conventional Commit message from `git diff`, and commits.
988
+ */
970
989
  if (lowerCommand === '/commit') {
971
990
  const commitSpinner = p.spinner();
972
991
  commitSpinner.start('Staging changes and analyzing diff...');
@@ -1006,6 +1025,9 @@ ${diffOut}
1006
1025
  }
1007
1026
  return { shouldContinue: true };
1008
1027
  }
1028
+ /**
1029
+ * `/config-key` - Manages Bring Your Own Key (BYOK) settings, allowing users to set, validate, toggle, or clear API keys.
1030
+ */
1009
1031
  if (lowerCommand === '/config-key') {
1010
1032
  const creds = await loadCredentials();
1011
1033
  const byokEnabled = !!(creds.useByok && creds.geminiApiKey);
@@ -1040,11 +1062,9 @@ ${diffOut}
1040
1062
  const spinner = p.spinner();
1041
1063
  spinner.start('Validating API Key...');
1042
1064
  try {
1043
- // Test call to verify key
1065
+ // Test call directly with the entered API key to verify it
1044
1066
  const client = new ProxyClient();
1045
- const idToken = (await getAuthorizedIdToken()) || '';
1046
- // We use a simple prompt to test the key
1047
- await client.generateFunctionCallViaProxy(idToken, 'gemini-3.5-flash-lite', [{ role: 'user', parts: [{ text: 'ping' }] }], undefined, undefined, 'You are a validator. Respond with "pong".', { maxOutputTokens: 10 });
1067
+ await client.generateViaBYOK(key, 'gemini-3.5-flash-lite', [{ role: 'user', parts: [{ text: 'ping' }] }], undefined, undefined, 'You are a validator. Respond with "pong".', { maxOutputTokens: 10 });
1048
1068
  // Reset metrics after validation
1049
1069
  getAndResetTurnUsage();
1050
1070
  await updateCredentialField('geminiApiKey', key);
@@ -1,15 +1,29 @@
1
1
  import type { ProxyChatSession } from '../ai.js';
2
2
  import type { AsyncInputHandler } from './inputHandler.js';
3
+ /**
4
+ * State tracking container for the active agent operational mode.
5
+ */
3
6
  export interface AgentState {
7
+ /** The target agent mode: 'CHAT' for conversational interactions or 'EXECUTE' for tool/command execution. */
4
8
  targetAgent: 'CHAT' | 'EXECUTE';
5
9
  }
10
+ /**
11
+ * Context object provided to slash command handlers containing session handles, terminal state, and metadata.
12
+ */
6
13
  export interface SlashCommandContext {
14
+ /** Active AI session handle supporting message sending and model switching. */
7
15
  chat: ProxyChatSession;
16
+ /** Terminal input handler for async non-blocking user prompts. */
8
17
  inputHandler: AsyncInputHandler;
18
+ /** Absolute file system path to the active workspace root directory. */
9
19
  workspaceRoot: string;
20
+ /** Version string of the running CLI application. */
10
21
  version: string;
22
+ /** Flag indicating whether raw paste mode is currently enabled. */
11
23
  isRawPasteMode: boolean;
24
+ /** Flag indicating whether step-by-step implementation planning mode is currently active. */
12
25
  isPlanMode: boolean;
26
+ /** Session state tracking tokens, credits, usage metadata, and model turn counts. */
13
27
  chatSessionState: {
14
28
  id: string;
15
29
  title: string;
@@ -22,9 +36,16 @@ export interface SlashCommandContext {
22
36
  modelUsageCounts?: Record<string, number>;
23
37
  };
24
38
  }
39
+ /**
40
+ * Result structure returned by slash command handlers to direct the main event loop.
41
+ */
25
42
  export interface SlashCommandResult {
43
+ /** Indicates whether the main agent loop should continue prompting for user input. */
26
44
  shouldContinue: boolean;
45
+ /** Optional input override string to submit directly to the agent (e.g., from `/paste`). */
27
46
  userInputOverride?: string;
47
+ /** Optional override flag to enable or disable raw paste mode. */
28
48
  isRawPasteMode?: boolean;
49
+ /** Optional override flag to enable or disable implementation planning mode. */
29
50
  isPlanModeOverride?: boolean;
30
51
  }
@@ -3,6 +3,13 @@
3
3
  * @description Implements tool declarations, approval state management, ignore rule parser,
4
4
  * atomic file operations, search/grep engines, dependency tracer, scratchpad script runner,
5
5
  * and central tool dispatcher used by the Minovative Mind AI agents.
6
+ *
7
+ * @module AgentTools
8
+ * @exports toolDeclarations, getToolDeclarations, getApprovalMode, setApprovalMode,
9
+ * isSubAgentsEnabled, setSubAgentsEnabled, consumeSkipOnce, readFile,
10
+ * writeFile, deleteFile, renameFile, modifyFile, listDirectory, runCommand,
11
+ * grepSearch, traceDependencies, findRecentChanges, runDebugScript,
12
+ * executeFuzzProbe, executeHeapDelta, executeBehavioralDrift, executeTool
6
13
  */
7
14
  import { type FunctionDeclaration } from '@google/generative-ai';
8
15
  /**
@@ -185,6 +192,13 @@ export declare function findRecentChanges(workspaceRoot: string, dirPath?: strin
185
192
  export declare function runDebugScript(workspaceRoot: string, language: string, code: string, abortSignal?: AbortSignal): Promise<ToolResult>;
186
193
  /**
187
194
  * Executes a fuzz probing analysis script and formats the resulting pass/fail and crash statistics.
195
+ *
196
+ * @param workspaceRoot - Absolute path to the workspace root directory.
197
+ * @param code - The fuzz test script or probe code to execute.
198
+ * @param language - Runtime environment language (defaults to `'node'`).
199
+ * @param config - Optional configuration parameters including iterations, maxInputLength, seed, and timeoutMs.
200
+ * @param abortSignal - Optional AbortSignal to cancel execution.
201
+ * @returns A promise resolving to a {@link ToolResult} containing fuzz run statistics and error summaries.
188
202
  */
189
203
  export declare function executeFuzzProbe(workspaceRoot: string, code: string, language?: string, config?: {
190
204
  iterations?: number;
@@ -194,6 +208,13 @@ export declare function executeFuzzProbe(workspaceRoot: string, code: string, la
194
208
  }, abortSignal?: AbortSignal): Promise<ToolResult>;
195
209
  /**
196
210
  * Executes a heap memory inspection script and formats memory consumption and growth delta metrics.
211
+ *
212
+ * @param workspaceRoot - Absolute path to the workspace root directory.
213
+ * @param code - The memory inspection script or code snippet to execute.
214
+ * @param language - Runtime environment language (defaults to `'node'`).
215
+ * @param config - Optional configuration parameters including warmupIterations, testIterations, samplingIntervalMs, and timeoutMs.
216
+ * @param abortSignal - Optional AbortSignal to cancel execution.
217
+ * @returns A promise resolving to a {@link ToolResult} containing memory delta statistics and leak analysis.
197
218
  */
198
219
  export declare function executeHeapDelta(workspaceRoot: string, code: string, language?: string, config?: {
199
220
  warmupIterations?: number;
@@ -203,6 +224,14 @@ export declare function executeHeapDelta(workspaceRoot: string, code: string, la
203
224
  }, abortSignal?: AbortSignal): Promise<ToolResult>;
204
225
  /**
205
226
  * Executes baseline and candidate scripts, returning output diffs and behavioral drift findings.
227
+ *
228
+ * @param workspaceRoot - Absolute path to the workspace root directory.
229
+ * @param baselineCode - The baseline implementation script source code.
230
+ * @param candidateCode - The candidate implementation script source code.
231
+ * @param language - Runtime environment language (defaults to `'node'`).
232
+ * @param config - Optional configuration parameters including tolerance, compareKeys, and timeoutMs.
233
+ * @param abortSignal - Optional AbortSignal to cancel execution.
234
+ * @returns A promise resolving to a {@link ToolResult} containing behavioral drift and diff summaries.
206
235
  */
207
236
  export declare function executeBehavioralDrift(workspaceRoot: string, baselineCode: string, candidateCode: string, language?: string, config?: {
208
237
  tolerance?: number;
@@ -3,6 +3,13 @@
3
3
  * @description Implements tool declarations, approval state management, ignore rule parser,
4
4
  * atomic file operations, search/grep engines, dependency tracer, scratchpad script runner,
5
5
  * and central tool dispatcher used by the Minovative Mind AI agents.
6
+ *
7
+ * @module AgentTools
8
+ * @exports toolDeclarations, getToolDeclarations, getApprovalMode, setApprovalMode,
9
+ * isSubAgentsEnabled, setSubAgentsEnabled, consumeSkipOnce, readFile,
10
+ * writeFile, deleteFile, renameFile, modifyFile, listDirectory, runCommand,
11
+ * grepSearch, traceDependencies, findRecentChanges, runDebugScript,
12
+ * executeFuzzProbe, executeHeapDelta, executeBehavioralDrift, executeTool
6
13
  */
7
14
  import { exec } from 'node:child_process';
8
15
  import { promises as fs } from 'node:fs';
@@ -330,7 +337,7 @@ export const toolDeclarations = [
330
337
  },
331
338
  {
332
339
  name: 'finish_task',
333
- description: 'Marks your execution as fully complete. You MUST call this tool when you have finished all tasks on your todo list and completely satisfied the user\'s request.',
340
+ description: "Marks your execution as fully complete. You MUST call this tool when you have finished all tasks on your todo list and completely satisfied the user's request.",
334
341
  parameters: {
335
342
  type: SchemaType.OBJECT,
336
343
  properties: {
@@ -933,7 +940,11 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
933
940
  // Provide a preview of the file to help the AI self-correct
934
941
  const preview = modified.split('\n').slice(0, 30).join('\n');
935
942
  const priorEditsSummary = appliedEdits.length > 0
936
- ? `\nPrior applied edits before failure:\n` + appliedEdits.map((e) => ` - Edit #${e.index}: Line ${e.line === -1 ? 'N/A' : e.line} (${e.strategy})`).join('\n') + '\n'
943
+ ? `\nPrior applied edits before failure:\n` +
944
+ appliedEdits
945
+ .map((e) => ` - Edit #${e.index}: Line ${e.line === -1 ? 'N/A' : e.line} (${e.strategy})`)
946
+ .join('\n') +
947
+ '\n'
937
948
  : '';
938
949
  return {
939
950
  output: '',
@@ -991,7 +1002,10 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
991
1002
  }
992
1003
  if (!success) {
993
1004
  const appliedSummary = appliedEdits.length > 0
994
- ? `\nApplied edit details:\n` + appliedEdits.map((e) => ` - Edit #${e.index}: Line ${e.line === -1 ? 'N/A' : e.line} (${e.strategy})`).join('\n')
1005
+ ? `\nApplied edit details:\n` +
1006
+ appliedEdits
1007
+ .map((e) => ` - Edit #${e.index}: Line ${e.line === -1 ? 'N/A' : e.line} (${e.strategy})`)
1008
+ .join('\n')
995
1009
  : '';
996
1010
  return {
997
1011
  output: '',
@@ -1033,6 +1047,13 @@ export async function listDirectory(workspaceRoot, dirPath, maxDepth = 3) {
1033
1047
  const absPath = resolveAndValidatePath(workspaceRoot, dirPath);
1034
1048
  const lines = [];
1035
1049
  const { ig } = await getIgnoredPaths(workspaceRoot);
1050
+ /**
1051
+ * Recursively walks directory entries to construct an ASCII tree structure.
1052
+ *
1053
+ * @param currentPath - Absolute path to the current directory being traversed.
1054
+ * @param prefix - Current tree branch indentation prefix.
1055
+ * @param depth - Current recursion depth.
1056
+ */
1036
1057
  async function walk(currentPath, prefix, depth) {
1037
1058
  if (depth > maxDepth)
1038
1059
  return;
@@ -1146,6 +1167,11 @@ export async function grepSearch(workspaceRoot, pattern, fileGlob, fixedStrings,
1146
1167
  // Use ignore package to evaluate path globs (e.g. src/**/*.ts)
1147
1168
  const globMatcher = fileGlob ? ignore().add(fileGlob) : null;
1148
1169
  const results = [];
1170
+ /**
1171
+ * Recursively walks directory entries to search for pattern matches.
1172
+ *
1173
+ * @param dir - Absolute path to the current directory being searched.
1174
+ */
1149
1175
  async function walk(dir) {
1150
1176
  if (results.length >= 50)
1151
1177
  return;
@@ -1255,6 +1281,12 @@ export async function findRecentChanges(workspaceRoot, dirPath = '.', minutes =
1255
1281
  const { ig } = await getIgnoredPaths(workspaceRoot);
1256
1282
  const thresholdMs = Date.now() - minutes * 60 * 1000;
1257
1283
  const recentFiles = [];
1284
+ /**
1285
+ * Recursively walks directory entries to identify recently modified files.
1286
+ *
1287
+ * @param currentPath - Absolute path to the current directory being checked.
1288
+ * @param depth - Current recursion depth.
1289
+ */
1258
1290
  async function walk(currentPath, depth) {
1259
1291
  if (depth > maxDepth)
1260
1292
  return;
@@ -1418,6 +1450,13 @@ export async function runDebugScript(workspaceRoot, language, code, abortSignal)
1418
1450
  }
1419
1451
  /**
1420
1452
  * Executes a fuzz probing analysis script and formats the resulting pass/fail and crash statistics.
1453
+ *
1454
+ * @param workspaceRoot - Absolute path to the workspace root directory.
1455
+ * @param code - The fuzz test script or probe code to execute.
1456
+ * @param language - Runtime environment language (defaults to `'node'`).
1457
+ * @param config - Optional configuration parameters including iterations, maxInputLength, seed, and timeoutMs.
1458
+ * @param abortSignal - Optional AbortSignal to cancel execution.
1459
+ * @returns A promise resolving to a {@link ToolResult} containing fuzz run statistics and error summaries.
1421
1460
  */
1422
1461
  export async function executeFuzzProbe(workspaceRoot, code, language = 'node', config, abortSignal) {
1423
1462
  try {
@@ -1451,6 +1490,13 @@ export async function executeFuzzProbe(workspaceRoot, code, language = 'node', c
1451
1490
  }
1452
1491
  /**
1453
1492
  * Executes a heap memory inspection script and formats memory consumption and growth delta metrics.
1493
+ *
1494
+ * @param workspaceRoot - Absolute path to the workspace root directory.
1495
+ * @param code - The memory inspection script or code snippet to execute.
1496
+ * @param language - Runtime environment language (defaults to `'node'`).
1497
+ * @param config - Optional configuration parameters including warmupIterations, testIterations, samplingIntervalMs, and timeoutMs.
1498
+ * @param abortSignal - Optional AbortSignal to cancel execution.
1499
+ * @returns A promise resolving to a {@link ToolResult} containing memory delta statistics and leak analysis.
1454
1500
  */
1455
1501
  export async function executeHeapDelta(workspaceRoot, code, language = 'node', config, abortSignal) {
1456
1502
  try {
@@ -1487,6 +1533,14 @@ export async function executeHeapDelta(workspaceRoot, code, language = 'node', c
1487
1533
  }
1488
1534
  /**
1489
1535
  * Executes baseline and candidate scripts, returning output diffs and behavioral drift findings.
1536
+ *
1537
+ * @param workspaceRoot - Absolute path to the workspace root directory.
1538
+ * @param baselineCode - The baseline implementation script source code.
1539
+ * @param candidateCode - The candidate implementation script source code.
1540
+ * @param language - Runtime environment language (defaults to `'node'`).
1541
+ * @param config - Optional configuration parameters including tolerance, compareKeys, and timeoutMs.
1542
+ * @param abortSignal - Optional AbortSignal to cancel execution.
1543
+ * @returns A promise resolving to a {@link ToolResult} containing behavioral drift and diff summaries.
1490
1544
  */
1491
1545
  export async function executeBehavioralDrift(workspaceRoot, baselineCode, candidateCode, language = 'node', config, abortSignal) {
1492
1546
  try {
@@ -1626,7 +1680,7 @@ export async function executeTool(workspaceRoot, toolName, args, abortSignal) {
1626
1680
  // Normalize common LLM tool alias hallucinations to registered tool names
1627
1681
  let normalizedToolName = toolName;
1628
1682
  if (normalizedToolName === 'search' || normalizedToolName === 'web_search') {
1629
- normalizedToolName = (args.query || args.searchQuery || args.url) ? 'perform_web_search' : 'grep_search';
1683
+ normalizedToolName = args.query || args.searchQuery || args.url ? 'perform_web_search' : 'grep_search';
1630
1684
  }
1631
1685
  else if (normalizedToolName === 'grep' || normalizedToolName === 'search_codebase') {
1632
1686
  normalizedToolName = 'grep_search';
@@ -1,4 +1,4 @@
1
- import { GoogleGenerativeAI, SchemaType, } from '@google/generative-ai';
1
+ import { SchemaType, } from '@google/generative-ai';
2
2
  import { GEMINI_MODELS, DEFAULT_MODEL, MAX_OUTPUT_TOKENS, isByokEnabled } from '../utils/config.js';
3
3
  import { getToolDeclarations } from './agent-tools.js';
4
4
  import { getMetricCollector } from './metrics.js';
@@ -8,7 +8,7 @@ import { readCache, writeCache } from '../utils/projectStorage.js';
8
8
  import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
9
9
  import { workspaceRegistry } from './workspaceRegistry.js';
10
10
  import { loadCredentials } from '../utils/credentialStore.js';
11
- import { ProxyClient, accumulateTurnUsage } from './proxyClient.js';
11
+ import { ProxyClient } from './proxyClient.js';
12
12
  function getMultiWorkspaceBlock() {
13
13
  const summary = workspaceRegistry.buildPromptSummary();
14
14
  const primaryRoot = process.cwd();
@@ -232,42 +232,9 @@ export class ProxyChatSession {
232
232
  let result;
233
233
  if (byokEnabled) {
234
234
  const creds = await loadCredentials();
235
- const genAI = new GoogleGenerativeAI(creds.geminiApiKey);
236
- const model = genAI.getGenerativeModel({
237
- model: this.modelName,
238
- systemInstruction: this.systemInstruction,
239
- tools: this.tools,
240
- });
241
- const chat = model.startChat({
242
- history: this.history.slice(0, -1),
243
- generationConfig: effectiveGenerationConfig,
244
- });
245
- const lastMessage = this.history[this.history.length - 1];
246
- try {
247
- const response = await chat.sendMessage(lastMessage.parts);
248
- const responseObj = await response.response;
249
- result = {
250
- functionCalls: responseObj.functionCalls(),
251
- parts: responseObj.candidates?.[0]?.content?.parts,
252
- usageMetadata: responseObj.usageMetadata,
253
- };
254
- if (responseObj.usageMetadata) {
255
- const collector = getMetricCollector();
256
- collector?.accumulateUsage(responseObj.usageMetadata);
257
- }
258
- }
259
- catch (error) {
260
- debugLog(`Failed to send message via BYOK: ${error}`);
261
- const errorMessage = error?.message || '';
262
- if (error?.status === 401 ||
263
- error?.status === 403 ||
264
- errorMessage.includes('API_KEY_INVALID') ||
265
- errorMessage.includes('quota') ||
266
- errorMessage.includes('PERMISSION_DENIED')) {
267
- throw new Error('AI_BYOK_ERROR: Your API key or quota is invalid. Please run /config-key to update your settings.');
268
- }
269
- throw error;
270
- }
235
+ result = await proxyClient.generateViaBYOK(creds.geminiApiKey, this.modelName, this.history, this.tools, undefined, // toolConfig
236
+ this.systemInstruction, effectiveGenerationConfig, onChunk ? { onChunk } : undefined, // streamCallbacks
237
+ abortSignal);
271
238
  }
272
239
  else {
273
240
  result = await proxyClient.generateFunctionCallViaProxy(idToken, this.modelName, this.history, this.tools, undefined, // toolConfig
@@ -390,30 +357,9 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
390
357
  const byokEnabled = await isByokEnabled();
391
358
  let result;
392
359
  if (byokEnabled) {
393
- try {
394
- const creds = await loadCredentials();
395
- const genAI = new GoogleGenerativeAI(creds.geminiApiKey);
396
- const modelObj = genAI.getGenerativeModel({
397
- model,
398
- systemInstruction: instruction,
399
- });
400
- const response = await modelObj.generateContent({
401
- contents: [{ role: 'user', parts }],
402
- generationConfig: { temperature: 0.2 },
403
- });
404
- const responseObj = await response.response;
405
- result = {
406
- parts: responseObj.candidates?.[0]?.content?.parts,
407
- usageMetadata: responseObj.usageMetadata,
408
- };
409
- if (responseObj.usageMetadata) {
410
- accumulateTurnUsage(responseObj.usageMetadata, model);
411
- }
412
- }
413
- catch (error) {
414
- console.error('BYOK Error:', error.message);
415
- throw new Error(`BYOK AI Error: ${error.message}`);
416
- }
360
+ const creds = await loadCredentials();
361
+ result = await proxyClient.generateViaBYOK(creds.geminiApiKey, model, contents, [], // no tools
362
+ undefined, instruction, { temperature: 0.2 });
417
363
  }
418
364
  else {
419
365
  result = await proxyClient.generateFunctionCallViaProxy(idToken, model, contents, [], // no tools
@@ -680,30 +626,9 @@ export async function generateChatTitle(firstMessage) {
680
626
  const byokEnabled = await isByokEnabled();
681
627
  let result;
682
628
  if (byokEnabled) {
683
- try {
684
- const creds = await loadCredentials();
685
- const genAI = new GoogleGenerativeAI(creds.geminiApiKey);
686
- const modelObj = genAI.getGenerativeModel({
687
- model,
688
- systemInstruction: instruction,
689
- });
690
- const response = await modelObj.generateContent({
691
- contents,
692
- generationConfig: { temperature: 0.2 },
693
- });
694
- const responseObj = await response.response;
695
- result = {
696
- parts: responseObj.candidates?.[0]?.content?.parts,
697
- usageMetadata: responseObj.usageMetadata,
698
- };
699
- if (responseObj.usageMetadata) {
700
- accumulateTurnUsage(responseObj.usageMetadata, model);
701
- }
702
- }
703
- catch (error) {
704
- console.error('BYOK Error:', error.message);
705
- throw new Error(`BYOK AI Error: ${error.message}`);
706
- }
629
+ const creds = await loadCredentials();
630
+ result = await proxyClient.generateViaBYOK(creds.geminiApiKey, model, contents, [], // no tools
631
+ undefined, instruction, { temperature: 0.2 });
707
632
  }
708
633
  else {
709
634
  result = await proxyClient.generateFunctionCallViaProxy(idToken, model, contents, [], // no tools
@@ -83,7 +83,7 @@ export class Orchestrator {
83
83
  * was too simple and should be handled by the main agent loop.
84
84
  */
85
85
  async runOrchestration(objective, contextInjection, signal) {
86
- p.log.step(pc.cyan('Orchestrator: Planning task execution...'));
86
+ p.log.step(pc.cyan('Orchestrator: Planning task execution'));
87
87
  // 1. Task Decomposition
88
88
  const graph = await this.decomposeTask(objective, contextInjection, signal);
89
89
  if (!graph)
@@ -110,13 +110,13 @@ export class Orchestrator {
110
110
  p.log.step(pc.magenta(`Starting Wave ${wave.depth + 1} (${wave.taskIds.length} tasks):\n${taskDescriptions}`));
111
111
  const s = p.spinner();
112
112
  this.inputHandler.setSpinner(s);
113
- s.start(`Executing Wave ${wave.depth + 1}...`);
113
+ s.start(`Executing Wave ${wave.depth + 1}`);
114
114
  const agentStatuses = new Map();
115
115
  const updateSpinner = () => {
116
116
  const statuses = Array.from(agentStatuses.entries())
117
117
  .map(([id, status]) => `${pc.cyan(id)}: ${pc.dim(status)}`)
118
118
  .join(' │ ');
119
- s.message(statuses || `Executing Wave ${wave.depth + 1}...`);
119
+ s.message(statuses || `Executing Wave ${wave.depth + 1}`);
120
120
  };
121
121
  // Limit to 2 concurrent sub-agents to avoid Vertex AI RESOURCE_EXHAUSTED
122
122
  const results = [];
@@ -127,7 +127,7 @@ export class Orchestrator {
127
127
  const wavePromises = chunk.map((taskId) => {
128
128
  const taskDef = graph.tasks.find((t) => t.id === taskId);
129
129
  const globalContext = `Objective:\n${objective}\n\nContext:\n${contextInjection}`;
130
- agentStatuses.set(taskDef.id, 'Starting...');
130
+ agentStatuses.set(taskDef.id, 'Starting');
131
131
  updateSpinner();
132
132
  const onToolCall = (msg) => {
133
133
  const formattedLog = `${pc.dim(`[${taskDef.id}]`)} ${msg}`;
@@ -151,7 +151,7 @@ export class Orchestrator {
151
151
  const successful = results.filter((r) => r.success).length;
152
152
  s.stop(`Wave ${wave.depth + 1} completed: ${successful}/${wave.taskIds.length} tasks succeeded`);
153
153
  if (toolLogs.length > 0) {
154
- toolLogs.forEach(log => p.log.step(log));
154
+ toolLogs.forEach((log) => p.log.step(log));
155
155
  }
156
156
  // Post-wave evaluation
157
157
  const failedCount = results.filter((r) => !r.success).length;
@@ -200,7 +200,7 @@ export class Orchestrator {
200
200
  }
201
201
  else {
202
202
  // JSON parsing error or InvalidDependencyError
203
- debugLog(`PM Agent generated invalid graph: ${err.message}. Retrying...`);
203
+ debugLog(`PM Agent generated invalid graph: ${err.message}. Retrying`);
204
204
  result = await this.pmChat.sendMessage(`Invalid JSON or missing dependency ID: ${err.message}. Please fix.`, undefined, signal);
205
205
  if (signal.aborted)
206
206
  return null;
@@ -246,7 +246,7 @@ export class Orchestrator {
246
246
  * Final reconciliation phase after all waves complete.
247
247
  */
248
248
  async reconcile(graph) {
249
- p.log.step(pc.cyan('Orchestrator: Reconciling results...'));
249
+ p.log.step(pc.cyan('Orchestrator: Reconciling results'));
250
250
  const stats = this.bus.getStats();
251
251
  let totalTokens = 0;
252
252
  let inputTokens = 0;
@@ -259,10 +259,10 @@ export class Orchestrator {
259
259
  outputTokens += res.outputTokens || 0;
260
260
  if (!res.success)
261
261
  failedTasks++;
262
- debugLog(`Task ${taskId} summary: ${res.summary.substring(0, 100)}...`);
262
+ debugLog(`Task ${taskId} summary: ${res.summary.substring(0, 100)}`);
263
263
  rawSummaryData += `Task: ${taskId} | Status: ${res.success ? 'Success' : 'Failed'}\nChanges:\n${res.summary}\n\n`;
264
264
  }
265
- p.log.step(pc.cyan('Orchestrator: Synthesizing final changes overview...'));
265
+ p.log.step(pc.cyan('Orchestrator: Synthesizing final changes overview'));
266
266
  let finalSummary = '';
267
267
  try {
268
268
  const payloadWithContext = `<original_request>\n${graph.objective}\n</original_request>\n\n<task_summaries>\n${rawSummaryData}\n</task_summaries>`;
@@ -72,4 +72,18 @@ export declare class ProxyClient {
72
72
  usageMetadata?: ProxyUsageMetadata;
73
73
  groundingMetadata?: any;
74
74
  }>;
75
+ /**
76
+ * Generates text, thoughts, or function calls directly via Google's official Gemini REST API (BYOK mode).
77
+ * Ensures identical return structure and standard role mapping parity with proxy mode.
78
+ */
79
+ generateViaBYOK(apiKey: string, modelName: string, contents: Content[], tools?: Tool[], toolConfig?: ToolConfig, systemInstruction?: string | Content, generationConfig?: any, streamCallbacks?: {
80
+ onChunk: (chunk: string) => void;
81
+ }, abortSignal?: AbortSignal): Promise<{
82
+ functionCall: FunctionCall | null;
83
+ functionCalls: FunctionCall[];
84
+ thought?: string;
85
+ parts?: any[];
86
+ usageMetadata?: ProxyUsageMetadata;
87
+ groundingMetadata?: any;
88
+ }>;
75
89
  }
@@ -114,11 +114,11 @@ export class ProxyClient {
114
114
  signal: abortSignal,
115
115
  });
116
116
  debugLog(`Proxy Request to ${modelName} complete. Status: ${response.status} ${response.statusText}`);
117
- if ((response.status === 429 || response.status === 503) && attempt < MAX_RETRIES) {
117
+ if ((response.status === 429 || response.status === 503 || response.status === 502 || response.status === 500 || response.status === 504) && attempt < MAX_RETRIES) {
118
118
  const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
119
- const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
119
+ const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
120
120
  process.stdout.write('\n');
121
- console.warn(`Rate limit or service unavailable hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
121
+ console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
122
122
  await delay(delayTime, abortSignal);
123
123
  attempt++;
124
124
  continue;
@@ -232,14 +232,18 @@ export class ProxyClient {
232
232
  }
233
233
  catch (streamError) {
234
234
  if (streamError.message?.includes('429') ||
235
+ streamError.message?.includes('502') ||
235
236
  streamError.message?.includes('503') ||
237
+ streamError.message?.includes('500') ||
238
+ streamError.message?.includes('504') ||
239
+ streamError.message?.includes('Bad Gateway') ||
236
240
  streamError.message?.includes('RESOURCE_EXHAUSTED') ||
237
241
  streamError.message?.includes('Too Many Requests')) {
238
242
  if (attempt < MAX_RETRIES) {
239
243
  const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
240
- const delayTime = Math.round(exponentialDelay * (0.5 + Math.random() * 0.5));
244
+ const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
241
245
  process.stdout.write('\n');
242
- console.warn(`Rate limit or service unavailable hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
246
+ console.warn(`Server error or rate limit hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
243
247
  await delay(delayTime, abortSignal);
244
248
  attempt++;
245
249
  continue retryLoop;
@@ -260,4 +264,179 @@ export class ProxyClient {
260
264
  };
261
265
  }
262
266
  }
267
+ /**
268
+ * Generates text, thoughts, or function calls directly via Google's official Gemini REST API (BYOK mode).
269
+ * Ensures identical return structure and standard role mapping parity with proxy mode.
270
+ */
271
+ async generateViaBYOK(apiKey, modelName, contents, tools, toolConfig, systemInstruction, generationConfig, streamCallbacks, abortSignal) {
272
+ const isStreaming = Boolean(streamCallbacks?.onChunk);
273
+ const endpoint = isStreaming ? 'streamGenerateContent?alt=sse&key=' : 'generateContent?key=';
274
+ const url = `https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(modelName)}:${endpoint}${encodeURIComponent(apiKey)}`;
275
+ const formattedSystemInstruction = typeof systemInstruction === 'string' ? { parts: [{ text: systemInstruction }] } : systemInstruction;
276
+ const payload = { contents };
277
+ if (tools && tools.length > 0)
278
+ payload.tools = tools;
279
+ if (toolConfig)
280
+ payload.toolConfig = toolConfig;
281
+ if (formattedSystemInstruction)
282
+ payload.systemInstruction = formattedSystemInstruction;
283
+ if (generationConfig)
284
+ payload.generationConfig = generationConfig;
285
+ const MAX_RETRIES = 5;
286
+ const BASE_DELAY_MS = 2000;
287
+ const MAX_DELAY_MS = 30000;
288
+ let attempt = 0;
289
+ while (true) {
290
+ const response = await fetch(url, {
291
+ method: 'POST',
292
+ headers: { 'Content-Type': 'application/json' },
293
+ body: JSON.stringify(payload),
294
+ signal: abortSignal,
295
+ });
296
+ debugLog(`BYOK Request to ${modelName} complete. Status: ${response.status} ${response.statusText}`);
297
+ if ((response.status === 429 ||
298
+ response.status === 503 ||
299
+ response.status === 502 ||
300
+ response.status === 500 ||
301
+ response.status === 504) &&
302
+ attempt < MAX_RETRIES) {
303
+ const exponentialDelay = Math.min(MAX_DELAY_MS, BASE_DELAY_MS * Math.pow(2, attempt));
304
+ const delayTime = Math.round(exponentialDelay * (1.0 + Math.random() * 0.5));
305
+ process.stdout.write('\n');
306
+ console.warn(`Server error or rate limit hit (${response.status}). Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
307
+ await delay(delayTime, abortSignal);
308
+ attempt++;
309
+ continue;
310
+ }
311
+ if (!response.ok) {
312
+ let errorData = {};
313
+ try {
314
+ errorData = await response.json();
315
+ }
316
+ catch {
317
+ // ignore parsing error
318
+ }
319
+ const errorMsg = errorData.error?.message || response.statusText;
320
+ if (response.status === 400 ||
321
+ response.status === 401 ||
322
+ response.status === 403 ||
323
+ errorMsg.includes('API_KEY_INVALID') ||
324
+ errorMsg.includes('quota') ||
325
+ errorMsg.includes('PERMISSION_DENIED')) {
326
+ throw new Error('AI_BYOK_ERROR: Your API key or quota is invalid. Please run /config-key to update your settings.');
327
+ }
328
+ throw new Error(`BYOK API Error ${response.status}: ${errorMsg}`);
329
+ }
330
+ let functionCall = null;
331
+ const functionCalls = [];
332
+ let thought = '';
333
+ let parts = undefined;
334
+ let usageMetadata = undefined;
335
+ let groundingMetadata = undefined;
336
+ if (isStreaming) {
337
+ if (!response.body)
338
+ throw new Error('No response body received from BYOK API endpoint');
339
+ const reader = response.body.getReader();
340
+ const decoder = new TextDecoder();
341
+ let buffer = '';
342
+ try {
343
+ while (true) {
344
+ const { done, value } = await reader.read();
345
+ if (done)
346
+ break;
347
+ buffer += decoder.decode(value, { stream: true });
348
+ const lines = buffer.split('\n\n');
349
+ buffer = lines.pop() || '';
350
+ for (const line of lines) {
351
+ if (!line.startsWith('data: '))
352
+ continue;
353
+ const dataStr = line.slice(6).trim();
354
+ if (!dataStr)
355
+ continue;
356
+ try {
357
+ const data = JSON.parse(dataStr);
358
+ const candidate = data.candidates?.[0];
359
+ if (candidate?.content?.parts) {
360
+ parts = candidate.content.parts;
361
+ for (const part of candidate.content.parts) {
362
+ if (part.text) {
363
+ thought += part.text;
364
+ if (streamCallbacks?.onChunk)
365
+ streamCallbacks.onChunk(part.text);
366
+ }
367
+ if (part.functionCall) {
368
+ functionCall = part.functionCall;
369
+ functionCalls.push(part.functionCall);
370
+ }
371
+ }
372
+ }
373
+ if (candidate?.groundingMetadata) {
374
+ groundingMetadata = candidate.groundingMetadata;
375
+ }
376
+ if (data.usageMetadata) {
377
+ const pTokens = data.usageMetadata.promptTokenCount || 0;
378
+ const cTokens = data.usageMetadata.candidatesTokenCount || 0;
379
+ const cachedTokens = data.usageMetadata.cachedContentTokenCount || 0;
380
+ usageMetadata = {
381
+ promptTokens: pTokens,
382
+ candidatesTokens: cTokens,
383
+ cachedTokens: cachedTokens,
384
+ creditsUsed: 0,
385
+ remainingBalance: 0,
386
+ };
387
+ accumulateTurnUsage(data.usageMetadata, modelName);
388
+ }
389
+ }
390
+ catch (parseError) {
391
+ debugLog(`Failed to parse BYOK SSE data: ${dataStr} - Error: ${parseError.message || parseError}`);
392
+ }
393
+ }
394
+ }
395
+ }
396
+ finally {
397
+ reader.releaseLock();
398
+ }
399
+ }
400
+ else {
401
+ const data = await response.json();
402
+ const candidate = data.candidates?.[0];
403
+ if (candidate?.content?.parts) {
404
+ parts = candidate.content.parts;
405
+ for (const part of candidate.content.parts) {
406
+ if (part.text) {
407
+ thought += part.text;
408
+ }
409
+ if (part.functionCall) {
410
+ functionCall = part.functionCall;
411
+ functionCalls.push(part.functionCall);
412
+ }
413
+ }
414
+ }
415
+ if (candidate?.groundingMetadata) {
416
+ groundingMetadata = candidate.groundingMetadata;
417
+ }
418
+ if (data.usageMetadata) {
419
+ const pTokens = data.usageMetadata.promptTokenCount || 0;
420
+ const cTokens = data.usageMetadata.candidatesTokenCount || 0;
421
+ const cachedTokens = data.usageMetadata.cachedContentTokenCount || 0;
422
+ usageMetadata = {
423
+ promptTokens: pTokens,
424
+ candidatesTokens: cTokens,
425
+ cachedTokens: cachedTokens,
426
+ creditsUsed: 0,
427
+ remainingBalance: 0,
428
+ };
429
+ accumulateTurnUsage(data.usageMetadata, modelName);
430
+ }
431
+ }
432
+ return {
433
+ functionCall,
434
+ functionCalls,
435
+ thought: thought.trim(),
436
+ parts,
437
+ usageMetadata,
438
+ groundingMetadata,
439
+ };
440
+ }
441
+ }
263
442
  }
@@ -32,9 +32,9 @@ export declare const MAX_OUTPUT_TOKENS = 60000;
32
32
  */
33
33
  export declare const TPM_COOLING_DELAYS: {
34
34
  /** Pause before running initial intent routing between turns */
35
- readonly INTER_TURN_MS: 1000;
35
+ readonly INTER_TURN_MS: 3000;
36
36
  /** Pause before dispatching parallel sub-agent investigations simultaneously */
37
- readonly PARALLEL_DISPATCH_MS: 1000;
37
+ readonly PARALLEL_DISPATCH_MS: 3000;
38
38
  /** Pause after context gathering completes before starting the execution stream */
39
39
  readonly POST_INVESTIGATION_MS: 3000;
40
40
  };
@@ -32,9 +32,9 @@ export const MAX_OUTPUT_TOKENS = 60_000;
32
32
  */
33
33
  export const TPM_COOLING_DELAYS = {
34
34
  /** Pause before running initial intent routing between turns */
35
- INTER_TURN_MS: 1000,
35
+ INTER_TURN_MS: 3000,
36
36
  /** Pause before dispatching parallel sub-agent investigations simultaneously */
37
- PARALLEL_DISPATCH_MS: 1000,
37
+ PARALLEL_DISPATCH_MS: 3000,
38
38
  /** Pause after context gathering completes before starting the execution stream */
39
39
  POST_INVESTIGATION_MS: 3000,
40
40
  };
@@ -115,7 +115,6 @@ export function localValidate(filePath, content) {
115
115
  '?',
116
116
  ':',
117
117
  '<',
118
- '>',
119
118
  '/',
120
119
  '=>',
121
120
  '${',
@@ -227,10 +226,13 @@ export function localValidate(filePath, content) {
227
226
  continue;
228
227
  }
229
228
  // Check for regex literal start
230
- if (isJsTs && char === '/') {
229
+ if (isJsTs && char === '/' && nextChar !== '>') {
231
230
  // Determine if '/' starts a regex or is a division operator
232
231
  const prevToken = currentWord || lastToken;
233
- const isExpr = !prevToken || EXPR_KEYWORDS.has(prevToken) || EXPR_PUNCT.has(prevToken);
232
+ const isExpr = prevToken !== '<' &&
233
+ prevToken !== '}' &&
234
+ prevToken !== '>' &&
235
+ (!prevToken || EXPR_KEYWORDS.has(prevToken) || EXPR_PUNCT.has(prevToken));
234
236
  if (isExpr) {
235
237
  inRegex = true;
236
238
  inRegexCharClass = false;
@@ -22,24 +22,42 @@ import * as readline from 'node:readline';
22
22
  */
23
23
  export async function readPaste() {
24
24
  return new Promise((resolve) => {
25
+ if (process.stdin.isTTY) {
26
+ process.stdin.setRawMode(false);
27
+ }
28
+ process.stdin.resume();
25
29
  const rl = readline.createInterface({
26
30
  input: process.stdin,
27
31
  output: process.stdout,
28
32
  terminal: true,
29
33
  });
34
+ rl.setPrompt('');
35
+ rl.prompt();
30
36
  const content = [];
31
37
  let isCanceled = false;
38
+ let cleanedUp = false;
39
+ const cleanup = () => {
40
+ if (cleanedUp)
41
+ return;
42
+ cleanedUp = true;
43
+ rl.close();
44
+ if (process.stdin.isTTY) {
45
+ process.stdin.setRawMode(false);
46
+ }
47
+ };
32
48
  rl.on('line', (line) => {
33
49
  content.push(line);
50
+ rl.prompt();
34
51
  });
35
52
  rl.on('close', () => {
53
+ cleanup();
36
54
  if (!isCanceled) {
37
55
  resolve(content.join('\n').trim());
38
56
  }
39
57
  });
40
58
  rl.on('SIGINT', () => {
41
59
  isCanceled = true;
42
- rl.close();
60
+ cleanup();
43
61
  resolve(null);
44
62
  });
45
63
  });
@@ -3,7 +3,7 @@
3
3
  "chat": {
4
4
  "aliases": [],
5
5
  "args": {},
6
- "description": "Start an interactive AI coding agent session powered by Vertex AI.\n\nInside the chat session, you can use the following commands in the slash menu:\n /models - Select the active model\n /plan - Toggle plan mode to review implementation strategies\n /paste - Enter multi-line paste mode for long snippets\n /clear - Clear conversation history\n /debug - Debug tests or command execution in a sandbox loop\n /auto-approve - Toggle automatic approval of tool/command runs\n /sub-agents - Toggle the MMAAK Engine for parallel investigation and execution\n /workspaces - Manage external workspaces for cross-project development\n /config-key - BYOK Use your own Google AI Studio API key\n /stats - View current session statistics and configuration\n /commit - Commit current workspace changes to Git\n /revert - Revert the last file modification made by the agent\n /chats - View, resume, edit titles, or delete previous chat sessions (includes bulk delete)\n \nChat Controls:\n - Multi-line Input: End a line with \\ to continue on the next line\n - Stop/Abort: Type \"stop\" to immediately interrupt agent generation\n - Exit Session: Type \"exit\" or \"quit\" to end the agent session",
6
+ "description": "Start an interactive AI coding agent session powered by Vertex AI.\n\nInside the chat session, you can use the following commands in the slash menu:\n /config-key - BYOK Configure, clear, or view status of custom Google AI Studio API key\n /paste - Enter multi-line paste mode for long code snippets and prompts\n /plan - Toggle plan mode to review step-by-step implementation strategies\n /clear - Clear conversation history, reset terminal screen, and display logo\n /models - Select or hot-swap the active Gemini model for the session\n /debug - Toggle internal agent telemetry, diagnostic logging, and execution state\n /auto-approve - Toggle automatic execution approval for shell commands and tools\n /sub-agents - Toggle the MMAAK Engine for parallel sub-agent task orchestration\n /stats - View session token usage, credits, active settings, and configurations\n /revert - Undo recent file modifications made by the agent or toggle change logging\n /chats - Resume, list, search, rename, or delete saved chat sessions\n /workspaces - Register and manage cross-repository aliases and active workspace paths\n /commit - Auto-generate Conventional Commit messages from git diff and commit\n\nChat Controls:\n - Multi-line Input: End a line with \\ to continue on the next line\n - Stop/Abort: Type \"stop\" to immediately interrupt agent generation\n - Exit Session: Type \"exit\" or \"quit\" to end the agent session",
7
7
  "examples": [
8
8
  "<%= config.bin %> chat",
9
9
  "<%= config.bin %> chat --help"
@@ -65,5 +65,5 @@
65
65
  ]
66
66
  }
67
67
  },
68
- "version": "2.8.1"
68
+ "version": "2.8.2"
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.8.1",
4
+ "version": "2.8.2",
5
5
  "author": "Daniel Ward",
6
6
  "bin": {
7
7
  "minovative-mind-cli": "bin/run.js"