minovative-mind-cli 2.9.1 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +14 -6
  2. package/dist/services/agent/commandApproval.js +5 -2
  3. package/dist/services/agent/slashCommands.js +90 -53
  4. package/dist/services/agent-tools.d.ts +4 -3
  5. package/dist/services/agent-tools.js +32 -79
  6. package/dist/services/agent.d.ts +5 -6
  7. package/dist/services/agent.js +11 -15
  8. package/dist/services/ai.d.ts +21 -1
  9. package/dist/services/ai.js +236 -11
  10. package/dist/services/chatHistoryService.d.ts +95 -2
  11. package/dist/services/chatHistoryService.js +236 -9
  12. package/dist/services/contextAgent.js +196 -81
  13. package/dist/services/investigationComplexity.d.ts +1 -1
  14. package/dist/services/investigationComplexity.js +1 -1
  15. package/dist/services/orchestration/investigationAgent.js +101 -84
  16. package/dist/services/orchestration/investigationCache.d.ts +80 -5
  17. package/dist/services/orchestration/investigationCache.js +570 -41
  18. package/dist/services/orchestration/investigationOrchestrator.js +17 -4
  19. package/dist/services/orchestration/orchestrator.js +6 -3
  20. package/dist/services/orchestration/scopedTools.js +5 -0
  21. package/dist/services/orchestration/subAgent.d.ts +31 -1
  22. package/dist/services/orchestration/subAgent.js +153 -2
  23. package/dist/utils/analysisRunner.d.ts +29 -0
  24. package/dist/utils/analysisRunner.js +200 -5
  25. package/dist/utils/contextPrompts.d.ts +20 -4
  26. package/dist/utils/contextPrompts.js +158 -23
  27. package/dist/utils/historyPrompt.d.ts +92 -1
  28. package/dist/utils/historyPrompt.js +166 -2
  29. package/dist/utils/symbolExtractor.d.ts +12 -0
  30. package/dist/utils/symbolExtractor.js +946 -0
  31. package/dist/utils/systemPrompts.d.ts +5 -4
  32. package/dist/utils/systemPrompts.js +46 -14
  33. package/oclif.manifest.json +1 -1
  34. package/package.json +2 -2
package/README.md CHANGED
@@ -89,7 +89,7 @@ Hot-swap during a session using `/models`:
89
89
  | Model | Best for |
90
90
  | ------------------------- | --------------------------------------------------- |
91
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
+ | **Gemini 3.7 Flash** | Next-gen performance, reasoning & fast execution |
93
93
  | **Gemini 3.6 Flash** | Everyday coding — fast and accurate |
94
94
  | **Gemini 3.1 Pro** | Complex architectural changes |
95
95
  | **Gemini 3.5 Flash Lite** | Best for speed and cost efficiency |
@@ -101,11 +101,19 @@ If you prefer to use your own API key instead of credits, you can configure it v
101
101
  - **Configuration:** Use `/config-key` in the chat session to set and manage your API key.
102
102
  - **Error Handling:** If your key is invalid, expired, or you hit rate limits, the CLI will report a `BYOK AI Error`. Please check your API key status in the [Google AI Studio dashboard](https://aistudio.google.com).
103
103
 
104
- > Background tasks (routing, history summarization, context compression, commits) always use lightweight
105
- > models automatically. You pay for those lightweight model
106
- > background ai models and for your selected model
107
- > during chat and code execution.
108
- > Use `/debug` to see exactly what's running.
104
+ ### Auxiliary Model Routing Defaults
105
+
106
+ Background tasks automatically route to dedicated auxiliary models with native `responseSchema` constraints for optimal latency, cost efficiency, and structured output reliability:
107
+
108
+ - **Intent Router (`routeIntent`)**: `Gemini 3.7 Flash` (Temp 0, native `responseSchema`) — zero-temperature classification of `SEARCH` vs `SKIP` and `CHAT` vs `EXECUTE`.
109
+ - **Complexity Evaluators**: `Gemini 3.7 Flash` (Temp 0, native `responseSchema`) — domain partitioning and parallel execution wave feasibility.
110
+ - **Context Compressor**: `Gemini 3.7 Flash` (Temp 0.2) — surgical code distillation for files exceeding 2,000 characters.
111
+ - **Session Titling**: `Gemini 3.7 Flash` (Temp 0.7, native `responseSchema`) — automated concise chat session titling.
112
+ - **Semantic Cache Classifier**: `Gemini 3.5 Flash Lite` (Temp 0, native `responseSchema`) — intent and topic classification for cache hits.
113
+ - **History Summarizer**: `Gemini 3.7 Flash` (Temp 0.2) — 3-part structured conversation compression preserving recent history.
114
+ - **Commit Generator**: `Gemini 3.5 Flash Lite` — conventional commit message synthesis.
115
+
116
+ > You pay for auxiliary model background AI operations and for your selected model during chat and code execution. Use `/debug` to inspect real-time routing diagnostics.
109
117
 
110
118
  ---
111
119
 
@@ -33,6 +33,9 @@ export function isCommandDestructive(command) {
33
33
  // Massive permission shifts (chmod 777, recursive chowns)
34
34
  /\bchmod\s+(?:-[R\w]*\s+)?777\b/i,
35
35
  /\bchown\s+-[R\w]*\b/i,
36
+ // Privileged root escalation commands (require explicit interactive password / system-level access)
37
+ /\bsudo\b/i,
38
+ /\bsu\s+/i,
36
39
  ];
37
40
  return destructivePatterns.some((pattern) => pattern.test(command));
38
41
  }
@@ -49,10 +52,10 @@ export function isCommandSafe(command) {
49
52
  /^\s*echo\b/i,
50
53
  /^\s*grep\b/i,
51
54
  /^\s*find\b/i,
52
- /^\s*npm\s+(install|i|ci|run\b)/i,
55
+ /^\s*npm\s+(install|i|ci|run\b|test\b|t\b)/i,
53
56
  /^\s*yarn\s+(install|add|build|lint|test|run\b)/i,
54
57
  /^\s*pnpm\s+(install|i|add|build|lint|test|run\b)/i,
55
- /^\s*bun\s+(install|i|add|run\b)/i,
58
+ /^\s*bun\s+(install|i|add|run\b|test\b)/i,
56
59
  /^\s*cargo\s+(build|check|add|test|run\b)/i,
57
60
  /^\s*go\s+(mod|get|build|test|run\b)/i,
58
61
  /^\s*pip\s+(install|list|show)\b/i,
@@ -563,73 +563,87 @@ export async function handleSlashCommand(command, context) {
563
563
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
564
564
  p.log.info(`${pc.dim('Commands:')} Type ${pc.yellow('/')} to open the command menu and "${pc.yellow('stop')}" to stop the ai generation. Type ${pc.yellow('exit')} to leave.`);
565
565
  p.log.success(`Resumed session: ${session.title}`);
566
- // Print the loaded history so the user can see past context
567
- let sessionTasks = [];
566
+ // Print the loaded history cleanly: only user and AI conversational messages,
567
+ // with a clean indicator label if the turn involved tool/code changes.
568
+ let turnHadModifications = false;
569
+ let turnHadInspections = false;
570
+ let renderedIndicatorForTurn = false;
568
571
  for (const item of session.history) {
569
572
  if (item.role === 'user') {
573
+ const hasFunctionResponse = item.parts?.some((p) => p.functionResponse);
574
+ if (hasFunctionResponse) {
575
+ // Internal tool response from the execution loop — do not display as a user message
576
+ continue;
577
+ }
570
578
  const textParts = item.parts
571
- .filter((p) => p.text)
579
+ ?.filter((p) => p.text)
572
580
  .map((p) => p.text)
573
581
  .join('');
574
- if (textParts && !textParts.includes('SYSTEM CHECK: Please objectively verify')) {
582
+ if (textParts &&
583
+ !textParts.includes('SYSTEM CHECK: Please objectively verify') &&
584
+ !textParts.includes('[PREVIOUS CONVERSATION SUMMARY]') &&
585
+ !textParts.startsWith('[USER INTERRUPTION]')) {
586
+ // Reset turn tool flags for the new user prompt
587
+ turnHadModifications = false;
588
+ turnHadInspections = false;
589
+ renderedIndicatorForTurn = false;
575
590
  p.log.step(pc.bgBlue(pc.white(` ${textParts} `)));
576
591
  }
577
592
  }
578
593
  else if (item.role === 'model') {
579
- // First, print all function calls
580
- for (const part of item.parts) {
594
+ // Track if any tool actions were called in this turn
595
+ for (const part of item.parts || []) {
581
596
  if (part.functionCall) {
582
- const call = part.functionCall;
583
- p.log.message(`◇ ${pc.blue('🔧')} ${pc.bold(call.name)}`);
584
- const args = (call.args || {});
585
- if (call.name === 'create_todo_list' && Array.isArray(args.tasks)) {
586
- sessionTasks = args.tasks;
587
- p.log.message(pc.bold(pc.cyan(' 📋 Agent Task List:')));
588
- args.tasks.forEach((task, index) => {
589
- p.log.message(` ${pc.dim(`[ ] ${index + 1}.`)} ${task}`);
590
- });
591
- }
592
- else if (call.name === 'update_todo_status') {
593
- const status = args.status;
594
- const taskIndex = args.taskIndex;
595
- const taskText = sessionTasks[taskIndex - 1] || 'completed';
596
- let icon = '[ ]';
597
- let color = pc.dim;
598
- if (status?.toLowerCase() === 'completed' || status?.toLowerCase() === 'done') {
599
- icon = '[x]';
600
- color = pc.green;
601
- }
602
- else if (status?.toLowerCase() === 'in_progress' || status?.toLowerCase() === 'started') {
603
- icon = '[/]';
604
- color = pc.yellow;
605
- }
606
- else if (status?.toLowerCase() === 'failed' || status?.toLowerCase() === 'error') {
607
- icon = '[!]';
608
- color = pc.red;
609
- }
610
- p.log.message(color(` ${icon} ${taskIndex}. ${taskText}`));
597
+ const name = part.functionCall.name;
598
+ if (name === 'modify_file' ||
599
+ name === 'write_file' ||
600
+ name === 'delete_file' ||
601
+ name === 'rename_file' ||
602
+ name === 'run_command') {
603
+ turnHadModifications = true;
611
604
  }
612
- else {
613
- let argsStr = '';
614
- try {
615
- argsStr = JSON.stringify(args);
616
- }
617
- catch (e) {
618
- argsStr = String(call.args);
619
- }
620
- const argsPreview = argsStr.slice(0, 50);
621
- p.log.message(pc.dim(` ├─ Executed tool`));
605
+ else if (name === 'read_file' ||
606
+ name === 'grep_search' ||
607
+ name === 'list_directory' ||
608
+ name === 'find_dependencies') {
609
+ turnHadInspections = true;
622
610
  }
623
611
  }
624
612
  }
625
- // Then aggregate and print all text
626
- const textParts = item.parts
613
+ // Aggregate and extract model conversational text
614
+ let textParts = (item.parts || [])
627
615
  .filter((p) => p.text && !p.text.includes('[TASK_FINISHED]'))
628
616
  .map((p) => p.text)
629
617
  .join('');
630
- if (textParts.trim()) {
618
+ // Backward-compatibility: if text is empty, check finish_task args.summary
619
+ if (!textParts.trim()) {
620
+ const finishCall = (item.parts || []).find((p) => p.functionCall?.name === 'finish_task');
621
+ const finishArgs = finishCall?.functionCall?.args;
622
+ if (finishArgs?.summary) {
623
+ textParts = String(finishArgs.summary);
624
+ }
625
+ }
626
+ if (textParts.includes('[ORCHESTRATOR_EXECUTION]')) {
627
+ turnHadModifications = true;
628
+ }
629
+ const cleanText = textParts
630
+ .replace(/\[TASK_FINISHED\]/g, '')
631
+ .replace(/\[ORCHESTRATOR_EXECUTION\]\n?/g, '')
632
+ .replace(/\[PREVIOUS CONVERSATION SUMMARY\]:[\s\S]*?(?=\n\n|$)/g, '')
633
+ .trim();
634
+ if (cleanText) {
635
+ if (!renderedIndicatorForTurn) {
636
+ if (turnHadModifications) {
637
+ p.log.message(pc.dim(' ⚡ [Mino applied workspace modifications & commands]'));
638
+ renderedIndicatorForTurn = true;
639
+ }
640
+ else if (turnHadInspections) {
641
+ p.log.message(pc.dim(' 🔍 [Inspected workspace context]'));
642
+ renderedIndicatorForTurn = true;
643
+ }
644
+ }
631
645
  console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim('(History)')}\n`);
632
- console.log(renderTerminalMarkdown(textParts));
646
+ console.log(renderTerminalMarkdown(cleanText));
633
647
  }
634
648
  }
635
649
  }
@@ -715,7 +729,7 @@ export async function handleSlashCommand(command, context) {
715
729
  }
716
730
  else if (chatsMenu === 'bulk-delete') {
717
731
  const selectedIds = await p['multiselect']({
718
- message: 'Select chat sessions to delete:',
732
+ message: `Select chat sessions to delete: ${pc.dim('(Press Esc to go back / cancel)')}`,
719
733
  options: sessions
720
734
  .slice()
721
735
  .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))
@@ -723,7 +737,7 @@ export async function handleSlashCommand(command, context) {
723
737
  value: s.id,
724
738
  label: `${truncate(s.title, 50)} (${new Date(s.timestamp).toLocaleString()})`,
725
739
  })),
726
- required: true,
740
+ required: false,
727
741
  });
728
742
  if (p.isCancel(selectedIds) || !Array.isArray(selectedIds) || selectedIds.length === 0) {
729
743
  p.log.warn('Bulk delete canceled.');
@@ -735,6 +749,16 @@ export async function handleSlashCommand(command, context) {
735
749
  if (confirm) {
736
750
  await chatHistoryService.bulkDeleteSessions(selectedIds);
737
751
  p.log.success(`Successfully deleted ${selectedIds.length} session(s).`);
752
+ if (chatSessionState && selectedIds.includes(chatSessionState.id)) {
753
+ chat.clearHistory();
754
+ process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
755
+ printLogo();
756
+ p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + version)}`);
757
+ p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
758
+ p.log.info(`${pc.dim('Commands:')} Type ${pc.yellow('/')} to open the command menu and "${pc.yellow('stop')}" to stop the ai generation. Type ${pc.yellow('exit')} to leave.`);
759
+ p.log.warn('Active session was deleted. Chat history cleared.');
760
+ console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
761
+ }
738
762
  }
739
763
  else {
740
764
  p.log.warn('Bulk delete canceled.');
@@ -1008,10 +1032,23 @@ export async function handleSlashCommand(command, context) {
1008
1032
  <diff>
1009
1033
  ${diffOut}
1010
1034
  </diff>`;
1011
- const commitSystemPrompt = 'You are an expert developer. Output only the git commit message, no markdown formatting, no explanations. Follow Conventional Commits format (feat:, fix:, chore:, refactor:, etc.) for the first line, keeping it under 70 characters and using the imperative mood. Then, add a blank line followed by a more descriptive bulleted list explaining the "what" and "why" of the changes based on the diff.';
1035
+ const commitSystemPrompt = `You are a strict Conventional Commits generator and expert software engineer.
1036
+ Output ONLY the raw commit message text. Absolutely NO conversational preambles, NO markdown code blocks (e.g. no \`\`\`), and NO post-explanations.
1037
+
1038
+ Strict Formatting Rules:
1039
+ 1. First line must follow Conventional Commits format: type(scope): description
1040
+ - Allowed types: feat, fix, refactor, test, docs, chore, perf, style
1041
+ - Subject line must be in imperative mood (e.g. "add support for...", "fix bug in...") and strictly under 50 characters.
1042
+ 2. Leave a single blank line after the subject.
1043
+ 3. Provide a concise bulleted list (using '-' or '*') explaining the architectural "what" and "why" of the changes based on the provided diff. If diff spans multiple areas, prioritize the primary architectural change.`;
1012
1044
  const commitAgent = new ProxyChatSession(GEMINI_MODELS.FLASH_LITE, commitSystemPrompt, [], {});
1013
1045
  const commitResult = await commitAgent.sendMessage(prompt);
1014
- const commitMsg = commitResult.response.text().trim();
1046
+ let commitMsg = commitResult.response.text().trim();
1047
+ // Sanitize markdown fences if model included them despite instructions
1048
+ commitMsg = commitMsg
1049
+ .replace(/^```[a-zA-Z]*\n?/gm, '')
1050
+ .replace(/```\s*$/gm, '')
1051
+ .trim();
1015
1052
  commitSpinner.message('Committing...');
1016
1053
  const tmpMsgPath = path.join(workspaceRoot, '.gemini-commit-msg.tmp');
1017
1054
  await fs.writeFile(tmpMsgPath, commitMsg, 'utf-8');
@@ -180,11 +180,12 @@ export declare function traceDependencies(workspaceRoot: string, filePath: strin
180
180
  */
181
181
  export declare function findRecentChanges(workspaceRoot: string, dirPath?: string, minutes?: number, maxDepth?: number): Promise<ToolResult>;
182
182
  /**
183
- * Writes a disposable scratchpad script to a temporary file, executes it using the specified runtime,
184
- * and returns standard output and standard error.
183
+ * Executes an ephemeral debugging or validation script in the specified language runtime.
184
+ * Delegates to the sandboxed ephemeral script runner in `os.tmpdir()` with automatic ESM/CJS
185
+ * module resolution and multi-language runtime detection.
185
186
  *
186
187
  * @param workspaceRoot - Absolute path to the workspace root directory.
187
- * @param language - Runtime language (`'node'`, `'ts-node'`, `'python'`, `'bash'`, `'go'`, or `'rust'`).
188
+ * @param language - Runtime language (`'node'`, `'ts-node'`, `'python'`, `'bash'`, `'go'`, `'rust'`, `'c'`, `'cpp'`, `'ruby'`, `'php'`, `'java'`, or `'auto'`).
188
189
  * @param code - The exact script source code to execute.
189
190
  * @param abortSignal - Optional AbortSignal to cancel execution.
190
191
  * @returns A promise resolving to a {@link ToolResult} containing execution output.
@@ -13,7 +13,6 @@
13
13
  */
14
14
  import { exec } from 'node:child_process';
15
15
  import { promises as fs } from 'node:fs';
16
- import os from 'node:os';
17
16
  import path from 'node:path';
18
17
  import { promisify } from 'node:util';
19
18
  import { SchemaType } from '@google/generative-ai';
@@ -31,7 +30,7 @@ import { extractSymbols } from '../utils/symbolExtractor.js';
31
30
  import { getMetricCollector } from './metrics.js';
32
31
  import { getCurrentAgentId } from '../utils/asyncContext.js';
33
32
  import { recordFileRead, hasAgentReadFile } from '../utils/fileReadGuard.js';
34
- import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, } from '../utils/analysisRunner.js';
33
+ import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, runEphemeralScript, } from '../utils/analysisRunner.js';
35
34
  import ignore from 'ignore';
36
35
  const execAsync = promisify(exec);
37
36
  // ─── Tool Declarations for Gemini Function Calling ───────────────────
@@ -1112,6 +1111,13 @@ export async function listDirectory(workspaceRoot, dirPath, maxDepth = 3) {
1112
1111
  * @returns A promise resolving to a {@link ToolResult} containing stdout/stderr or an error.
1113
1112
  */
1114
1113
  export async function runCommand(workspaceRoot, command, abortSignal) {
1114
+ // Reject sudo or interactive root commands to prevent non-interactive subshell deadlocks and security escalation
1115
+ if (/\bsudo\b/i.test(command)) {
1116
+ return {
1117
+ output: '',
1118
+ error: "Execution blocked: Commands requiring 'sudo' or root privileges cannot be executed automatically in background tool loops. Please explain to the user in your text response that they must run this command manually in their own terminal.",
1119
+ };
1120
+ }
1115
1121
  try {
1116
1122
  const { stdout, stderr } = await execAsync(command, {
1117
1123
  cwd: workspaceRoot,
@@ -1351,102 +1357,49 @@ export async function findRecentChanges(workspaceRoot, dirPath = '.', minutes =
1351
1357
  }
1352
1358
  // ─── Tool Dispatcher ─────────────────────────────────────────────────
1353
1359
  /**
1354
- * Writes a disposable scratchpad script to a temporary file, executes it using the specified runtime,
1355
- * and returns standard output and standard error.
1360
+ * Executes an ephemeral debugging or validation script in the specified language runtime.
1361
+ * Delegates to the sandboxed ephemeral script runner in `os.tmpdir()` with automatic ESM/CJS
1362
+ * module resolution and multi-language runtime detection.
1356
1363
  *
1357
1364
  * @param workspaceRoot - Absolute path to the workspace root directory.
1358
- * @param language - Runtime language (`'node'`, `'ts-node'`, `'python'`, `'bash'`, `'go'`, or `'rust'`).
1365
+ * @param language - Runtime language (`'node'`, `'ts-node'`, `'python'`, `'bash'`, `'go'`, `'rust'`, `'c'`, `'cpp'`, `'ruby'`, `'php'`, `'java'`, or `'auto'`).
1359
1366
  * @param code - The exact script source code to execute.
1360
1367
  * @param abortSignal - Optional AbortSignal to cancel execution.
1361
1368
  * @returns A promise resolving to a {@link ToolResult} containing execution output.
1362
1369
  */
1363
1370
  export async function runDebugScript(workspaceRoot, language, code, abortSignal) {
1364
- const extMap = {
1365
- node: '.js',
1366
- 'ts-node': '.ts',
1367
- python: '.py',
1368
- bash: '.sh',
1369
- go: '.go',
1370
- rust: '.rs',
1371
- };
1372
- const ext = extMap[language.toLowerCase()] || '.txt';
1373
- const tmpFileName = `.minovative-scratch${ext}`;
1374
- const absPath = path.join(workspaceRoot, tmpFileName);
1375
1371
  try {
1376
1372
  const p = await import('@clack/prompts');
1377
1373
  const pc = (await import('picocolors')).default;
1378
- p.log.info(pc.dim(`🛠️ Running temporary ${language} debug script...`));
1379
- await fs.writeFile(absPath, code, 'utf-8');
1380
- let cmd = '';
1381
- switch (language.toLowerCase()) {
1382
- case 'node':
1383
- cmd = `node ${tmpFileName}`;
1384
- break;
1385
- case 'ts-node':
1386
- cmd = `npx ts-node ${tmpFileName}`;
1387
- break;
1388
- case 'python':
1389
- cmd = `python3 ${tmpFileName}`;
1390
- break;
1391
- case 'bash':
1392
- cmd = `bash ${tmpFileName}`;
1393
- break;
1394
- case 'go':
1395
- cmd = `go run ${tmpFileName}`;
1396
- break;
1397
- case 'rust': {
1398
- const binExt = os.platform() === 'win32' ? '.exe' : '';
1399
- const binPath = tmpFileName.replace('.rs', binExt);
1400
- const runCmd = os.platform() === 'win32' ? binPath : `./${binPath}`;
1401
- cmd = `rustc ${tmpFileName} -o ${binPath} && ${runCmd}`;
1402
- break;
1403
- }
1404
- default:
1405
- return { output: '', error: `Unsupported language runtime: ${language}` };
1374
+ p.log.info(pc.dim(`🛠️ Running temporary ${language || 'script'} debug script...`));
1375
+ const res = await runEphemeralScript(workspaceRoot, language || 'auto', code, {
1376
+ abortSignal,
1377
+ timeoutMs: 60_000,
1378
+ });
1379
+ let finalOutput = '';
1380
+ if (res.exitCode !== 0) {
1381
+ finalOutput += `Script failed with exit code ${res.exitCode}.\n`;
1406
1382
  }
1407
- try {
1408
- const { stdout, stderr } = await execAsync(cmd, { cwd: workspaceRoot, timeout: 60_000, signal: abortSignal });
1409
- const out = stdout.trim();
1410
- const errOut = stderr.trim();
1411
- let finalOutput = '';
1412
- if (out)
1413
- finalOutput += `[STDOUT]\n${out}\n`;
1414
- if (errOut)
1415
- finalOutput += `[STDERR]\n${errOut}\n`;
1416
- if (!finalOutput)
1417
- finalOutput = 'Script executed successfully with no output.';
1418
- return { output: `<test_results>\n${sanitizeForCDATA(finalOutput)}\n</test_results>` };
1383
+ if (res.stdout) {
1384
+ finalOutput += `[STDOUT]\n${res.stdout}\n`;
1419
1385
  }
1420
- catch (err) {
1421
- const out = (err.stdout || '').trim();
1422
- const errOut = (err.stderr || '').trim();
1423
- let finalOutput = `Script failed with exit code ${err.code || 1}.\n`;
1424
- if (out)
1425
- finalOutput += `[STDOUT]\n${out}\n`;
1426
- if (errOut)
1427
- finalOutput += `[STDERR]\n${errOut}\n`;
1428
- return { output: `<test_results>\n${sanitizeForCDATA(finalOutput)}\n</test_results>` };
1386
+ if (res.stderr) {
1387
+ finalOutput += `[STDERR]\n${res.stderr}\n`;
1429
1388
  }
1389
+ if (!finalOutput.trim()) {
1390
+ finalOutput = 'Script executed successfully with no output.';
1391
+ }
1392
+ return {
1393
+ output: `<test_results>\n${sanitizeForCDATA(finalOutput.trim())}\n</test_results>`,
1394
+ ...(res.exitCode !== 0 ? { error: `Script failed with exit code ${res.exitCode}` } : {}),
1395
+ };
1430
1396
  }
1431
1397
  catch (err) {
1432
1398
  return {
1433
1399
  output: '',
1434
- error: `Failed to create or run test script: ${err instanceof Error ? err.message : String(err)}`,
1400
+ error: `Failed to execute debug script: ${err instanceof Error ? err.message : String(err)}`,
1435
1401
  };
1436
1402
  }
1437
- finally {
1438
- try {
1439
- await fs.rm(absPath, { force: true });
1440
- if (language.toLowerCase() === 'rust') {
1441
- const binExt = os.platform() === 'win32' ? '.exe' : '';
1442
- const binPath = absPath.replace('.rs', binExt);
1443
- await fs.rm(binPath, { force: true });
1444
- }
1445
- }
1446
- catch {
1447
- // Ignore cleanup errors
1448
- }
1449
- }
1450
1403
  }
1451
1404
  /**
1452
1405
  * Executes a fuzz probing analysis script and formats the resulting pass/fail and crash statistics.
@@ -112,15 +112,14 @@ export declare function executeSingleTurn(workspaceRoot: string, userInput: stri
112
112
  } | void>;
113
113
  /**
114
114
  * Raw chat history entry count threshold to trigger chat history summarization.
115
- * 50 entries = 25 conversational user/model turns.
115
+ * 2 entries = 1 complete conversational user/model turn.
116
116
  */
117
- export declare const HISTORY_SUMMARIZATION_THRESHOLD = 50;
117
+ export declare const HISTORY_SUMMARIZATION_THRESHOLD = 2;
118
118
  /**
119
- * Summarizes older chat history entries if the active session's history
120
- * exceeds {@link HISTORY_SUMMARIZATION_THRESHOLD} entries.
119
+ * Summarizes chat history entries starting from the first full user/model turn.
121
120
  *
122
- * Preserves the most recent 20 entries (10 turns) intact for conversational continuity,
123
- * replacing all preceding turns with a single summarized pair in the chat history.
121
+ * Compresses preceding conversation history into a structured high-density summary pair
122
+ * in the active session while permanently preserving uncompressed history in fullHistory.
124
123
  *
125
124
  * @param chat - The active ProxyChatSession instance.
126
125
  * @returns A promise resolving to true if history was summarized and updated; false otherwise.
@@ -544,7 +544,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
544
544
  parts: [{ text: userInput }],
545
545
  }, {
546
546
  role: 'model',
547
- parts: [{ text: handledByOrchestrator }],
547
+ parts: [{ text: `[ORCHESTRATOR_EXECUTION]\n${handledByOrchestrator}` }],
548
548
  });
549
549
  // Print the summary text just like single-agent mode
550
550
  console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(Orchestrator)`)}\n`);
@@ -670,6 +670,7 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
670
670
  if (cleanFinalText) {
671
671
  console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(${chat.getModel()})`)}\n`);
672
672
  console.log(renderTerminalMarkdown(cleanFinalText));
673
+ chat.appendFinalSummary(cleanFinalText);
673
674
  }
674
675
  }
675
676
  if (usage) {
@@ -989,15 +990,14 @@ async function compressContextFiles(workspaceRoot, contextResult) {
989
990
  }
990
991
  /**
991
992
  * Raw chat history entry count threshold to trigger chat history summarization.
992
- * 50 entries = 25 conversational user/model turns.
993
+ * 2 entries = 1 complete conversational user/model turn.
993
994
  */
994
- export const HISTORY_SUMMARIZATION_THRESHOLD = 50;
995
+ export const HISTORY_SUMMARIZATION_THRESHOLD = 2;
995
996
  /**
996
- * Summarizes older chat history entries if the active session's history
997
- * exceeds {@link HISTORY_SUMMARIZATION_THRESHOLD} entries.
997
+ * Summarizes chat history entries starting from the first full user/model turn.
998
998
  *
999
- * Preserves the most recent 20 entries (10 turns) intact for conversational continuity,
1000
- * replacing all preceding turns with a single summarized pair in the chat history.
999
+ * Compresses preceding conversation history into a structured high-density summary pair
1000
+ * in the active session while permanently preserving uncompressed history in fullHistory.
1001
1001
  *
1002
1002
  * @param chat - The active ProxyChatSession instance.
1003
1003
  * @returns A promise resolving to true if history was summarized and updated; false otherwise.
@@ -1008,12 +1008,8 @@ export async function summarizeHistoryIfNeeded(chat, abortSignal) {
1008
1008
  return false;
1009
1009
  }
1010
1010
  try {
1011
- // Keep the most recent 20 entries (10 full user/model turns) intact
1012
- const recentCount = 20;
1013
- const olderHistory = history.slice(0, history.length - recentCount);
1014
- const recentHistory = history.slice(history.length - recentCount);
1015
- debugLog(`Summarizing ${olderHistory.length} older chat history entries...`);
1016
- const summaryText = await summarizeChatHistory(olderHistory, abortSignal);
1011
+ debugLog(`Summarizing ${history.length} chat history entries (Flash-Lite Summarizer triggered after first turn)...`);
1012
+ const summaryText = await summarizeChatHistory(history, abortSignal);
1017
1013
  if (summaryText && summaryText.trim().length > 0) {
1018
1014
  const summaryContent = [
1019
1015
  {
@@ -1025,8 +1021,8 @@ export async function summarizeHistoryIfNeeded(chat, abortSignal) {
1025
1021
  parts: [{ text: 'Understood. I have reviewed and absorbed the summary of our preceding conversation.' }],
1026
1022
  },
1027
1023
  ];
1028
- chat.loadCompressedHistory([...summaryContent, ...recentHistory]);
1029
- debugLog(`Chat history compressed successfully from ${history.length} to ${summaryContent.length + recentHistory.length} entries.`);
1024
+ chat.loadCompressedHistory(summaryContent);
1025
+ debugLog(`Chat history compressed successfully from ${history.length} to ${summaryContent.length} entries.`);
1030
1026
  return true;
1031
1027
  }
1032
1028
  }
@@ -22,6 +22,12 @@ export declare class ProxyChatSession {
22
22
  loadRawHistory(history: Content[]): void;
23
23
  loadCompressedHistory(history: Content[]): void;
24
24
  addTurn(userContent: Content, modelContent: Content): void;
25
+ /**
26
+ * Appends or merges the final conversational summary text into the active session history.
27
+ * Ensures that the final response produced by finish_task, PM reconciliation, or final turns
28
+ * is permanently preserved as a model text response in both working history and full transcript.
29
+ */
30
+ appendFinalSummary(text: string): void;
25
31
  /**
26
32
  * Retrieves the most recent conversation history as a formatted string.
27
33
  * Useful for passing conversation context to stateless background agents.
@@ -42,6 +48,19 @@ export declare class ProxyChatSession {
42
48
  * conversational context while preventing OOM crashes.
43
49
  */
44
50
  private pruneHistory;
51
+ /**
52
+ * Collapses oversized functionResponse outputs in historical turns (>1 turn old)
53
+ * while strictly maintaining Gemini functionCall and functionResponse pairing.
54
+ *
55
+ * Gemini requires every functionCall in a model turn to have a matching functionResponse
56
+ * in the immediately following user turn. Deleting parts or entries breaks this invariant and
57
+ * causes 400 Bad Request errors. This method mutates oversized response outputs in-place
58
+ * on older turns to drastically conserve context window tokens.
59
+ *
60
+ * @param threshold Maximum characters allowed for a historical tool response before collapsing (default: 1500).
61
+ * @param turnsToKeep Number of recent turns to preserve unpruned (default: 1).
62
+ */
63
+ pruneToolOutputHistory(threshold?: number, turnsToKeep?: number): void;
45
64
  sendMessage(message: string | Array<{
46
65
  functionResponse: {
47
66
  name: string;
@@ -77,7 +96,7 @@ export declare function getPlanModeConfig(): {
77
96
  tools: never[];
78
97
  };
79
98
  /**
80
- * Compresses a large string of text using gemini-3.5-flash-lite.
99
+ * Compresses a large string of text using Gemini Flash.
81
100
  * Used for shrinking context payloads to prevent OOM/choking.
82
101
  */
83
102
  export declare function compressTextUsingFlashLite(text: string, instruction?: string, inlineData?: any, force?: boolean, abortSignal?: AbortSignal): Promise<string>;
@@ -91,6 +110,7 @@ export declare function createContextAgentSession(): any;
91
110
  export declare function createIntentRouterSession(): any;
92
111
  export declare function createExecutionComplexitySession(): any;
93
112
  export declare function createInvestigationComplexitySession(): any;
113
+ export declare function createInvestigationSemanticSession(): any;
94
114
  export declare function createWebSearchAgentSession(): any;
95
115
  export declare function createHistorySummarizerSession(): any;
96
116
  /**