minovative-mind-cli 2.8.3 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/services/agent/slashCommands.js +31 -9
- package/dist/services/agent/syntaxAgent.d.ts +3 -1
- package/dist/services/agent/syntaxAgent.js +15 -5
- package/dist/services/agent/toolLoop.js +1 -1
- package/dist/services/agent.d.ts +1 -1
- package/dist/services/agent.js +96 -32
- package/dist/services/ai.d.ts +3 -3
- package/dist/services/ai.js +50 -13
- package/dist/services/auth.d.ts +7 -0
- package/dist/services/auth.js +41 -0
- package/dist/services/contextAgent.d.ts +2 -2
- package/dist/services/contextAgent.js +61 -10
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +12 -2
- package/dist/services/orchestration/orchestrator.d.ts +1 -1
- package/dist/services/orchestration/orchestrator.js +1 -1
- package/dist/services/orchestration/subAgent.js +1 -1
- package/dist/services/proxyClient.js +44 -2
- package/dist/utils/config.d.ts +3 -1
- package/dist/utils/config.js +3 -1
- package/dist/utils/symbolExtractor.d.ts +27 -0
- package/dist/utils/symbolExtractor.js +160 -67
- package/dist/utils/systemPrompts.d.ts +3 -3
- package/dist/utils/systemPrompts.js +3 -0
- package/dist/utils/terminal.d.ts +17 -0
- package/dist/utils/terminal.js +216 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,7 +88,8 @@ Hot-swap during a session using `/models`:
|
|
|
88
88
|
|
|
89
89
|
| Model | Best for |
|
|
90
90
|
| ------------------------- | --------------------------------------------------- |
|
|
91
|
-
| **Auto** (default) | Automatically selects (3.
|
|
91
|
+
| **Auto** (default) | Automatically selects (3.7 Flash or 3.5 Flash Lite) |
|
|
92
|
+
| **Gemini 3.7 Flash** | Next-gen performance, reasoning & fast execution |
|
|
92
93
|
| **Gemini 3.6 Flash** | Everyday coding — fast and accurate |
|
|
93
94
|
| **Gemini 3.1 Pro** | Complex architectural changes |
|
|
94
95
|
| **Gemini 3.5 Flash Lite** | Best for speed and cost efficiency |
|
|
@@ -9,9 +9,11 @@ import { toggleDebugMode, isDebugOn } from '../../utils/logger.js';
|
|
|
9
9
|
import { changeLogger } from '../changeLogger.js';
|
|
10
10
|
import { chatHistoryService } from '../chatHistoryService.js';
|
|
11
11
|
import { printLogo, brandBg, brandFg } from '../../utils/logo.js';
|
|
12
|
+
import { renderTerminalMarkdown } from '../../utils/terminal.js';
|
|
12
13
|
import { readPaste } from '../../utils/paste.js';
|
|
13
14
|
import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled } from '../agent-tools.js';
|
|
14
15
|
import { ProxyClient, getAndResetTurnUsage } from '../proxyClient.js';
|
|
16
|
+
import { checkByokSubscription } from '../auth.js';
|
|
15
17
|
import { GEMINI_MODELS, isByokEnabled } from '../../utils/config.js';
|
|
16
18
|
import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
|
|
17
19
|
import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '../ai.js';
|
|
@@ -24,7 +26,7 @@ import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '..
|
|
|
24
26
|
* - `/paste` : Enter multi-line paste mode using EOF tracking (`Ctrl+D` submission).
|
|
25
27
|
* - `/plan` : Toggle AI step-by-step implementation planning mode.
|
|
26
28
|
* - `/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).
|
|
29
|
+
* - `/models` : Hot-swap the active generative AI model (Gemini 3.1 Pro, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.5 Flash-Lite, Auto routing).
|
|
28
30
|
* - `/debug` : Toggle internal agent telemetry and diagnostic logging.
|
|
29
31
|
* - `/auto-approve`: Toggle automatic confirmation skipping for terminal shell execution commands.
|
|
30
32
|
* - `/sub-agents` : Toggle MMAAK Engine for parallel investigation sub-agent orchestration.
|
|
@@ -119,10 +121,15 @@ export async function handleSlashCommand(command, context) {
|
|
|
119
121
|
// label: 'Claude 5 Sonnet',
|
|
120
122
|
// hint: 'Best for raw speed and cost efficiency',
|
|
121
123
|
// },
|
|
124
|
+
{
|
|
125
|
+
value: 'gemini-3.7-flash',
|
|
126
|
+
label: 'Gemini 3.7 Flash',
|
|
127
|
+
hint: 'Next-gen reasoning, speed & balanced performance',
|
|
128
|
+
},
|
|
122
129
|
{
|
|
123
130
|
value: 'gemini-3.6-flash',
|
|
124
131
|
label: 'Gemini 3.6 Flash',
|
|
125
|
-
hint: '
|
|
132
|
+
hint: 'Everyday coding — fast and accurate',
|
|
126
133
|
},
|
|
127
134
|
{
|
|
128
135
|
value: 'gemini-3.5-flash-lite',
|
|
@@ -132,9 +139,9 @@ export async function handleSlashCommand(command, context) {
|
|
|
132
139
|
{
|
|
133
140
|
value: 'auto',
|
|
134
141
|
label: 'Auto (Flash-Lite / Flash)',
|
|
135
|
-
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.
|
|
142
|
+
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.7 Flash based on prompt complexity',
|
|
136
143
|
},
|
|
137
|
-
].filter(o => !(byokEnabled && o.value.includes('claude')));
|
|
144
|
+
].filter((o) => !(byokEnabled && o.value.includes('claude')));
|
|
138
145
|
const selectedModel = await p['select']({
|
|
139
146
|
message: `Select AI Model (Current: ${pc.cyan(currentModel)})`,
|
|
140
147
|
initialValue: currentModel,
|
|
@@ -622,9 +629,7 @@ export async function handleSlashCommand(command, context) {
|
|
|
622
629
|
.join('');
|
|
623
630
|
if (textParts.trim()) {
|
|
624
631
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim('(History)')}\n`);
|
|
625
|
-
|
|
626
|
-
const cleanText = textParts.replace(/\n([ \t]*\n){2,}/g, '\n\n');
|
|
627
|
-
console.log(marked.parse(cleanText));
|
|
632
|
+
console.log(renderTerminalMarkdown(textParts));
|
|
628
633
|
}
|
|
629
634
|
}
|
|
630
635
|
}
|
|
@@ -1047,12 +1052,29 @@ ${diffOut}
|
|
|
1047
1052
|
if (!byokEnabled && !creds.geminiApiKey) {
|
|
1048
1053
|
p.log.error('You must set an API key before enabling BYOK mode.');
|
|
1049
1054
|
}
|
|
1055
|
+
else if (!byokEnabled) {
|
|
1056
|
+
const subCheck = await checkByokSubscription();
|
|
1057
|
+
if (!subCheck.active) {
|
|
1058
|
+
p.log.error(subCheck.message ||
|
|
1059
|
+
'A $3.99/month BYOK Subscription is required. Visit https://www.minovativemind.dev/pricing');
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
await updateCredentialField('useByok', true);
|
|
1063
|
+
p.log.success(`BYOK mode is now ${pc.green('Enabled')}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1050
1066
|
else {
|
|
1051
|
-
await updateCredentialField('useByok',
|
|
1052
|
-
p.log.success(`BYOK mode is now ${
|
|
1067
|
+
await updateCredentialField('useByok', false);
|
|
1068
|
+
p.log.success(`BYOK mode is now ${pc.yellow('Disabled')}`);
|
|
1053
1069
|
}
|
|
1054
1070
|
}
|
|
1055
1071
|
else if (action === 'set') {
|
|
1072
|
+
const subCheck = await checkByokSubscription();
|
|
1073
|
+
if (!subCheck.active) {
|
|
1074
|
+
p.log.error(subCheck.message ||
|
|
1075
|
+
'A $3.99/month BYOK Subscription is required to use your own API key. Please visit https://www.minovativemind.dev/pricing to subscribe. The $3.99 is to cover account maintance for you.');
|
|
1076
|
+
return { shouldContinue: true };
|
|
1077
|
+
}
|
|
1056
1078
|
const key = await p['password']({
|
|
1057
1079
|
message: 'Enter your Google AI Studio API Key:',
|
|
1058
1080
|
validate: (v) => (!v ? 'API key is required' : undefined),
|
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
* @param error Optional error message from local validation to guide the AI.
|
|
9
9
|
* @returns The fixed content, or undefined if it was completely valid or couldn't be fixed.
|
|
10
10
|
*/
|
|
11
|
-
export declare function validateAndFixSyntax(content: string, filePath: string, error?: string): Promise<string | undefined>;
|
|
11
|
+
export declare function validateAndFixSyntax(content: string, filePath: string, error?: string, abortSignal?: AbortSignal): Promise<string | undefined>;
|
|
12
12
|
export interface AIFuzzyMatchOptions {
|
|
13
13
|
/** Optional custom Gemini model override. Defaults to GEMINI_MODELS.FLASH. */
|
|
14
14
|
model?: string;
|
|
15
15
|
/** Whether to validate and fix syntax on the updated content. Defaults to true. */
|
|
16
16
|
validateSyntax?: boolean;
|
|
17
|
+
/** Optional AbortSignal to cancel execution immediately. */
|
|
18
|
+
abortSignal?: AbortSignal;
|
|
17
19
|
}
|
|
18
20
|
export interface AIFuzzyMatchResult {
|
|
19
21
|
/** Indicates whether the fuzzy search matching and edit application succeeded. */
|
|
@@ -68,7 +68,9 @@ function cleanOutput(raw) {
|
|
|
68
68
|
* @param error Optional error message from local validation to guide the AI.
|
|
69
69
|
* @returns The fixed content, or undefined if it was completely valid or couldn't be fixed.
|
|
70
70
|
*/
|
|
71
|
-
export async function validateAndFixSyntax(content, filePath, error) {
|
|
71
|
+
export async function validateAndFixSyntax(content, filePath, error, abortSignal) {
|
|
72
|
+
if (abortSignal?.aborted)
|
|
73
|
+
return undefined;
|
|
72
74
|
// 1. First run fast local validation to check if content is already valid or gather error details
|
|
73
75
|
const localResult = localValidate(filePath, content);
|
|
74
76
|
if (localResult.isValid && !error) {
|
|
@@ -108,7 +110,7 @@ ${win.snippet}
|
|
|
108
110
|
|
|
109
111
|
Please fix the syntax error in the snippet above and return ONLY the raw repaired code snippet:`;
|
|
110
112
|
try {
|
|
111
|
-
const result = await snippetChat.sendMessage(snippetPrompt);
|
|
113
|
+
const result = await snippetChat.sendMessage(snippetPrompt, undefined, abortSignal);
|
|
112
114
|
const repairedSnippet = cleanOutput(result.response.text());
|
|
113
115
|
if (repairedSnippet && repairedSnippet !== win.snippet) {
|
|
114
116
|
const candidateContent = content.slice(0, win.startPos) + repairedSnippet + content.slice(win.endPos);
|
|
@@ -123,6 +125,8 @@ Please fix the syntax error in the snippet above and return ONLY the raw repaire
|
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
}
|
|
128
|
+
if (abortSignal?.aborted)
|
|
129
|
+
return undefined;
|
|
126
130
|
// 3. Full file repair strategy (for smaller files or when snippet repair was insufficient)
|
|
127
131
|
const fullSystemInstruction = `
|
|
128
132
|
<identity>
|
|
@@ -150,7 +154,7 @@ ${effectiveError}
|
|
|
150
154
|
Content:
|
|
151
155
|
${content}`;
|
|
152
156
|
try {
|
|
153
|
-
const result = await fullChat.sendMessage(fullPrompt);
|
|
157
|
+
const result = await fullChat.sendMessage(fullPrompt, undefined, abortSignal);
|
|
154
158
|
const output = cleanOutput(result.response.text());
|
|
155
159
|
if (output === 'VALID' || output === '' || output === content) {
|
|
156
160
|
return undefined;
|
|
@@ -185,6 +189,12 @@ export async function aiFuzzyMatch(fileContent, searchContent, replaceContent, f
|
|
|
185
189
|
error: 'File content and search content must not be empty.',
|
|
186
190
|
};
|
|
187
191
|
}
|
|
192
|
+
if (options.abortSignal?.aborted) {
|
|
193
|
+
return {
|
|
194
|
+
success: false,
|
|
195
|
+
error: 'Operation aborted',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
188
198
|
const model = options.model || GEMINI_MODELS.FLASH;
|
|
189
199
|
const shouldValidateSyntax = options.validateSyntax !== false;
|
|
190
200
|
const systemInstruction = `
|
|
@@ -217,7 +227,7 @@ ${replaceContent}
|
|
|
217
227
|
File Content:
|
|
218
228
|
${fileContent}`;
|
|
219
229
|
try {
|
|
220
|
-
const response = await chat.sendMessage(prompt);
|
|
230
|
+
const response = await chat.sendMessage(prompt, undefined, options.abortSignal);
|
|
221
231
|
const rawOutput = response.response.text();
|
|
222
232
|
const output = cleanOutput(rawOutput);
|
|
223
233
|
if (output === 'UNMATCHED' || !output || output === fileContent) {
|
|
@@ -231,7 +241,7 @@ ${fileContent}`;
|
|
|
231
241
|
const valResult = localValidate(filePath, finalContent);
|
|
232
242
|
if (!valResult.isValid) {
|
|
233
243
|
// Attempt automatic syntax repair on AI output if local validation detected syntax error
|
|
234
|
-
const repaired = await validateAndFixSyntax(finalContent, filePath, valResult.error);
|
|
244
|
+
const repaired = await validateAndFixSyntax(finalContent, filePath, valResult.error, options.abortSignal);
|
|
235
245
|
if (repaired) {
|
|
236
246
|
finalContent = repaired;
|
|
237
247
|
}
|
|
@@ -264,7 +264,7 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
|
|
|
264
264
|
additionalText = `[USER INTERRUPTION] The user sent the following message during your execution:\n"${queuedMsg}"\n\nPlease incorporate this feedback into your ongoing work. Address the user's message, but DO NOT lose track of your original overall plan or focus. After addressing this interruption, continue with your broader objective.`;
|
|
265
265
|
p.log.info(pc.cyan(`Sending queued message to AI...`));
|
|
266
266
|
// Dynamically upgrade agent permissions/intent to EXECUTE mode if the interrupted instruction requires system modifications
|
|
267
|
-
const newIntent = await routeIntent(queuedMsg);
|
|
267
|
+
const newIntent = await routeIntent(queuedMsg, '', abortSignal);
|
|
268
268
|
if (agentState.targetAgent === 'CHAT') {
|
|
269
269
|
if (newIntent.targetAgent === 'EXECUTE' || newIntent.needsContext) {
|
|
270
270
|
agentState.targetAgent = 'EXECUTE';
|
package/dist/services/agent.d.ts
CHANGED
|
@@ -125,4 +125,4 @@ export declare const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
|
125
125
|
* @param chat - The active ProxyChatSession instance.
|
|
126
126
|
* @returns A promise resolving to true if history was summarized and updated; false otherwise.
|
|
127
127
|
*/
|
|
128
|
-
export declare function summarizeHistoryIfNeeded(chat: any): Promise<boolean>;
|
|
128
|
+
export declare function summarizeHistoryIfNeeded(chat: any, abortSignal?: AbortSignal): Promise<boolean>;
|
package/dist/services/agent.js
CHANGED
|
@@ -21,8 +21,7 @@ import path from 'node:path';
|
|
|
21
21
|
import * as crypto from 'node:crypto';
|
|
22
22
|
import { exec } from 'node:child_process';
|
|
23
23
|
import { promisify } from 'node:util';
|
|
24
|
-
import {
|
|
25
|
-
import { markedTerminal } from 'marked-terminal';
|
|
24
|
+
import { renderTerminalMarkdown } from '../utils/terminal.js';
|
|
26
25
|
const execAsync = promisify(exec);
|
|
27
26
|
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
28
27
|
import { ensureProjectStorage, ensureIgnored, readCache, writeCache, invalidateCacheForDependents, } from '../utils/projectStorage.js';
|
|
@@ -44,7 +43,6 @@ import { getMetricCollector } from './metrics.js';
|
|
|
44
43
|
import { Orchestrator } from './orchestration/orchestrator.js';
|
|
45
44
|
import { isSubAgentsEnabled, getApprovalMode } from './agent-tools.js';
|
|
46
45
|
import { runWithAgentId } from '../utils/asyncContext.js';
|
|
47
|
-
marked.use(markedTerminal({ reflowText: false }));
|
|
48
46
|
// Export submodules for potential external uses if required
|
|
49
47
|
export { AsyncInputHandler } from './agent/inputHandler.js';
|
|
50
48
|
/**
|
|
@@ -317,15 +315,19 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
317
315
|
inputHandler.start(spinner);
|
|
318
316
|
changeLogger.startChangeSet(userInput);
|
|
319
317
|
let finalInput = userInput;
|
|
318
|
+
if (ac.signal.aborted)
|
|
319
|
+
return;
|
|
320
320
|
// Inter-turn cooling-off delay to allow API token buckets to settle before running intent routing
|
|
321
321
|
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.INTER_TURN_MS));
|
|
322
|
+
if (ac.signal.aborted)
|
|
323
|
+
return;
|
|
322
324
|
// Stage 0: Summarize chat history if length threshold is met
|
|
323
325
|
if (!cachedContextResult) {
|
|
324
326
|
const isDebug = isDebugOn();
|
|
325
327
|
if (!inputHandler.isCurrentlyPrompting() && !isDebug) {
|
|
326
328
|
spinner.start('Checking conversation history...');
|
|
327
329
|
}
|
|
328
|
-
const historySummarized = await summarizeHistoryIfNeeded(chat);
|
|
330
|
+
const historySummarized = await summarizeHistoryIfNeeded(chat, ac.signal);
|
|
329
331
|
if (historySummarized) {
|
|
330
332
|
debugLog('Chat history was summarized and compressed prior to context investigation.');
|
|
331
333
|
}
|
|
@@ -333,6 +335,8 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
333
335
|
spinner.stop();
|
|
334
336
|
}
|
|
335
337
|
}
|
|
338
|
+
if (ac.signal.aborted)
|
|
339
|
+
return;
|
|
336
340
|
// Stage 1: Gather Workspace Context and route intentions
|
|
337
341
|
spinner.start('🔍 Investigating workspace...');
|
|
338
342
|
let gatherRes = { targetAgent: 'EXECUTE', chainedMessages: [], contextResult: cachedContextResult || null };
|
|
@@ -342,19 +346,34 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
342
346
|
collector.startTimer('contextGather');
|
|
343
347
|
const chatHistory = chat.getRecentHistory(3);
|
|
344
348
|
const toolLogs = [];
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
try {
|
|
350
|
+
gatherRes = await gatherContext(workspaceRoot, userInput, chatHistory, inputHandler, ac.signal, (msg) => {
|
|
351
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
352
|
+
spinner.message(`🔍 Investigating workspace... ${pc.dim(msg)}`);
|
|
353
|
+
}
|
|
354
|
+
}, (toolMsg, label) => {
|
|
355
|
+
const formattedLog = `${pc.dim(`[${label}]`)} ${toolMsg}`;
|
|
356
|
+
toolLogs.push(formattedLog);
|
|
357
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
358
|
+
spinner.message(`🔍 Investigating workspace... ${formattedLog}`);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (gatherErr) {
|
|
363
|
+
spinner.stop();
|
|
364
|
+
process.stdout.write('\x1b[2K\r');
|
|
365
|
+
if (ac.signal.aborted || gatherErr?.name === 'AbortError' || gatherErr?.message?.includes('abort')) {
|
|
366
|
+
return;
|
|
354
367
|
}
|
|
355
|
-
|
|
368
|
+
throw gatherErr;
|
|
369
|
+
}
|
|
356
370
|
if (collector)
|
|
357
371
|
collector.stopTimer('contextGather');
|
|
372
|
+
if (ac.signal.aborted) {
|
|
373
|
+
spinner.stop();
|
|
374
|
+
process.stdout.write('\x1b[2K\r');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
358
377
|
// Print all collected logs at once to prevent flickering, while spinner is stopped
|
|
359
378
|
if (toolLogs.length > 0) {
|
|
360
379
|
spinner.stop();
|
|
@@ -363,6 +382,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
363
382
|
}
|
|
364
383
|
// Post-investigation cooling-off delay to allow API Tokens-Per-Minute (TPM) sliding window to settle
|
|
365
384
|
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.POST_INVESTIGATION_MS));
|
|
385
|
+
if (ac.signal.aborted) {
|
|
386
|
+
spinner.stop();
|
|
387
|
+
process.stdout.write('\x1b[2K\r');
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
366
390
|
}
|
|
367
391
|
let latestUsage = undefined;
|
|
368
392
|
// Collect any inputs that were queued while the Context Agent was investigating
|
|
@@ -374,7 +398,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
374
398
|
let effectiveTargetAgent = gatherRes.targetAgent;
|
|
375
399
|
if (gatherRes.chainedMessages.length > 0) {
|
|
376
400
|
const chainedContent = gatherRes.chainedMessages.join('\n');
|
|
377
|
-
const newIntent = await routeIntent(chainedContent);
|
|
401
|
+
const newIntent = await routeIntent(chainedContent, '', ac.signal);
|
|
378
402
|
if (gatherRes.contextResult !== null) {
|
|
379
403
|
// If the model previously needed search context, determine if followups change the mode
|
|
380
404
|
effectiveTargetAgent = newIntent.targetAgent;
|
|
@@ -394,6 +418,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
394
418
|
}
|
|
395
419
|
finalInput += `\n\n[USER FOLLOW-UP INSTRUCTIONS SENT DURING INVESTIGATION]:\n${chainedContent}\n\nPlease incorporate these instructions into your work. Address them appropriately, but ensure you do not lose track of the original request's primary objective.`;
|
|
396
420
|
}
|
|
421
|
+
if (ac.signal.aborted) {
|
|
422
|
+
spinner.stop();
|
|
423
|
+
process.stdout.write('\x1b[2K\r');
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
397
426
|
// Hot-swap the underlying LLM system instruction context depending on intent (conversational vs execution)
|
|
398
427
|
debugLog(`Intent Router output: original targetAgent = ${gatherRes.targetAgent}, effective = ${effectiveTargetAgent}`);
|
|
399
428
|
let config;
|
|
@@ -432,7 +461,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
432
461
|
else {
|
|
433
462
|
spinner.stop(); // MUST clear the investigation spinner first to prevent leaking the setInterval
|
|
434
463
|
spinner.start(pc.blue('🧠 Evaluating execution complexity...'));
|
|
435
|
-
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0);
|
|
464
|
+
const complexity = await evaluateExecutionComplexity(finalInput, gatherRes.contextResult?.summary, gatherRes.contextResult?.relevantFiles?.size || 0, '', ac.signal);
|
|
436
465
|
spinner.stop();
|
|
437
466
|
process.stdout.write('\x1b[2K\r');
|
|
438
467
|
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.FLASH_LITE : GEMINI_MODELS.FLASH;
|
|
@@ -443,6 +472,11 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
443
472
|
else {
|
|
444
473
|
chat.setModel(getGlobalActiveModel());
|
|
445
474
|
}
|
|
475
|
+
if (ac.signal.aborted) {
|
|
476
|
+
spinner.stop();
|
|
477
|
+
process.stdout.write('\x1b[2K\r');
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
446
480
|
if (gatherRes.contextResult) {
|
|
447
481
|
if (!inputHandler.isCurrentlyPrompting()) {
|
|
448
482
|
spinner.stop(pc.green('🔍 Investigation complete.'));
|
|
@@ -513,8 +547,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
513
547
|
});
|
|
514
548
|
// Print the summary text just like single-agent mode
|
|
515
549
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(Orchestrator)`)}\n`);
|
|
516
|
-
|
|
517
|
-
console.log(marked.parse(cleanText));
|
|
550
|
+
console.log(renderTerminalMarkdown(handledByOrchestrator));
|
|
518
551
|
}
|
|
519
552
|
// Print Usage Stats
|
|
520
553
|
const usage = getAndResetTurnUsage();
|
|
@@ -634,9 +667,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
634
667
|
const cleanFinalText = finalText.replace(/\[TASK_FINISHED\]/g, '').trim();
|
|
635
668
|
if (cleanFinalText) {
|
|
636
669
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(${chat.getModel()})`)}\n`);
|
|
637
|
-
|
|
638
|
-
const cleanText = cleanFinalText.replace(/\n([ \t]*\n){2,}/g, '\n\n');
|
|
639
|
-
console.log(marked.parse(cleanText));
|
|
670
|
+
console.log(renderTerminalMarkdown(cleanFinalText));
|
|
640
671
|
}
|
|
641
672
|
}
|
|
642
673
|
if (usage) {
|
|
@@ -715,7 +746,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
715
746
|
if (!chatSessionState.title) {
|
|
716
747
|
chatSessionState.title = 'Generating title...';
|
|
717
748
|
try {
|
|
718
|
-
const title = await generateChatTitle(userInput);
|
|
749
|
+
const title = await generateChatTitle(userInput, ac.signal);
|
|
719
750
|
chatSessionState.title = title;
|
|
720
751
|
await chatHistoryService.saveSession({
|
|
721
752
|
id: chatSessionState.id,
|
|
@@ -804,10 +835,15 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
804
835
|
// label: 'Claude 5 Sonnet',
|
|
805
836
|
// hint: 'Best for raw speed and cost efficiency',
|
|
806
837
|
// },
|
|
838
|
+
{
|
|
839
|
+
value: 'gemini-3.7-flash',
|
|
840
|
+
label: 'Gemini 3.7 Flash',
|
|
841
|
+
hint: 'Next-gen reasoning, speed & balanced performance',
|
|
842
|
+
},
|
|
807
843
|
{
|
|
808
844
|
value: 'gemini-3.6-flash',
|
|
809
845
|
label: 'Gemini 3.6 Flash',
|
|
810
|
-
hint: '
|
|
846
|
+
hint: 'Everyday coding — fast and accurate',
|
|
811
847
|
},
|
|
812
848
|
{
|
|
813
849
|
value: 'gemini-3.5-flash-lite',
|
|
@@ -817,7 +853,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
817
853
|
{
|
|
818
854
|
value: 'auto',
|
|
819
855
|
label: 'Auto (Flash-Lite / Flash)',
|
|
820
|
-
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.
|
|
856
|
+
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.7 Flash based on prompt complexity',
|
|
821
857
|
},
|
|
822
858
|
].filter((o) => !(byokEnabled && o.value.includes('claude')));
|
|
823
859
|
const selectedModel = await p['select']({
|
|
@@ -847,6 +883,9 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
847
883
|
return { planModeReturn, contextResult: gatherRes.contextResult };
|
|
848
884
|
}
|
|
849
885
|
catch (err) {
|
|
886
|
+
if (ac.signal.aborted || err?.name === 'AbortError' || err?.message?.includes('abort')) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
850
889
|
const message = err instanceof Error ? err.message : String(err);
|
|
851
890
|
p.log.error(`${pc.red('Error:')} ${message}`);
|
|
852
891
|
}
|
|
@@ -960,7 +999,7 @@ export const HISTORY_SUMMARIZATION_THRESHOLD = 50;
|
|
|
960
999
|
* @param chat - The active ProxyChatSession instance.
|
|
961
1000
|
* @returns A promise resolving to true if history was summarized and updated; false otherwise.
|
|
962
1001
|
*/
|
|
963
|
-
export async function summarizeHistoryIfNeeded(chat) {
|
|
1002
|
+
export async function summarizeHistoryIfNeeded(chat, abortSignal) {
|
|
964
1003
|
const history = chat.getRawHistory();
|
|
965
1004
|
if (!history || history.length < HISTORY_SUMMARIZATION_THRESHOLD) {
|
|
966
1005
|
return false;
|
|
@@ -971,7 +1010,7 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
971
1010
|
const olderHistory = history.slice(0, history.length - recentCount);
|
|
972
1011
|
const recentHistory = history.slice(history.length - recentCount);
|
|
973
1012
|
debugLog(`Summarizing ${olderHistory.length} older chat history entries...`);
|
|
974
|
-
const summaryText = await summarizeChatHistory(olderHistory);
|
|
1013
|
+
const summaryText = await summarizeChatHistory(olderHistory, abortSignal);
|
|
975
1014
|
if (summaryText && summaryText.trim().length > 0) {
|
|
976
1015
|
const summaryContent = [
|
|
977
1016
|
{
|
|
@@ -989,6 +1028,9 @@ export async function summarizeHistoryIfNeeded(chat) {
|
|
|
989
1028
|
}
|
|
990
1029
|
}
|
|
991
1030
|
catch (error) {
|
|
1031
|
+
if (abortSignal?.aborted || error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
992
1034
|
debugLog(`Failed to summarize chat history: ${error?.message || error}`);
|
|
993
1035
|
}
|
|
994
1036
|
return false;
|
|
@@ -1003,15 +1045,15 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
1003
1045
|
let intentVerified = false;
|
|
1004
1046
|
let isFixingCodeError = false;
|
|
1005
1047
|
while (correctionAttempts <= MAX_CORRECTIONS) {
|
|
1006
|
-
if (finalText === '[Generation stopped by user]')
|
|
1048
|
+
if (signal.aborted || finalText === '[Generation stopped by user]')
|
|
1007
1049
|
break;
|
|
1008
1050
|
const historyLengthBefore = chat.getRawHistory().length;
|
|
1009
1051
|
const currentText = await processResponse(chat, result, workspaceRoot, inputHandler, agentState, signal);
|
|
1010
1052
|
const historyLengthAfter = chat.getRawHistory().length;
|
|
1011
1053
|
const usedTools = historyLengthAfter > historyLengthBefore;
|
|
1012
1054
|
debugLog(`processResponse returned text (length ${currentText.length}): "${currentText.substring(0, 10)}..."`);
|
|
1013
|
-
if (currentText === '[Generation stopped by user]') {
|
|
1014
|
-
finalText =
|
|
1055
|
+
if (currentText === '[Generation stopped by user]' || signal.aborted) {
|
|
1056
|
+
finalText = '[Generation stopped by user]';
|
|
1015
1057
|
break;
|
|
1016
1058
|
}
|
|
1017
1059
|
if (currentText.startsWith('[The AI repeatedly returned empty responses')) {
|
|
@@ -1037,7 +1079,18 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
1037
1079
|
debugLog('AI failed to execute tools or modify files during the correction attempt. Retrying...');
|
|
1038
1080
|
const forcePrompt = `AUTOMATED SYSTEM CHECK: You did not execute any tools or modify files. You MUST use your tools (such as run_command or modify_file) to investigate and apply a fix for the previously mentioned errors. Do not just explain the issue.`;
|
|
1039
1081
|
spinner.start('Thinking (Correction)...');
|
|
1040
|
-
|
|
1082
|
+
try {
|
|
1083
|
+
result = await chat.sendMessage(forcePrompt, undefined, signal);
|
|
1084
|
+
}
|
|
1085
|
+
catch (e) {
|
|
1086
|
+
spinner.stop();
|
|
1087
|
+
process.stdout.write('\x1b[2K\r');
|
|
1088
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1089
|
+
p.log.warn(pc.yellow('Generation stopped by user during correction.'));
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
throw e;
|
|
1093
|
+
}
|
|
1041
1094
|
spinner.stop();
|
|
1042
1095
|
process.stdout.write('\x1b[2K\r');
|
|
1043
1096
|
correctionAttempts++;
|
|
@@ -1062,7 +1115,7 @@ If you are not done, please continue working using your other tools.`;
|
|
|
1062
1115
|
catch (e) {
|
|
1063
1116
|
spinner.stop();
|
|
1064
1117
|
process.stdout.write('\x1b[2K\r');
|
|
1065
|
-
if (e.name === 'AbortError' || e.message?.includes('abort')) {
|
|
1118
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1066
1119
|
p.log.warn(pc.yellow('Generation stopped by user during verification.'));
|
|
1067
1120
|
break;
|
|
1068
1121
|
}
|
|
@@ -1135,7 +1188,18 @@ If you are not done, please continue working using your other tools.`;
|
|
|
1135
1188
|
// Compile compilation and syntax diagnostic warnings into an auto-correction prompt
|
|
1136
1189
|
const correctionPrompt = `AUTOMATED SYSTEM CHECK: Your previous changes resulted in the following issues:\n\n${combinedIssuesForAI}\n\nPlease analyze these issues and use your file modification tools to fix them.`;
|
|
1137
1190
|
spinner.start('Thinking (Correction)...');
|
|
1138
|
-
|
|
1191
|
+
try {
|
|
1192
|
+
result = await chat.sendMessage(correctionPrompt, undefined, signal);
|
|
1193
|
+
}
|
|
1194
|
+
catch (e) {
|
|
1195
|
+
spinner.stop();
|
|
1196
|
+
process.stdout.write('\x1b[2K\r');
|
|
1197
|
+
if (e.name === 'AbortError' || e.message?.includes('abort') || signal.aborted) {
|
|
1198
|
+
p.log.warn(pc.yellow('Generation stopped by user during correction.'));
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
throw e;
|
|
1202
|
+
}
|
|
1139
1203
|
spinner.stop();
|
|
1140
1204
|
process.stdout.write('\x1b[2K\r');
|
|
1141
1205
|
}
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare function getPlanModeConfig(): {
|
|
|
80
80
|
* Compresses a large string of text using gemini-3.5-flash-lite.
|
|
81
81
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
82
82
|
*/
|
|
83
|
-
export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any, force?: boolean): Promise<string>;
|
|
83
|
+
export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any, force?: boolean, abortSignal?: AbortSignal): Promise<string>;
|
|
84
84
|
/**
|
|
85
85
|
* Returns the tool declarations for the read-only Context Agent.
|
|
86
86
|
* Extracted as a reusable function so investigation sub-agents can import
|
|
@@ -96,8 +96,8 @@ export declare function createHistorySummarizerSession(): any;
|
|
|
96
96
|
/**
|
|
97
97
|
* Summarizes an array of Content history entries using Gemini Flash Lite.
|
|
98
98
|
*/
|
|
99
|
-
export declare function summarizeChatHistory(history: Content[]): Promise<string>;
|
|
99
|
+
export declare function summarizeChatHistory(history: Content[], abortSignal?: AbortSignal): Promise<string>;
|
|
100
100
|
/**
|
|
101
101
|
* Generates a concise title for a chat session based on the user's first message.
|
|
102
102
|
*/
|
|
103
|
-
export declare function generateChatTitle(firstMessage: string): Promise<string>;
|
|
103
|
+
export declare function generateChatTitle(firstMessage: string, abortSignal?: AbortSignal): Promise<string>;
|