minovative-mind-cli 2.13.4 → 2.14.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 +104 -42
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.js +1 -2
- package/dist/services/agent/slashCommands.js +47 -23
- package/dist/services/agent/syntaxAgent.js +13 -0
- package/dist/services/agent/types.d.ts +0 -4
- package/dist/services/agent-tools.d.ts +1 -1
- package/dist/services/agent-tools.js +2 -2
- package/dist/services/agent.d.ts +1 -7
- package/dist/services/agent.js +184 -209
- package/dist/services/ai.d.ts +19 -4
- package/dist/services/ai.js +97 -12
- package/dist/services/contextAgent.js +33 -8
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/mentionEngine.d.ts +385 -0
- package/dist/services/mentionEngine.js +1395 -0
- package/dist/services/orchestration/investigationAgent.js +4 -1
- package/dist/services/orchestration/messageBus.d.ts +27 -4
- package/dist/services/orchestration/messageBus.js +206 -22
- package/dist/services/orchestration/orchestrator.js +4 -0
- package/dist/services/orchestration/scopedTools.js +16 -2
- package/dist/services/orchestration/subAgent.js +14 -2
- package/dist/services/proxyClient.d.ts +38 -2
- package/dist/services/proxyClient.js +42 -24
- package/dist/services/swebench/sweBenchRunnerService.js +1 -1
- package/dist/utils/config.d.ts +75 -0
- package/dist/utils/config.js +93 -0
- package/dist/utils/contextPrompts.d.ts +28 -4
- package/dist/utils/contextPrompts.js +70 -1
- package/dist/utils/historyPrompt.d.ts +166 -7
- package/dist/utils/historyPrompt.js +775 -30
- package/dist/utils/symbolExtractor.d.ts +111 -8
- package/dist/utils/symbolExtractor.js +616 -64
- package/dist/utils/systemPrompts.d.ts +3 -4
- package/dist/utils/systemPrompts.js +5 -34
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
package/dist/services/agent.js
CHANGED
|
@@ -25,8 +25,8 @@ import { renderTerminalMarkdown } from '../utils/terminal.js';
|
|
|
25
25
|
const execAsync = promisify(exec);
|
|
26
26
|
import { debugLog, isDebugOn } from '../utils/logger.js';
|
|
27
27
|
import { ensureProjectStorage, ensureIgnored, readCache, writeCache, invalidateCacheForDependents, } from '../utils/projectStorage.js';
|
|
28
|
-
import { GEMINI_MODELS,
|
|
29
|
-
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel,
|
|
28
|
+
import { GEMINI_MODELS, TPM_COOLING_DELAYS } from '../utils/config.js';
|
|
29
|
+
import { createSharedChatSession, getGeneralChatConfig, getPlanExecutionConfig, compressTextUsingFlashLite, generateChatTitle, getGlobalActiveModel, getModelThinkingLevel, summarizeChatHistory, } from './ai.js';
|
|
30
30
|
import { getAndResetTurnUsage } from './proxyClient.js';
|
|
31
31
|
import { changeLogger } from './changeLogger.js';
|
|
32
32
|
import { chatHistoryService } from './chatHistoryService.js';
|
|
@@ -35,7 +35,7 @@ import { verifyChangedFiles } from './verificationService.js';
|
|
|
35
35
|
import { buildContextInjection } from '../utils/contextPrompts.js';
|
|
36
36
|
import { registerContextFiles } from '../utils/fileReadGuard.js';
|
|
37
37
|
import { historyText } from '../utils/historyPrompt.js';
|
|
38
|
-
import { loadUserProfile, formatUserProfileForContext, extractAndSaveUserInsights
|
|
38
|
+
import { loadUserProfile, formatUserProfileForContext, extractAndSaveUserInsights } from './userProfileService.js';
|
|
39
39
|
// Submodule Imports
|
|
40
40
|
import { AsyncInputHandler } from './agent/inputHandler.js';
|
|
41
41
|
import { processResponse } from './agent/toolLoop.js';
|
|
@@ -130,7 +130,6 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
130
130
|
chat.setSessionInfo(chatSessionState.id, workspaceRoot);
|
|
131
131
|
const sessionInputHistory = [];
|
|
132
132
|
let isRawPasteMode = false;
|
|
133
|
-
let isPlanMode = false;
|
|
134
133
|
// Hook process.stdin.emit to intercept fast stream inputs.
|
|
135
134
|
// When large buffers containing newlines arrive rapidly, we interpret them as a clipboard paste,
|
|
136
135
|
// sanitizing and collapsing them to prevent premature command line submission.
|
|
@@ -165,7 +164,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
165
164
|
}
|
|
166
165
|
inputHandler.stop();
|
|
167
166
|
// Input collection (delegated to a helper function to avoid nested loop warning)
|
|
168
|
-
const { userInput: rawInput, canceled } = await collectUserInput(sessionInputHistory,
|
|
167
|
+
const { userInput: rawInput, canceled } = await collectUserInput(sessionInputHistory, workspaceRoot);
|
|
169
168
|
if (canceled) {
|
|
170
169
|
p.outro(pc.green('Goodbye! Happy coding. 🚀'));
|
|
171
170
|
return;
|
|
@@ -180,11 +179,6 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
180
179
|
message: 'Command Menu',
|
|
181
180
|
options: [
|
|
182
181
|
{ value: '/models', label: '/models', hint: 'Change the active AI model' },
|
|
183
|
-
{
|
|
184
|
-
value: '/plan',
|
|
185
|
-
label: '/plan',
|
|
186
|
-
hint: `Toggle plan mode (build a plan without executing) ${isPlanMode ? pc.green('(ON)') : pc.red('(OFF)')}`,
|
|
187
|
-
},
|
|
188
182
|
{ value: '/paste', label: '/paste', hint: 'Paste large text directly into the CLI (Press Ctrl+D to submit)' },
|
|
189
183
|
{ value: '/clear', label: '/clear', hint: 'Clear chat session history' },
|
|
190
184
|
{
|
|
@@ -244,16 +238,12 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
244
238
|
workspaceRoot,
|
|
245
239
|
version,
|
|
246
240
|
isRawPasteMode,
|
|
247
|
-
isPlanMode,
|
|
248
241
|
chatSessionState,
|
|
249
242
|
};
|
|
250
243
|
const slashResult = await handleSlashCommand(userInput, slashCtx);
|
|
251
244
|
if (slashResult.isRawPasteMode !== undefined) {
|
|
252
245
|
isRawPasteMode = slashResult.isRawPasteMode;
|
|
253
246
|
}
|
|
254
|
-
if (slashResult.isPlanModeOverride !== undefined) {
|
|
255
|
-
isPlanMode = slashResult.isPlanModeOverride;
|
|
256
|
-
}
|
|
257
247
|
if (slashResult.userInputOverride !== undefined) {
|
|
258
248
|
userInput = slashResult.userInputOverride;
|
|
259
249
|
}
|
|
@@ -262,32 +252,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
262
252
|
}
|
|
263
253
|
}
|
|
264
254
|
// ─── Main Execution Cycle ────────────────────────────────────────
|
|
265
|
-
|
|
266
|
-
let cachedContextResult = null;
|
|
267
|
-
while (true) {
|
|
268
|
-
const execResult = await executeSingleTurn(workspaceRoot, currentInput, chat, inputHandler, chatSessionState, isPlanMode, cachedContextResult);
|
|
269
|
-
const planModeReturn = execResult?.planModeReturn;
|
|
270
|
-
const contextResult = execResult?.contextResult;
|
|
271
|
-
if (isPlanMode && planModeReturn) {
|
|
272
|
-
if (planModeReturn === 'proceed') {
|
|
273
|
-
isPlanMode = false;
|
|
274
|
-
currentInput = 'Please execute the plan we just finalized.';
|
|
275
|
-
cachedContextResult = contextResult;
|
|
276
|
-
p.log.info(pc.cyan('Proceeding with execution...'));
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
else if (planModeReturn === 'edit') {
|
|
280
|
-
p.log.info(pc.cyan('Please provide additional instructions to revise the plan.'));
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
else if (planModeReturn === 'exit') {
|
|
284
|
-
isPlanMode = false;
|
|
285
|
-
p.log.info(pc.cyan('Exited plan mode. Returning to normal chat.'));
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
break;
|
|
290
|
-
}
|
|
255
|
+
await executeSingleTurn(workspaceRoot, userInput, chat, inputHandler, chatSessionState);
|
|
291
256
|
}
|
|
292
257
|
}
|
|
293
258
|
/**
|
|
@@ -301,11 +266,8 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
301
266
|
* @param chat - The active ProxyChatSession instance maintaining message history.
|
|
302
267
|
* @param inputHandler - The AsyncInputHandler instance for managing keyboard interrupts and queueing.
|
|
303
268
|
* @param chatSessionState - Object tracking session statistics, token counts, credit usage, and model metrics.
|
|
304
|
-
* @param isPlanMode - Flag indicating whether plan mode (read-only architecture planning) is active.
|
|
305
|
-
* @param cachedContextResult - Optional pre-gathered context result to bypass duplicate investigation turns.
|
|
306
|
-
* @returns An object containing optional plan mode actions or context results, or void.
|
|
307
269
|
*/
|
|
308
|
-
export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHandler, chatSessionState
|
|
270
|
+
export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHandler, chatSessionState) {
|
|
309
271
|
return runWithAgentId('main', async () => {
|
|
310
272
|
const { resetTurnAccumulator } = await import('./metrics.js');
|
|
311
273
|
resetTurnAccumulator();
|
|
@@ -324,71 +286,67 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
324
286
|
if (ac.signal.aborted)
|
|
325
287
|
return;
|
|
326
288
|
// Stage 0: Summarize chat history if length threshold is met
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
spinner.stop();
|
|
338
|
-
}
|
|
289
|
+
const isDebug = isDebugOn();
|
|
290
|
+
if (!inputHandler.isCurrentlyPrompting() && !isDebug) {
|
|
291
|
+
spinner.start('Checking conversation history...');
|
|
292
|
+
}
|
|
293
|
+
const historySummarized = await summarizeHistoryIfNeeded(chat, ac.signal);
|
|
294
|
+
if (historySummarized) {
|
|
295
|
+
debugLog('Chat history was summarized and compressed prior to context investigation.');
|
|
296
|
+
}
|
|
297
|
+
if (!inputHandler.isCurrentlyPrompting() && !isDebug) {
|
|
298
|
+
spinner.stop();
|
|
339
299
|
}
|
|
340
300
|
if (ac.signal.aborted)
|
|
341
301
|
return;
|
|
342
302
|
// Stage 1: Gather Workspace Context and route intentions
|
|
343
303
|
spinner.start('🔍 Investigating workspace...');
|
|
344
|
-
let gatherRes = { targetAgent: 'EXECUTE', chainedMessages: []
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
spinner.message(`🔍 Investigating workspace... ${pc.dim(msg)}`);
|
|
355
|
-
}
|
|
356
|
-
}, (toolMsg, label) => {
|
|
357
|
-
const formattedLog = `${pc.dim(`[${label}]`)} ${toolMsg}`;
|
|
358
|
-
toolLogs.push(formattedLog);
|
|
359
|
-
if (!inputHandler.isCurrentlyPrompting()) {
|
|
360
|
-
spinner.message(`🔍 Investigating workspace... ${formattedLog}`);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
catch (gatherErr) {
|
|
365
|
-
spinner.stop();
|
|
366
|
-
process.stdout.write('\x1b[2K\r');
|
|
367
|
-
if (ac.signal.aborted || gatherErr?.name === 'AbortError' || gatherErr?.message?.includes('abort')) {
|
|
368
|
-
return;
|
|
304
|
+
let gatherRes = { targetAgent: 'EXECUTE', chainedMessages: [] };
|
|
305
|
+
const collector = getMetricCollector();
|
|
306
|
+
if (collector)
|
|
307
|
+
collector.startTimer('contextGather');
|
|
308
|
+
const chatHistory = chat.getRecentHistory(3);
|
|
309
|
+
const toolLogs = [];
|
|
310
|
+
try {
|
|
311
|
+
gatherRes = await gatherContext(workspaceRoot, userInput, chatHistory, inputHandler, ac.signal, (msg) => {
|
|
312
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
313
|
+
spinner.message(`🔍 Investigating workspace... ${pc.dim(msg)}`);
|
|
369
314
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
toolLogs.forEach((log) => p.log.step(log));
|
|
383
|
-
spinner.start(`🔍 Context gathered successfully.`);
|
|
384
|
-
}
|
|
385
|
-
// Post-investigation cooling-off delay to allow API Tokens-Per-Minute (TPM) sliding window to settle
|
|
386
|
-
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.POST_INVESTIGATION_MS));
|
|
387
|
-
if (ac.signal.aborted) {
|
|
388
|
-
spinner.stop();
|
|
389
|
-
process.stdout.write('\x1b[2K\r');
|
|
315
|
+
}, (toolMsg, label) => {
|
|
316
|
+
const formattedLog = `${pc.dim(`[${label}]`)} ${toolMsg}`;
|
|
317
|
+
toolLogs.push(formattedLog);
|
|
318
|
+
if (!inputHandler.isCurrentlyPrompting()) {
|
|
319
|
+
spinner.message(`🔍 Investigating workspace... ${formattedLog}`);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
catch (gatherErr) {
|
|
324
|
+
spinner.stop();
|
|
325
|
+
process.stdout.write('\x1b[2K\r');
|
|
326
|
+
if (ac.signal.aborted || gatherErr?.name === 'AbortError' || gatherErr?.message?.includes('abort')) {
|
|
390
327
|
return;
|
|
391
328
|
}
|
|
329
|
+
throw gatherErr;
|
|
330
|
+
}
|
|
331
|
+
if (collector)
|
|
332
|
+
collector.stopTimer('contextGather');
|
|
333
|
+
if (ac.signal.aborted) {
|
|
334
|
+
spinner.stop();
|
|
335
|
+
process.stdout.write('\x1b[2K\r');
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
// Print all collected logs at once to prevent flickering, while spinner is stopped
|
|
339
|
+
if (toolLogs.length > 0) {
|
|
340
|
+
spinner.stop();
|
|
341
|
+
toolLogs.forEach((log) => p.log.step(log));
|
|
342
|
+
spinner.start(`🔍 Context gathered successfully.`);
|
|
343
|
+
}
|
|
344
|
+
// Post-investigation cooling-off delay to allow API Tokens-Per-Minute (TPM) sliding window to settle
|
|
345
|
+
await new Promise((resolve) => setTimeout(resolve, TPM_COOLING_DELAYS.POST_INVESTIGATION_MS));
|
|
346
|
+
if (ac.signal.aborted) {
|
|
347
|
+
spinner.stop();
|
|
348
|
+
process.stdout.write('\x1b[2K\r');
|
|
349
|
+
return;
|
|
392
350
|
}
|
|
393
351
|
let latestUsage = undefined;
|
|
394
352
|
// Collect any inputs that were queued while the Context Agent was investigating
|
|
@@ -427,21 +385,12 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
427
385
|
}
|
|
428
386
|
// Hot-swap the underlying LLM system instruction context depending on intent (conversational vs execution)
|
|
429
387
|
debugLog(`Intent Router output: original targetAgent = ${gatherRes.targetAgent}, effective = ${effectiveTargetAgent}`);
|
|
430
|
-
|
|
431
|
-
if (isPlanMode) {
|
|
432
|
-
const { getPlanModeConfig } = await import('./ai.js');
|
|
433
|
-
config = getPlanModeConfig();
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
config = effectiveTargetAgent === 'CHAT' ? getGeneralChatConfig() : getPlanExecutionConfig();
|
|
437
|
-
}
|
|
388
|
+
const config = effectiveTargetAgent === 'CHAT' ? getGeneralChatConfig() : getPlanExecutionConfig();
|
|
438
389
|
let dynamicSystemInstruction = config.systemInstruction;
|
|
439
390
|
// Inject gathered workspace directories, dependency configurations, and matching search patterns
|
|
440
391
|
if (gatherRes.contextResult) {
|
|
441
392
|
// Compress each relevant file individually using helper to avoid nested loop warning
|
|
442
|
-
|
|
443
|
-
gatherRes.contextResult.relevantFiles = await compressContextFiles(workspaceRoot, gatherRes.contextResult, ac.signal);
|
|
444
|
-
}
|
|
393
|
+
gatherRes.contextResult.relevantFiles = await compressContextFiles(workspaceRoot, gatherRes.contextResult, ac.signal);
|
|
445
394
|
// Assemble the final context injection string
|
|
446
395
|
const contextInjection = buildContextInjection(gatherRes.contextResult);
|
|
447
396
|
debugLog(`Final compressed context injection size: ${contextInjection.length} chars`);
|
|
@@ -453,6 +402,78 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
453
402
|
collector.recordCompressedContextSize(contextInjection.length);
|
|
454
403
|
dynamicSystemInstruction += '\n\n' + contextInjection;
|
|
455
404
|
}
|
|
405
|
+
// Resolve and inject explicit @ Context Mentions (files, symbols, git diffs, diagnostics, external workspaces)
|
|
406
|
+
try {
|
|
407
|
+
const { mentionEngine } = await import('./mentionEngine.js');
|
|
408
|
+
const resolvedMentions = await mentionEngine.resolveMentions(userInput, workspaceRoot);
|
|
409
|
+
if (resolvedMentions.hasMentions) {
|
|
410
|
+
const isPrompting = inputHandler.isCurrentlyPrompting();
|
|
411
|
+
if (!isPrompting) {
|
|
412
|
+
spinner.stop();
|
|
413
|
+
}
|
|
414
|
+
for (const item of resolvedMentions.mentions) {
|
|
415
|
+
if (item.resolved) {
|
|
416
|
+
const label = item.statusLabel ? ` ${pc.dim(`(${item.statusLabel})`)}` : '';
|
|
417
|
+
p.log.step(`${pc.green('✔')} Context ${pc.cyan(item.mention.raw)}${label}`);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
const reason = item.error ? ` ${pc.dim(`(${item.error})`)}` : '';
|
|
421
|
+
p.log.warn(`${pc.red('✖')} Context ${pc.yellow(item.mention.raw)}${reason}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
if (!isPrompting) {
|
|
425
|
+
spinner.start('Preparing agent configuration...');
|
|
426
|
+
}
|
|
427
|
+
if (resolvedMentions.formattedContext) {
|
|
428
|
+
debugLog(`Resolved ${resolvedMentions.mentions.length} context mention(s) (${resolvedMentions.totalTokens} tokens estimated)`);
|
|
429
|
+
dynamicSystemInstruction += '\n\n' + resolvedMentions.formattedContext;
|
|
430
|
+
// Pre-register all resolved file mention paths with the read-guard
|
|
431
|
+
const mentionedFiles = resolvedMentions.mentions
|
|
432
|
+
.filter((m) => m.mention.type === 'file' && m.resolved)
|
|
433
|
+
.map((m) => m.mention.target);
|
|
434
|
+
if (mentionedFiles.length > 0) {
|
|
435
|
+
registerContextFiles(mentionedFiles);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// Also resolve mentions from any chained follow-up instructions received during investigation
|
|
440
|
+
if (gatherRes.chainedMessages.length > 0) {
|
|
441
|
+
const chainedContent = gatherRes.chainedMessages.join('\n');
|
|
442
|
+
const chainedMentions = await mentionEngine.resolveMentions(chainedContent, workspaceRoot);
|
|
443
|
+
if (chainedMentions.hasMentions) {
|
|
444
|
+
const isPrompting = inputHandler.isCurrentlyPrompting();
|
|
445
|
+
if (!isPrompting) {
|
|
446
|
+
spinner.stop();
|
|
447
|
+
}
|
|
448
|
+
for (const item of chainedMentions.mentions) {
|
|
449
|
+
if (item.resolved) {
|
|
450
|
+
const label = item.statusLabel ? ` ${pc.dim(`(${item.statusLabel})`)}` : '';
|
|
451
|
+
p.log.step(`${pc.green('✔')} Chained Context ${pc.cyan(item.mention.raw)}${label}`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
const reason = item.error ? ` ${pc.dim(`(${item.error})`)}` : '';
|
|
455
|
+
p.log.warn(`${pc.red('✖')} Chained Context ${pc.yellow(item.mention.raw)}${reason}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (!isPrompting) {
|
|
459
|
+
spinner.start('Preparing agent configuration...');
|
|
460
|
+
}
|
|
461
|
+
if (chainedMentions.formattedContext) {
|
|
462
|
+
debugLog(`Resolved ${chainedMentions.mentions.length} chained context mention(s) (${chainedMentions.totalTokens} tokens estimated)`);
|
|
463
|
+
dynamicSystemInstruction += '\n\n' + chainedMentions.formattedContext;
|
|
464
|
+
const chainedFiles = chainedMentions.mentions
|
|
465
|
+
.filter((m) => m.mention.type === 'file' && m.resolved)
|
|
466
|
+
.map((m) => m.mention.target);
|
|
467
|
+
if (chainedFiles.length > 0) {
|
|
468
|
+
registerContextFiles(chainedFiles);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch (mentionErr) {
|
|
475
|
+
debugLog(`Context mention resolution encountered an error: ${mentionErr?.message || mentionErr}`);
|
|
476
|
+
}
|
|
456
477
|
// Inject global adaptive user persona and learned preferences
|
|
457
478
|
try {
|
|
458
479
|
const userProfile = await loadUserProfile();
|
|
@@ -480,10 +501,20 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
480
501
|
selectedModel = complexity === 'EASY' ? GEMINI_MODELS.FLASH_LITE : GEMINI_MODELS.FLASH;
|
|
481
502
|
}
|
|
482
503
|
chat.setModel(selectedModel);
|
|
483
|
-
|
|
504
|
+
const thinkingLevel = getModelThinkingLevel(selectedModel);
|
|
505
|
+
if (typeof chat.setThinkingLevel === 'function') {
|
|
506
|
+
chat.setThinkingLevel(thinkingLevel);
|
|
507
|
+
}
|
|
508
|
+
debugLog(`Auto-routing to model ${selectedModel} (thinking: ${thinkingLevel}) based on intent ${effectiveTargetAgent}`);
|
|
484
509
|
}
|
|
485
510
|
else {
|
|
486
|
-
|
|
511
|
+
const activeModel = getGlobalActiveModel();
|
|
512
|
+
chat.setModel(activeModel);
|
|
513
|
+
const thinkingLevel = getModelThinkingLevel(activeModel);
|
|
514
|
+
if (typeof chat.setThinkingLevel === 'function') {
|
|
515
|
+
chat.setThinkingLevel(thinkingLevel);
|
|
516
|
+
}
|
|
517
|
+
debugLog(`Active model set to ${activeModel} (thinking: ${thinkingLevel})`);
|
|
487
518
|
}
|
|
488
519
|
if (ac.signal.aborted) {
|
|
489
520
|
spinner.stop();
|
|
@@ -510,7 +541,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
510
541
|
gitBranch = stdout.trim();
|
|
511
542
|
}
|
|
512
543
|
catch { }
|
|
513
|
-
if (
|
|
544
|
+
if (effectiveTargetAgent === 'EXECUTE' && isSubAgentsEnabled()) {
|
|
514
545
|
spinner.stop(); // Clear the spinner before delegating
|
|
515
546
|
inputHandler.clearSpinner(); // Prevent prompt from reviving the old spinner
|
|
516
547
|
process.stdout.write('\x1b[2K\r');
|
|
@@ -650,6 +681,10 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
650
681
|
// Send the formulated prompt payload to the generative model
|
|
651
682
|
let result;
|
|
652
683
|
try {
|
|
684
|
+
const activeModel = chat.getModel();
|
|
685
|
+
const currentThinking = (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
|
|
686
|
+
getModelThinkingLevel(activeModel);
|
|
687
|
+
debugLog(`Dispatching turn to model ${activeModel} (thinking: ${currentThinking})`);
|
|
653
688
|
result = await chat.sendMessage(finalInput, undefined, ac.signal);
|
|
654
689
|
}
|
|
655
690
|
catch (e) {
|
|
@@ -673,7 +708,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
673
708
|
const calls = result.response.functionCalls();
|
|
674
709
|
debugLog(`Initial functionCalls: ${calls && calls.length > 0 ? JSON.stringify(calls) : 'None'}`);
|
|
675
710
|
// Stage 2: Recursive Tool Loops and Automated Self-Correction (delegated to a helper function to avoid nested loop warning)
|
|
676
|
-
const correctionRes = await executeSelfCorrectionLoop(chat, result, workspaceRoot, inputHandler, effectiveTargetAgent, ac.signal, spinner, userInput
|
|
711
|
+
const correctionRes = await executeSelfCorrectionLoop(chat, result, workspaceRoot, inputHandler, effectiveTargetAgent, ac.signal, spinner, userInput);
|
|
677
712
|
const finalText = correctionRes.finalText;
|
|
678
713
|
inputHandler.stop();
|
|
679
714
|
// Update latest usage metadata to reflect all completed turns
|
|
@@ -685,8 +720,9 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
685
720
|
if (modifiedFiles.length > 0) {
|
|
686
721
|
await invalidateCacheForDependents(workspaceRoot, modifiedFiles);
|
|
687
722
|
}
|
|
723
|
+
let cleanFinalText = '';
|
|
688
724
|
if (finalText) {
|
|
689
|
-
|
|
725
|
+
cleanFinalText = finalText.replace(/\[TASK_FINISHED\]/g, '').trim();
|
|
690
726
|
if (cleanFinalText) {
|
|
691
727
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(${chat.getModel()})`)}\n`);
|
|
692
728
|
console.log(renderTerminalMarkdown(cleanFinalText));
|
|
@@ -824,93 +860,6 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
824
860
|
})
|
|
825
861
|
.catch((e) => debugLog('Failed to auto-save session: ' + e));
|
|
826
862
|
}
|
|
827
|
-
let planModeReturn = undefined;
|
|
828
|
-
if (isPlanMode && finalText && finalText !== '[Generation stopped.]') {
|
|
829
|
-
inputHandler.stop(); // MUST stop the async input handler before showing an interactive prompt, or it will intercept stdin and freeze/duplicate
|
|
830
|
-
// Ensure the terminal has fully scrolled and there is a clean line break
|
|
831
|
-
console.log('\n');
|
|
832
|
-
// Small delay to allow stdout to flush and terminal to finish scrolling from the markdown output
|
|
833
|
-
// This prevents @clack/prompts from miscalculating the cursor position and duplicating the prompt on arrow keys
|
|
834
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
835
|
-
while (true) {
|
|
836
|
-
const currentModel = getGlobalActiveModel();
|
|
837
|
-
const planAction = await p['select']({
|
|
838
|
-
message: 'Plan generated. What would you like to do?',
|
|
839
|
-
options: [
|
|
840
|
-
{ value: 'proceed', label: 'Proceed with plan', hint: 'Executes the generated plan immediately' },
|
|
841
|
-
{ value: 'edit', label: 'Edit plan', hint: 'Provide additional feedback to revise the plan' },
|
|
842
|
-
{
|
|
843
|
-
value: 'choose_model',
|
|
844
|
-
label: 'Choose AI model',
|
|
845
|
-
hint: `Change the active AI model (Current: ${currentModel})`,
|
|
846
|
-
},
|
|
847
|
-
{ value: 'exit', label: 'Exit plan mode', hint: 'Return to normal chat without executing' },
|
|
848
|
-
],
|
|
849
|
-
});
|
|
850
|
-
if (planAction === 'choose_model') {
|
|
851
|
-
const byokEnabled = await isByokEnabled();
|
|
852
|
-
const options = [
|
|
853
|
-
// {
|
|
854
|
-
// value: 'claude-opus-5',
|
|
855
|
-
// label: 'Claude 5 Opus',
|
|
856
|
-
// hint: 'For hardcore & complex problems',
|
|
857
|
-
// },
|
|
858
|
-
{
|
|
859
|
-
value: 'gemini-3.1-pro-preview',
|
|
860
|
-
label: 'Gemini 3.1 Pro',
|
|
861
|
-
hint: 'For hardcore & complex problems',
|
|
862
|
-
},
|
|
863
|
-
// {
|
|
864
|
-
// value: 'claude-sonnet-5',
|
|
865
|
-
// label: 'Claude 5 Sonnet',
|
|
866
|
-
// hint: 'Best for raw speed and cost efficiency',
|
|
867
|
-
// },
|
|
868
|
-
{
|
|
869
|
-
value: 'gemini-3.7-flash',
|
|
870
|
-
label: 'Gemini 3.7 Flash',
|
|
871
|
-
hint: 'Next-gen reasoning, speed & balanced performance',
|
|
872
|
-
},
|
|
873
|
-
{
|
|
874
|
-
value: 'gemini-3.6-flash',
|
|
875
|
-
label: 'Gemini 3.6 Flash',
|
|
876
|
-
hint: 'Everyday coding — fast and accurate',
|
|
877
|
-
},
|
|
878
|
-
{
|
|
879
|
-
value: 'gemini-3.5-flash-lite',
|
|
880
|
-
label: 'Gemini 3.5 Flash-Lite',
|
|
881
|
-
hint: 'Best for raw speed and cost efficiency',
|
|
882
|
-
},
|
|
883
|
-
{
|
|
884
|
-
value: 'auto',
|
|
885
|
-
label: 'Auto (Flash-Lite / Flash)',
|
|
886
|
-
hint: 'Dynamically routes between Gemini 3.5 Flash-Lite and Gemini 3.7 Flash based on prompt complexity',
|
|
887
|
-
},
|
|
888
|
-
].filter((o) => !(byokEnabled && o.value.includes('claude')));
|
|
889
|
-
const selectedModel = await p['select']({
|
|
890
|
-
message: `Select AI Model (Current: ${pc.cyan(currentModel)})`,
|
|
891
|
-
initialValue: currentModel,
|
|
892
|
-
options: options,
|
|
893
|
-
});
|
|
894
|
-
if (!p.isCancel(selectedModel)) {
|
|
895
|
-
chat.setModel(selectedModel);
|
|
896
|
-
setGlobalActiveModel(selectedModel);
|
|
897
|
-
p.log.success(`Model successfully switched to ${pc.cyan(selectedModel)}`);
|
|
898
|
-
}
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
if (p.isCancel(planAction) || planAction === 'exit') {
|
|
902
|
-
planModeReturn = 'exit';
|
|
903
|
-
}
|
|
904
|
-
else if (planAction === 'edit') {
|
|
905
|
-
planModeReturn = 'edit';
|
|
906
|
-
}
|
|
907
|
-
else if (planAction === 'proceed') {
|
|
908
|
-
planModeReturn = 'proceed';
|
|
909
|
-
}
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return { planModeReturn, contextResult: gatherRes.contextResult };
|
|
914
863
|
}
|
|
915
864
|
catch (err) {
|
|
916
865
|
if (ac.signal.aborted || err?.name === 'AbortError' || err?.message?.includes('abort')) {
|
|
@@ -931,14 +880,40 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
931
880
|
}
|
|
932
881
|
});
|
|
933
882
|
}
|
|
934
|
-
async function collectUserInput(history,
|
|
883
|
+
async function collectUserInput(history, workspaceRoot = process.cwd()) {
|
|
935
884
|
let lines = [];
|
|
936
885
|
let isMultiLine = false;
|
|
937
886
|
while (true) {
|
|
938
887
|
const userInputRaw = await historyText({
|
|
939
|
-
message: isMultiLine ? ' ' :
|
|
940
|
-
placeholder: isMultiLine
|
|
888
|
+
message: isMultiLine ? ' ' : pc.magenta('❯'),
|
|
889
|
+
placeholder: isMultiLine
|
|
890
|
+
? ''
|
|
891
|
+
: 'Use ' + String.fromCharCode(92) + ' for new lines or @ to mention file paths and more',
|
|
941
892
|
history,
|
|
893
|
+
autocomplete: {
|
|
894
|
+
workspaceRoot,
|
|
895
|
+
getSuggestions: async (query) => {
|
|
896
|
+
try {
|
|
897
|
+
const { mentionEngine } = await import('./mentionEngine.js');
|
|
898
|
+
const suggestions = await mentionEngine.getSuggestionsForQuery(query, { workspaceRoot });
|
|
899
|
+
return suggestions.map((s) => ({
|
|
900
|
+
value: s.value,
|
|
901
|
+
label: s.label,
|
|
902
|
+
category: s.category,
|
|
903
|
+
description: s.description,
|
|
904
|
+
metadata: {
|
|
905
|
+
filePath: s.filePath,
|
|
906
|
+
symbol: s.label,
|
|
907
|
+
helperLabel: s.helperLabel,
|
|
908
|
+
symbolKind: s.symbolKind,
|
|
909
|
+
},
|
|
910
|
+
}));
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
return [];
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
},
|
|
942
917
|
});
|
|
943
918
|
if (p.isCancel(userInputRaw)) {
|
|
944
919
|
if (isMultiLine) {
|
|
@@ -1075,7 +1050,7 @@ export async function summarizeHistoryIfNeeded(chat, abortSignal) {
|
|
|
1075
1050
|
}
|
|
1076
1051
|
return false;
|
|
1077
1052
|
}
|
|
1078
|
-
async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inputHandler, effectiveTargetAgent, signal, spinner, originalUserInput
|
|
1053
|
+
async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inputHandler, effectiveTargetAgent, signal, spinner, originalUserInput) {
|
|
1079
1054
|
let correctionAttempts = 0;
|
|
1080
1055
|
const MAX_CORRECTIONS = 5;
|
|
1081
1056
|
let result = initialResult;
|
|
@@ -1141,7 +1116,7 @@ async function executeSelfCorrectionLoop(chat, initialResult, workspaceRoot, inp
|
|
|
1141
1116
|
}
|
|
1142
1117
|
previousChangeCount = currentChanges.length;
|
|
1143
1118
|
// ─── Tool-Based Intent Verification ─────────────────────────────────
|
|
1144
|
-
if (effectiveTargetAgent === 'EXECUTE' && !intentVerified
|
|
1119
|
+
if (effectiveTargetAgent === 'EXECUTE' && !intentVerified) {
|
|
1145
1120
|
if (!currentText.includes('[TASK_FINISHED]')) {
|
|
1146
1121
|
p.log.warn(pc.yellow('AI paused execution without calling finish_task. Prompting to continue or finish...'));
|
|
1147
1122
|
const finishReminder = `SYSTEM CHECK: You stopped without calling the \`finish_task\` tool.
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
import { type Content, type FunctionCall } from '@google/generative-ai';
|
|
2
|
+
import { type ThinkingLevel } from '../utils/config.js';
|
|
2
3
|
export declare function getMultiWorkspaceBlock(): string;
|
|
3
4
|
export declare function setModelOverride(agent: 'context' | 'execution', overrideStr: string): void;
|
|
4
5
|
export declare function clearModelOverrides(): void;
|
|
5
6
|
export declare function setGlobalActiveModel(model: string): void;
|
|
6
7
|
export declare function getGlobalActiveModel(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Gets the thinking reasoning level configured for a specific model or active model.
|
|
10
|
+
* Falls back to default model thinking levels or 'MEDIUM' if not explicitly configured.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getModelThinkingLevel(model?: string): ThinkingLevel;
|
|
13
|
+
/**
|
|
14
|
+
* Sets the user-configured thinking reasoning level for a specific model.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setModelThinkingLevel(model: string, level: ThinkingLevel): void;
|
|
17
|
+
/**
|
|
18
|
+
* Resets user-configured thinking levels back to default model presets.
|
|
19
|
+
*/
|
|
20
|
+
export declare function resetModelThinkingLevels(): void;
|
|
7
21
|
export declare class ProxyChatSession {
|
|
8
22
|
private history;
|
|
9
23
|
private fullHistory;
|
|
@@ -12,6 +26,7 @@ export declare class ProxyChatSession {
|
|
|
12
26
|
private tools;
|
|
13
27
|
private toolConfig?;
|
|
14
28
|
private generationConfig;
|
|
29
|
+
private configuredThinkingLevel?;
|
|
15
30
|
private latestUsageMetadata;
|
|
16
31
|
constructor(modelName: string, systemInstruction: string, tools: any[], generationConfig: any, toolConfig?: any);
|
|
17
32
|
getLatestUsageMetadata(): any;
|
|
@@ -20,6 +35,10 @@ export declare class ProxyChatSession {
|
|
|
20
35
|
getToolConfig(): any;
|
|
21
36
|
setModel(modelName: string): void;
|
|
22
37
|
getModel(): string;
|
|
38
|
+
setThinkingLevel(level: ThinkingLevel): void;
|
|
39
|
+
getThinkingLevel(): ThinkingLevel | undefined;
|
|
40
|
+
setGenerationConfig(generationConfig: any): void;
|
|
41
|
+
getGenerationConfig(): any;
|
|
23
42
|
clearHistory(): void;
|
|
24
43
|
getRawHistory(): Content[];
|
|
25
44
|
getFullHistory(): Content[];
|
|
@@ -95,10 +114,6 @@ export declare function getPlanExecutionConfig(): {
|
|
|
95
114
|
functionDeclarations: import("@google/generative-ai").FunctionDeclaration[];
|
|
96
115
|
}[];
|
|
97
116
|
};
|
|
98
|
-
export declare function getPlanModeConfig(): {
|
|
99
|
-
systemInstruction: string;
|
|
100
|
-
tools: never[];
|
|
101
|
-
};
|
|
102
117
|
/**
|
|
103
118
|
* Compresses a large string of text using Gemini Flash.
|
|
104
119
|
* Used for shrinking context payloads to prevent OOM/choking.
|