minovative-mind-cli 2.10.0 → 2.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/chat.d.ts +1 -1
  3. package/dist/commands/chat.js +2 -1
  4. package/dist/services/agent/commandApproval.js +5 -2
  5. package/dist/services/agent/slashCommands.js +213 -51
  6. package/dist/services/agent-tools.d.ts +4 -3
  7. package/dist/services/agent-tools.js +32 -79
  8. package/dist/services/agent.d.ts +5 -6
  9. package/dist/services/agent.js +38 -15
  10. package/dist/services/ai.d.ts +25 -0
  11. package/dist/services/ai.js +253 -2
  12. package/dist/services/chatHistoryService.d.ts +95 -2
  13. package/dist/services/chatHistoryService.js +236 -9
  14. package/dist/services/contextAgent.js +184 -89
  15. package/dist/services/orchestration/investigationAgent.js +100 -84
  16. package/dist/services/orchestration/investigationOrchestrator.js +6 -2
  17. package/dist/services/orchestration/orchestrator.js +6 -3
  18. package/dist/services/orchestration/scopedTools.js +5 -0
  19. package/dist/services/orchestration/subAgent.d.ts +31 -1
  20. package/dist/services/orchestration/subAgent.js +153 -2
  21. package/dist/services/userProfileService.d.ts +97 -0
  22. package/dist/services/userProfileService.js +410 -0
  23. package/dist/utils/analysisRunner.d.ts +29 -0
  24. package/dist/utils/analysisRunner.js +200 -5
  25. package/dist/utils/contextPrompts.d.ts +19 -3
  26. package/dist/utils/contextPrompts.js +144 -26
  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 +6 -4
  32. package/dist/utils/systemPrompts.js +77 -9
  33. package/oclif.manifest.json +2 -2
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -122,6 +122,7 @@ Background tasks automatically route to dedicated auxiliary models with native `
122
122
  | Command | What it does |
123
123
  | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
124
124
  | `/config-key` | Configure custom Google AI Studio API key (BYOK mode) |
125
+ | `/profile` | View, inspect, delete, or reset global adaptive persona memory and AI side-notes |
125
126
  | `/models` | Hot-swap the active model |
126
127
  | `/plan` | Toggle plan mode to review implementation strategies |
127
128
  | `/paste` | Multi-line input mode (cancel with Ctrl+C) |
@@ -9,7 +9,7 @@ import { Command } from '@oclif/core';
9
9
  export default class DefaultCommand extends Command {
10
10
  /**
11
11
  * The description displayed in the CLI help output.
12
- * Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
12
+ * Details all supported slash commands (/config-key, /profile, /paste, /plan, /clear, /models,
13
13
  * /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
14
14
  * along with interactive chat controls.
15
15
  */
@@ -17,7 +17,7 @@ import { updateWorkspaceStatus } from '../services/workspace.js';
17
17
  export default class DefaultCommand extends Command {
18
18
  /**
19
19
  * The description displayed in the CLI help output.
20
- * Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
20
+ * Details all supported slash commands (/config-key, /profile, /paste, /plan, /clear, /models,
21
21
  * /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
22
22
  * along with interactive chat controls.
23
23
  */
@@ -25,6 +25,7 @@ export default class DefaultCommand extends Command {
25
25
 
26
26
  Inside the chat session, you can use the following commands in the slash menu:
27
27
  /config-key - BYOK — Configure, clear, or view status of custom Google AI Studio API key
28
+ /profile - View, inspect, delete, or reset global adaptive persona memory and AI side-notes
28
29
  /paste - Enter multi-line paste mode for long code snippets and prompts
29
30
  /plan - Toggle plan mode to review step-by-step implementation strategies
30
31
  /clear - Clear conversation history, reset terminal screen, and display logo
@@ -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,
@@ -17,12 +17,14 @@ import { checkByokSubscription } from '../auth.js';
17
17
  import { GEMINI_MODELS, isByokEnabled } from '../../utils/config.js';
18
18
  import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
19
19
  import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '../ai.js';
20
+ import { loadUserProfile, deleteSideNote, clearUserProfile, getUserProfilePath, } from '../userProfileService.js';
20
21
  /**
21
22
  * @file slashCommands.ts
22
23
  * @description Interactive slash command handler module for Minovative Mind CLI.
23
24
  *
24
25
  * Supported slash commands:
25
26
  * - `/config-key` : Configure, validate, toggle, or clear Bring Your Own Key (BYOK) Google AI Studio API credentials.
27
+ * - `/profile` : View, inspect, delete, or reset global adaptive persona memory and AI side-notes.
26
28
  * - `/paste` : Enter multi-line paste mode using EOF tracking (`Ctrl+D` submission).
27
29
  * - `/plan` : Toggle AI step-by-step implementation planning mode.
28
30
  * - `/clear` : Clear conversation history, wipe terminal screen, and reset CLI header.
@@ -563,73 +565,87 @@ export async function handleSlashCommand(command, context) {
563
565
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
564
566
  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
567
  p.log.success(`Resumed session: ${session.title}`);
566
- // Print the loaded history so the user can see past context
567
- let sessionTasks = [];
568
+ // Print the loaded history cleanly: only user and AI conversational messages,
569
+ // with a clean indicator label if the turn involved tool/code changes.
570
+ let turnHadModifications = false;
571
+ let turnHadInspections = false;
572
+ let renderedIndicatorForTurn = false;
568
573
  for (const item of session.history) {
569
574
  if (item.role === 'user') {
575
+ const hasFunctionResponse = item.parts?.some((p) => p.functionResponse);
576
+ if (hasFunctionResponse) {
577
+ // Internal tool response from the execution loop — do not display as a user message
578
+ continue;
579
+ }
570
580
  const textParts = item.parts
571
- .filter((p) => p.text)
581
+ ?.filter((p) => p.text)
572
582
  .map((p) => p.text)
573
583
  .join('');
574
- if (textParts && !textParts.includes('SYSTEM CHECK: Please objectively verify')) {
584
+ if (textParts &&
585
+ !textParts.includes('SYSTEM CHECK: Please objectively verify') &&
586
+ !textParts.includes('[PREVIOUS CONVERSATION SUMMARY]') &&
587
+ !textParts.startsWith('[USER INTERRUPTION]')) {
588
+ // Reset turn tool flags for the new user prompt
589
+ turnHadModifications = false;
590
+ turnHadInspections = false;
591
+ renderedIndicatorForTurn = false;
575
592
  p.log.step(pc.bgBlue(pc.white(` ${textParts} `)));
576
593
  }
577
594
  }
578
595
  else if (item.role === 'model') {
579
- // First, print all function calls
580
- for (const part of item.parts) {
596
+ // Track if any tool actions were called in this turn
597
+ for (const part of item.parts || []) {
581
598
  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}`));
599
+ const name = part.functionCall.name;
600
+ if (name === 'modify_file' ||
601
+ name === 'write_file' ||
602
+ name === 'delete_file' ||
603
+ name === 'rename_file' ||
604
+ name === 'run_command') {
605
+ turnHadModifications = true;
611
606
  }
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`));
607
+ else if (name === 'read_file' ||
608
+ name === 'grep_search' ||
609
+ name === 'list_directory' ||
610
+ name === 'find_dependencies') {
611
+ turnHadInspections = true;
622
612
  }
623
613
  }
624
614
  }
625
- // Then aggregate and print all text
626
- const textParts = item.parts
615
+ // Aggregate and extract model conversational text
616
+ let textParts = (item.parts || [])
627
617
  .filter((p) => p.text && !p.text.includes('[TASK_FINISHED]'))
628
618
  .map((p) => p.text)
629
619
  .join('');
630
- if (textParts.trim()) {
620
+ // Backward-compatibility: if text is empty, check finish_task args.summary
621
+ if (!textParts.trim()) {
622
+ const finishCall = (item.parts || []).find((p) => p.functionCall?.name === 'finish_task');
623
+ const finishArgs = finishCall?.functionCall?.args;
624
+ if (finishArgs?.summary) {
625
+ textParts = String(finishArgs.summary);
626
+ }
627
+ }
628
+ if (textParts.includes('[ORCHESTRATOR_EXECUTION]')) {
629
+ turnHadModifications = true;
630
+ }
631
+ const cleanText = textParts
632
+ .replace(/\[TASK_FINISHED\]/g, '')
633
+ .replace(/\[ORCHESTRATOR_EXECUTION\]\n?/g, '')
634
+ .replace(/\[PREVIOUS CONVERSATION SUMMARY\]:[\s\S]*?(?=\n\n|$)/g, '')
635
+ .trim();
636
+ if (cleanText) {
637
+ if (!renderedIndicatorForTurn) {
638
+ if (turnHadModifications) {
639
+ p.log.message(pc.dim(' ⚡ [Mino applied workspace modifications & commands]'));
640
+ renderedIndicatorForTurn = true;
641
+ }
642
+ else if (turnHadInspections) {
643
+ p.log.message(pc.dim(' 🔍 [Inspected workspace context]'));
644
+ renderedIndicatorForTurn = true;
645
+ }
646
+ }
631
647
  console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim('(History)')}\n`);
632
- console.log(renderTerminalMarkdown(textParts));
648
+ console.log(renderTerminalMarkdown(cleanText));
633
649
  }
634
650
  }
635
651
  }
@@ -1018,10 +1034,23 @@ export async function handleSlashCommand(command, context) {
1018
1034
  <diff>
1019
1035
  ${diffOut}
1020
1036
  </diff>`;
1021
- 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.';
1037
+ const commitSystemPrompt = `You are a strict Conventional Commits generator and expert software engineer.
1038
+ Output ONLY the raw commit message text. Absolutely NO conversational preambles, NO markdown code blocks (e.g. no \`\`\`), and NO post-explanations.
1039
+
1040
+ Strict Formatting Rules:
1041
+ 1. First line must follow Conventional Commits format: type(scope): description
1042
+ - Allowed types: feat, fix, refactor, test, docs, chore, perf, style
1043
+ - Subject line must be in imperative mood (e.g. "add support for...", "fix bug in...") and strictly under 50 characters.
1044
+ 2. Leave a single blank line after the subject.
1045
+ 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.`;
1022
1046
  const commitAgent = new ProxyChatSession(GEMINI_MODELS.FLASH_LITE, commitSystemPrompt, [], {});
1023
1047
  const commitResult = await commitAgent.sendMessage(prompt);
1024
- const commitMsg = commitResult.response.text().trim();
1048
+ let commitMsg = commitResult.response.text().trim();
1049
+ // Sanitize markdown fences if model included them despite instructions
1050
+ commitMsg = commitMsg
1051
+ .replace(/^```[a-zA-Z]*\n?/gm, '')
1052
+ .replace(/```\s*$/gm, '')
1053
+ .trim();
1025
1054
  commitSpinner.message('Committing...');
1026
1055
  const tmpMsgPath = path.join(workspaceRoot, '.gemini-commit-msg.tmp');
1027
1056
  await fs.writeFile(tmpMsgPath, commitMsg, 'utf-8');
@@ -1119,5 +1148,138 @@ ${diffOut}
1119
1148
  }
1120
1149
  return { shouldContinue: true };
1121
1150
  }
1151
+ /**
1152
+ * `/profile` - Displays and manages global adaptive user profile traits, persona memory, and AI side-notes.
1153
+ */
1154
+ if (lowerCommand === '/profile') {
1155
+ const profile = await loadUserProfile();
1156
+ const action = await p.select({
1157
+ message: 'Adaptive User Profile & AI Memory Bank:',
1158
+ options: [
1159
+ {
1160
+ value: 'view',
1161
+ label: 'View Profile & Observations',
1162
+ hint: `${profile.agentNotes.length} side-note(s) recorded`,
1163
+ },
1164
+ {
1165
+ value: 'delete_note',
1166
+ label: 'Delete Specific Side-Note',
1167
+ hint: 'Select and remove an individual note',
1168
+ },
1169
+ {
1170
+ value: 'clear_all',
1171
+ label: 'Reset Profile & Clear Memory',
1172
+ hint: 'Wipe all persona traits and side-notes',
1173
+ },
1174
+ { value: 'cancel', label: 'Back to Chat', hint: 'Return to active session' },
1175
+ ],
1176
+ });
1177
+ if (p.isCancel(action) || action === 'cancel') {
1178
+ return { shouldContinue: true };
1179
+ }
1180
+ if (action === 'view') {
1181
+ const hasStyle = profile.communicationStyle?.tonePreference ||
1182
+ profile.communicationStyle?.verbosity ||
1183
+ profile.communicationStyle?.formulationStyle;
1184
+ const hasCognitive = profile.cognitiveTraits?.architecturalStyle ||
1185
+ profile.cognitiveTraits?.decisionPreference ||
1186
+ profile.cognitiveTraits?.riskTolerance ||
1187
+ profile.cognitiveTraits?.delegationDepth ||
1188
+ profile.cognitiveTraits?.debuggingStyle ||
1189
+ profile.cognitiveTraits?.explanationFormat;
1190
+ const hasStrengths = profile.technicalPreferences?.strengths && profile.technicalPreferences.strengths.length > 0;
1191
+ const hasConventions = profile.technicalPreferences?.conventions && profile.technicalPreferences.conventions.length > 0;
1192
+ const hasNotes = profile.agentNotes && profile.agentNotes.length > 0;
1193
+ console.log(`\n${pc.bold(pc.cyan('🧠 Global Adaptive User Profile & Persona Memory'))}`);
1194
+ console.log(`${pc.dim('Storage:')} ${pc.dim(getUserProfilePath())}\n`);
1195
+ if (!hasStyle && !hasCognitive && !hasStrengths && !hasConventions && !hasNotes) {
1196
+ p.log.info(pc.yellow('No personalized observations recorded yet. Mino will learn your communication style and preferences organically as you chat.'));
1197
+ }
1198
+ else {
1199
+ if (hasStyle) {
1200
+ console.log(pc.bold('Communication Style:'));
1201
+ if (profile.communicationStyle?.tonePreference) {
1202
+ console.log(` ${pc.dim('•')} Tone & Demeanor: ${pc.green(profile.communicationStyle.tonePreference)}`);
1203
+ }
1204
+ if (profile.communicationStyle?.verbosity) {
1205
+ console.log(` ${pc.dim('•')} Output Formatting: ${pc.green(profile.communicationStyle.verbosity)}`);
1206
+ }
1207
+ if (profile.communicationStyle?.formulationStyle) {
1208
+ console.log(` ${pc.dim('•')} Thought Formulation: ${pc.green(profile.communicationStyle.formulationStyle)}`);
1209
+ }
1210
+ console.log('');
1211
+ }
1212
+ if (hasCognitive) {
1213
+ console.log(pc.bold('Decision-Making & Cognitive Traits:'));
1214
+ if (profile.cognitiveTraits?.architecturalStyle) {
1215
+ console.log(` ${pc.dim('•')} Architectural Orientation: ${pc.magenta(profile.cognitiveTraits.architecturalStyle)}`);
1216
+ }
1217
+ if (profile.cognitiveTraits?.decisionPreference) {
1218
+ console.log(` ${pc.dim('•')} Decision Autonomy: ${pc.magenta(profile.cognitiveTraits.decisionPreference)}`);
1219
+ }
1220
+ if (profile.cognitiveTraits?.riskTolerance) {
1221
+ console.log(` ${pc.dim('•')} Risk & Velocity: ${pc.magenta(profile.cognitiveTraits.riskTolerance)}`);
1222
+ }
1223
+ if (profile.cognitiveTraits?.delegationDepth) {
1224
+ console.log(` ${pc.dim('•')} Delegation Depth: ${pc.magenta(profile.cognitiveTraits.delegationDepth)}`);
1225
+ }
1226
+ if (profile.cognitiveTraits?.debuggingStyle) {
1227
+ console.log(` ${pc.dim('•')} Debugging Preference: ${pc.magenta(profile.cognitiveTraits.debuggingStyle)}`);
1228
+ }
1229
+ if (profile.cognitiveTraits?.explanationFormat) {
1230
+ console.log(` ${pc.dim('•')} Explanation Preference: ${pc.magenta(profile.cognitiveTraits.explanationFormat)}`);
1231
+ }
1232
+ console.log('');
1233
+ }
1234
+ if (hasStrengths || hasConventions) {
1235
+ console.log(pc.bold('Technical Preferences:'));
1236
+ if (hasStrengths) {
1237
+ console.log(` ${pc.dim('•')} Strengths: ${pc.cyan(profile.technicalPreferences.strengths.join(', '))}`);
1238
+ }
1239
+ if (hasConventions) {
1240
+ console.log(` ${pc.dim('•')} Conventions: ${pc.cyan(profile.technicalPreferences.conventions.join(', '))}`);
1241
+ }
1242
+ console.log('');
1243
+ }
1244
+ if (hasNotes) {
1245
+ console.log(pc.bold(`AI Observations & Side-Notes (${profile.agentNotes.length}):`));
1246
+ profile.agentNotes.forEach((note, idx) => {
1247
+ console.log(` ${pc.yellow(`[${idx + 1}]`)} ${note}`);
1248
+ });
1249
+ console.log('');
1250
+ }
1251
+ }
1252
+ }
1253
+ else if (action === 'delete_note') {
1254
+ if (profile.agentNotes.length === 0) {
1255
+ p.log.warn('No side-notes recorded yet to delete.');
1256
+ }
1257
+ else {
1258
+ const noteChoices = profile.agentNotes.map((note, idx) => ({
1259
+ value: idx,
1260
+ label: `${idx + 1}. ${note.length > 70 ? note.slice(0, 67) + '...' : note}`,
1261
+ }));
1262
+ const selectedIndex = await p.select({
1263
+ message: 'Select a side-note to delete:',
1264
+ options: [...noteChoices, { value: -1, label: 'Cancel' }],
1265
+ });
1266
+ if (!p.isCancel(selectedIndex) && typeof selectedIndex === 'number' && selectedIndex >= 0) {
1267
+ await deleteSideNote(selectedIndex);
1268
+ p.log.success(`Deleted side-note [${selectedIndex + 1}].`);
1269
+ }
1270
+ }
1271
+ }
1272
+ else if (action === 'clear_all') {
1273
+ const confirmClear = await p.confirm({
1274
+ message: 'Are you sure you want to clear your global profile and all AI side-notes?',
1275
+ initialValue: false,
1276
+ });
1277
+ if (confirmClear && !p.isCancel(confirmClear)) {
1278
+ await clearUserProfile();
1279
+ p.log.success('User profile memory and side-notes have been reset.');
1280
+ }
1281
+ }
1282
+ return { shouldContinue: true };
1283
+ }
1122
1284
  return { shouldContinue: true };
1123
1285
  }
@@ -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.