minovative-mind-cli 2.14.2 → 2.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -156,14 +156,14 @@ Minovative Mind CLI continuously adapts to your unique developer personality and
156
156
  | `/profile` | View, inspect, delete, or reset global adaptive persona memory, pairing dynamics, and AI side-notes |
157
157
  | `/models` | Hot-swap the active model or configure its native reasoning thinking level (`Low`, `Medium`, `High`) |
158
158
  | `/paste` | Multi-line input mode (cancel with Ctrl+C) |
159
- | `/clear` | Clear conversation history |
160
- | `/debug` | Expose internal agent diagnostics |
161
- | `/auto-approve` | Toggle skipping confirmation prompts for commands |
162
- | `/sub-agents` | Toggle the MMAAK Engine for parallel investigation and execution (concurrency limited to 2) |
159
+ | `/clear` | Clear conversation history and reset session settings to safe defaults |
160
+ | `/debug` | Toggle internal agent telemetry and diagnostic logging (saved per session) |
161
+ | `/auto-approve` | Toggle skipping confirmation prompts for commands (saved per session, automatically resets to 'ask' on new sessions) |
162
+ | `/sub-agents` | Toggle the MMAAK Engine for parallel investigation and execution (saved per session, concurrency limited to 2) |
163
163
  | `/stats` | View session and all-time statistics, token telemetry, active model settings, and live thinking level runtime telemetry |
164
164
  | `/commit` | Generate a conventional commit message from your diff |
165
165
  | `/revert` | Undo changes from the last turn, or toggle the revert logger |
166
- | `/chats` | View, resume, rename, or delete previous chat sessions (with centralized token telemetry, Git branch, auto-approve status, and sub-agent state) |
166
+ | `/chats` | View, resume, rename, or delete previous chat sessions (with session settings restoration, Git branch, auto-approve status, and sub-agent state) |
167
167
  | `/workspaces` | Manage active workspaces and linked cross-repo aliases |
168
168
  | `stop` | Abort generation immediately |
169
169
 
@@ -19,6 +19,10 @@ import { GEMINI_MODELS, isByokEnabled, DEFAULT_MODEL_THINKING_LEVELS } from '../
19
19
  import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
20
20
  import { getGlobalActiveModel, setGlobalActiveModel, getModelThinkingLevel, setModelThinkingLevel, ProxyChatSession } from '../ai.js';
21
21
  import { loadUserProfile, deleteSideNote, clearUserProfile, getUserProfilePath } from '../userProfileService.js';
22
+ import { applySessionSettings, resetSessionSettingsToDefault, syncActiveSessionSettings, } from '../sessionSettings.js';
23
+ import { clearFileReadCache } from '../../utils/fileReadCache.js';
24
+ import { invalidateDependencyGraph } from '../../utils/dependencyTracer.js';
25
+ import { clearSymbolExtractorCache } from '../../utils/symbolExtractor.js';
22
26
  /**
23
27
  * @file slashCommands.ts
24
28
  * @description Interactive slash command handler module for Minovative Mind CLI.
@@ -79,12 +83,22 @@ export async function handleSlashCommand(command, context) {
79
83
  */
80
84
  if (lowerCommand === '/clear') {
81
85
  chat.clearHistory();
86
+ resetSessionSettingsToDefault(chat);
87
+ if (chatSessionState) {
88
+ chatSessionState.autoApprove = false;
89
+ chatSessionState.subAgents = true;
90
+ chatSessionState.debugMode = false;
91
+ chatSessionState.modelName = chat.getModel();
92
+ chatSessionState.thinkingLevel =
93
+ (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
94
+ getModelThinkingLevel(chat.getModel());
95
+ }
82
96
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
83
97
  printLogo();
84
98
  p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + version)}`);
85
99
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
86
100
  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.`);
87
- p.log.success('Chat history cleared.');
101
+ p.log.success('Chat history cleared (session settings reset to defaults).');
88
102
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
89
103
  return { shouldContinue: true };
90
104
  }
@@ -139,11 +153,17 @@ export async function handleSlashCommand(command, context) {
139
153
  if (!p.isCancel(selectedModel)) {
140
154
  chat.setModel(selectedModel);
141
155
  setGlobalActiveModel(selectedModel);
156
+ if (chatSessionState) {
157
+ chatSessionState.modelName = selectedModel;
158
+ }
142
159
  if (selectedModel === 'auto') {
143
160
  const defaultAutoThinking = DEFAULT_MODEL_THINKING_LEVELS[GEMINI_MODELS.AUTO] || 'MEDIUM';
144
161
  if (typeof chat.setThinkingLevel === 'function') {
145
162
  chat.setThinkingLevel(defaultAutoThinking);
146
163
  }
164
+ if (chatSessionState) {
165
+ chatSessionState.thinkingLevel = defaultAutoThinking;
166
+ }
147
167
  p.log.success(`Model successfully switched to ${pc.cyan('Auto (Smart Routing & Reasoning)')}`);
148
168
  }
149
169
  else {
@@ -174,15 +194,22 @@ export async function handleSlashCommand(command, context) {
174
194
  if (typeof chat.setThinkingLevel === 'function') {
175
195
  chat.setThinkingLevel(selectedThinking);
176
196
  }
197
+ if (chatSessionState) {
198
+ chatSessionState.thinkingLevel = selectedThinking;
199
+ }
177
200
  p.log.success(`Model successfully switched to ${pc.cyan(selectedModel)} with thinking level ${pc.cyan(selectedThinking)}`);
178
201
  }
179
202
  else {
180
203
  if (typeof chat.setThinkingLevel === 'function') {
181
204
  chat.setThinkingLevel(activeModelThinking);
182
205
  }
206
+ if (chatSessionState) {
207
+ chatSessionState.thinkingLevel = activeModelThinking;
208
+ }
183
209
  p.log.success(`Model successfully switched to ${pc.cyan(selectedModel)}`);
184
210
  }
185
211
  }
212
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
186
213
  }
187
214
  return { shouldContinue: true };
188
215
  }
@@ -191,6 +218,10 @@ export async function handleSlashCommand(command, context) {
191
218
  */
192
219
  if (lowerCommand === '/debug') {
193
220
  const debugMode = toggleDebugMode();
221
+ if (chatSessionState) {
222
+ chatSessionState.debugMode = debugMode;
223
+ }
224
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
194
225
  if (debugMode) {
195
226
  const currentModel = chat.getModel();
196
227
  const currentThinkingLevel = (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
@@ -208,10 +239,18 @@ export async function handleSlashCommand(command, context) {
208
239
  if (lowerCommand === '/auto-approve') {
209
240
  if (getApprovalMode() === 'skip-all') {
210
241
  setApprovalMode('ask');
242
+ if (chatSessionState) {
243
+ chatSessionState.autoApprove = false;
244
+ }
245
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
211
246
  p.log.success('Auto-approve disabled. You will be prompted before commands run.');
212
247
  }
213
248
  else {
214
249
  setApprovalMode('skip-all');
250
+ if (chatSessionState) {
251
+ chatSessionState.autoApprove = true;
252
+ }
253
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
215
254
  p.log.success('Auto-approve enabled for all future commands in this session.');
216
255
  }
217
256
  return { shouldContinue: true };
@@ -222,10 +261,18 @@ export async function handleSlashCommand(command, context) {
222
261
  if (lowerCommand === '/sub-agents') {
223
262
  if (isSubAgentsEnabled()) {
224
263
  setSubAgentsEnabled(false);
264
+ if (chatSessionState) {
265
+ chatSessionState.subAgents = false;
266
+ }
267
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
225
268
  p.log.success('MMAAK Engine disabled. Single-agent mode active.');
226
269
  }
227
270
  else {
228
271
  setSubAgentsEnabled(true);
272
+ if (chatSessionState) {
273
+ chatSessionState.subAgents = true;
274
+ }
275
+ await syncActiveSessionSettings(chatSessionState?.id, chat);
229
276
  p.log.success('MMAAK Engine enabled. The system will now use parallel investigation and thread agents.');
230
277
  }
231
278
  return { shouldContinue: true };
@@ -478,6 +525,9 @@ export async function handleSlashCommand(command, context) {
478
525
  await fs.writeFile(absolutePath, change.originalContent, 'utf-8');
479
526
  }
480
527
  }
528
+ clearFileReadCache();
529
+ invalidateDependencyGraph(workspaceRoot);
530
+ clearSymbolExtractorCache();
481
531
  spinner.stop('Reverted successfully.');
482
532
  p.log.success(`Reverted ${changesToRevert.length} changeset(s) successfully.`);
483
533
  }
@@ -514,6 +564,7 @@ export async function handleSlashCommand(command, context) {
514
564
  }
515
565
  if (chatsMenu === 'new') {
516
566
  chat.clearHistory();
567
+ resetSessionSettingsToDefault(chat);
517
568
  if (chatSessionState) {
518
569
  chatSessionState.id = crypto.randomUUID();
519
570
  chatSessionState.title = '';
@@ -521,6 +572,13 @@ export async function handleSlashCommand(command, context) {
521
572
  chatSessionState.totalInputTokens = 0;
522
573
  chatSessionState.totalOutputTokens = 0;
523
574
  chatSessionState.totalCreditsUsed = 0;
575
+ chatSessionState.autoApprove = false;
576
+ chatSessionState.subAgents = true;
577
+ chatSessionState.debugMode = false;
578
+ chatSessionState.modelName = chat.getModel();
579
+ chatSessionState.thinkingLevel =
580
+ (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
581
+ getModelThinkingLevel(chat.getModel());
524
582
  chat.setSessionInfo(chatSessionState.id, workspaceRoot);
525
583
  }
526
584
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
@@ -528,7 +586,7 @@ export async function handleSlashCommand(command, context) {
528
586
  p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + version)}`);
529
587
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
530
588
  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.`);
531
- p.log.success('Started a new chat session.');
589
+ p.log.success('Started a new chat session (settings reset to defaults).');
532
590
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
533
591
  return { shouldContinue: true };
534
592
  }
@@ -577,6 +635,8 @@ export async function handleSlashCommand(command, context) {
577
635
  }
578
636
  const session = sessions.find((s) => s.id === selectedSessionId);
579
637
  if (session) {
638
+ // Restore session-scoped operational settings
639
+ const applied = applySessionSettings(session, chat);
580
640
  // Update timestamp so it becomes the most recently used session
581
641
  session.timestamp = Date.now();
582
642
  await chatHistoryService.saveSession(session);
@@ -591,6 +651,11 @@ export async function handleSlashCommand(command, context) {
591
651
  chatSessionState.latestUsageMetadata = session.latestUsageMetadata;
592
652
  chatSessionState.previousUsageMetadata = session.previousUsageMetadata;
593
653
  chatSessionState.modelUsageCounts = session.modelUsageCounts || {};
654
+ chatSessionState.autoApprove = applied.autoApprove;
655
+ chatSessionState.subAgents = applied.subAgents;
656
+ chatSessionState.debugMode = applied.debugMode;
657
+ chatSessionState.modelName = applied.modelName;
658
+ chatSessionState.thinkingLevel = applied.thinkingLevel;
594
659
  chat.setSessionInfo(session.id, workspaceRoot);
595
660
  }
596
661
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
@@ -599,6 +664,16 @@ export async function handleSlashCommand(command, context) {
599
664
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
600
665
  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.`);
601
666
  p.log.success(`Resumed session: ${session.title}`);
667
+ // Display session settings restoration status badge
668
+ const autoApproveBadge = applied.autoApprove ? pc.green('ON') : pc.dim('OFF');
669
+ const subAgentsBadge = applied.subAgents ? pc.green('ON') : pc.red('OFF');
670
+ const debugBadge = applied.debugMode ? pc.green('ON') : pc.dim('OFF');
671
+ const modelBadge = pc.cyan(applied.modelName);
672
+ const thinkingBadge = pc.cyan(applied.thinkingLevel);
673
+ p.log.info(`${pc.dim('Session Settings:')} Model: ${modelBadge} (thinking: ${thinkingBadge}) | Auto-Approve: ${autoApproveBadge} | Sub-Agents: ${subAgentsBadge} | Debug: ${debugBadge}`);
674
+ if (applied.autoApprove) {
675
+ p.log.warn(pc.yellow('Notice: Auto-approve is ENABLED for this resumed session.'));
676
+ }
602
677
  // Print the loaded history cleanly: only user and AI conversational messages,
603
678
  // with a clean indicator label if the turn involved tool/code changes.
604
679
  let turnHadModifications = false;
@@ -689,9 +764,6 @@ export async function handleSlashCommand(command, context) {
689
764
  if (session.lastTurnDuration !== undefined) {
690
765
  p.log.info(`${pc.dim('Generated in')} ${pc.cyan(session.lastTurnDuration + 's')}`);
691
766
  }
692
- if (session.modelName) {
693
- p.log.info(`${pc.dim('Model:')} ${pc.cyan(session.modelName)}`);
694
- }
695
767
  if (session.totalTokens !== undefined) {
696
768
  const inputStr = session.totalInputTokens
697
769
  ? ` (Input: ${session.totalInputTokens.toLocaleString()}, Output: ${(session.totalOutputTokens || 0).toLocaleString()})`
@@ -704,11 +776,6 @@ export async function handleSlashCommand(command, context) {
704
776
  if (session.gitBranch) {
705
777
  p.log.info(`${pc.dim('Last Active Branch:')} ${pc.cyan(session.gitBranch)}`);
706
778
  }
707
- if (session.autoApprove !== undefined || session.subAgents !== undefined) {
708
- const aa = session.autoApprove ? pc.green('Enabled') : pc.yellow('Disabled');
709
- const sa = session.subAgents ? pc.green('Enabled') : pc.yellow('Disabled');
710
- p.log.info(`${pc.dim('Sub-Agents:')} ${sa} | ${pc.dim('Auto-Approve:')} ${aa}`);
711
- }
712
779
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
713
780
  }
714
781
  }
@@ -754,12 +821,27 @@ export async function handleSlashCommand(command, context) {
754
821
  p.log.success('Session deleted successfully.');
755
822
  if (chatSessionState && chatSessionState.id === selectedSessionId) {
756
823
  chat.clearHistory();
824
+ resetSessionSettingsToDefault(chat);
825
+ chatSessionState.id = crypto.randomUUID();
826
+ chatSessionState.title = '';
827
+ chatSessionState.totalTokens = 0;
828
+ chatSessionState.totalInputTokens = 0;
829
+ chatSessionState.totalOutputTokens = 0;
830
+ chatSessionState.totalCreditsUsed = 0;
831
+ chatSessionState.autoApprove = false;
832
+ chatSessionState.subAgents = true;
833
+ chatSessionState.debugMode = false;
834
+ chatSessionState.modelName = chat.getModel();
835
+ chatSessionState.thinkingLevel =
836
+ (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
837
+ getModelThinkingLevel(chat.getModel());
838
+ chat.setSessionInfo(chatSessionState.id, workspaceRoot);
757
839
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
758
840
  printLogo();
759
841
  p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + version)}`);
760
842
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
761
843
  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.`);
762
- p.log.warn('Active session was deleted. Chat history cleared.');
844
+ p.log.warn('Active session was deleted. Chat history and settings reset to defaults.');
763
845
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
764
846
  }
765
847
  }
@@ -787,12 +869,27 @@ export async function handleSlashCommand(command, context) {
787
869
  p.log.success(`Successfully deleted ${selectedIds.length} session(s).`);
788
870
  if (chatSessionState && selectedIds.includes(chatSessionState.id)) {
789
871
  chat.clearHistory();
872
+ resetSessionSettingsToDefault(chat);
873
+ chatSessionState.id = crypto.randomUUID();
874
+ chatSessionState.title = '';
875
+ chatSessionState.totalTokens = 0;
876
+ chatSessionState.totalInputTokens = 0;
877
+ chatSessionState.totalOutputTokens = 0;
878
+ chatSessionState.totalCreditsUsed = 0;
879
+ chatSessionState.autoApprove = false;
880
+ chatSessionState.subAgents = true;
881
+ chatSessionState.debugMode = false;
882
+ chatSessionState.modelName = chat.getModel();
883
+ chatSessionState.thinkingLevel =
884
+ (typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
885
+ getModelThinkingLevel(chat.getModel());
886
+ chat.setSessionInfo(chatSessionState.id, workspaceRoot);
790
887
  process.stdout.write('\x1B[2J\x1B[3J\x1B[H'); // Hard clear screen and scrollback
791
888
  printLogo();
792
889
  p.intro(`${brandBg(' Minovative Mind CLI ')} ${pc.dim('v' + version)}`);
793
890
  p.log.info(`${pc.dim('Workspace:')} ${brandFg(workspaceRoot)}`);
794
891
  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.`);
795
- p.log.warn('Active session was deleted. Chat history cleared.');
892
+ p.log.warn('Active session was deleted. Chat history and settings reset to defaults.');
796
893
  console.log(pc.dim('\nType your coding request below. Type "exit" or "quit" to leave.\n'));
797
894
  }
798
895
  }
@@ -3,7 +3,7 @@ import * as p from '@clack/prompts';
3
3
  import pc from 'picocolors';
4
4
  import { debugLog } from '../../utils/logger.js';
5
5
  import { executeTool } from '../agent-tools.js';
6
- import { getPlanExecutionConfig, HISTORICAL_TOOL_OUTPUT_THRESHOLD } from '../ai.js';
6
+ import { getPlanExecutionConfig, getResearchConfig, HISTORICAL_TOOL_OUTPUT_THRESHOLD } from '../ai.js';
7
7
  import { routeIntent } from '../contextAgent.js';
8
8
  import { requestCommandApproval } from './commandApproval.js';
9
9
  import { getMetricCollector, recordRecoveryCircuitBreakerTrip } from '../metrics.js';
@@ -265,7 +265,7 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
265
265
  if (queuedMsg) {
266
266
  additionalText = `[USER INTERRUPTION] The user sent the following message during your execution:\n"${queuedMsg}"\n\nPlease incorporate this feedback into your ongoing work. Address the user's message, but DO NOT lose track of your original overall plan or focus. After addressing this interruption, continue with your broader objective.`;
267
267
  p.log.info(pc.cyan(`Sending queued message to AI...`));
268
- // Dynamically upgrade agent permissions/intent to EXECUTE mode if the interrupted instruction requires system modifications
268
+ // Dynamically upgrade agent permissions/intent if the interrupted instruction requires system modifications
269
269
  const newIntent = await routeIntent(queuedMsg, '', abortSignal);
270
270
  if (agentState.targetAgent === 'CHAT') {
271
271
  if (newIntent.targetAgent === 'EXECUTE' || newIntent.needsContext) {
@@ -274,7 +274,14 @@ export async function processResponse(chat, result, workspaceRoot, inputHandler,
274
274
  chat.setAgentConfig(config.systemInstruction, config.tools);
275
275
  p.log.info(pc.yellow(`Upgraded session intent to EXECUTE based on chained message.`));
276
276
  }
277
+ else if (newIntent.targetAgent === 'RESEARCH') {
278
+ agentState.targetAgent = 'RESEARCH';
279
+ const config = getResearchConfig();
280
+ chat.setAgentConfig(config.systemInstruction, config.tools);
281
+ p.log.info(pc.cyan(`Upgraded session intent to RESEARCH based on chained message.`));
282
+ }
277
283
  }
284
+ // If agentState.targetAgent === 'RESEARCH', keep RESEARCH mode and incorporate into ongoing research
278
285
  }
279
286
  // Apply in-place history pruning to collapse oversized tool responses older than 1 turn
280
287
  chat.pruneToolOutputHistory(HISTORICAL_TOOL_OUTPUT_THRESHOLD, 1);
@@ -1,12 +1,14 @@
1
1
  import type { ProxyChatSession } from '../ai.js';
2
2
  import type { AsyncInputHandler } from './inputHandler.js';
3
+ export type TargetAgent = 'CHAT' | 'RESEARCH' | 'EXECUTE';
3
4
  /**
4
5
  * State tracking container for the active agent operational mode.
5
6
  */
6
7
  export interface AgentState {
7
- /** The target agent mode: 'CHAT' for conversational interactions or 'EXECUTE' for tool/command execution. */
8
- targetAgent: 'CHAT' | 'EXECUTE';
8
+ /** The target agent mode: 'CHAT' for conversational interactions, 'RESEARCH' for active diagnostics/benchmarks, or 'EXECUTE' for tool/command execution. */
9
+ targetAgent: TargetAgent;
9
10
  }
11
+ import type { ThinkingLevel } from '../../utils/config.js';
10
12
  /**
11
13
  * Context object provided to slash command handlers containing session handles, terminal state, and metadata.
12
14
  */
@@ -32,6 +34,11 @@ export interface SlashCommandContext {
32
34
  latestUsageMetadata?: any;
33
35
  previousUsageMetadata?: any;
34
36
  modelUsageCounts?: Record<string, number>;
37
+ autoApprove?: boolean;
38
+ subAgents?: boolean;
39
+ debugMode?: boolean;
40
+ modelName?: string;
41
+ thinkingLevel?: ThinkingLevel;
35
42
  };
36
43
  }
37
44
  /**
@@ -31,11 +31,12 @@ export interface ToolResult {
31
31
  /**
32
32
  * Returns the list of available function declarations for Gemini function calling.
33
33
  *
34
- * @param options - Configuration options such as `isExecutionAgent`.
34
+ * @param options - Configuration options such as `isExecutionAgent` or `isResearchAgent`.
35
35
  * @returns An array of Google Generative AI `FunctionDeclaration` objects.
36
36
  */
37
37
  export declare function getToolDeclarations(options?: {
38
38
  isExecutionAgent?: boolean;
39
+ isResearchAgent?: boolean;
39
40
  }): FunctionDeclaration[];
40
41
  /**
41
42
  * FunctionDeclaration-compatible schema objects that describe
@@ -23,13 +23,15 @@ import { findBestMatch, applyMatch } from '../utils/fuzzyMatch.js';
23
23
  import { localValidate } from '../utils/localSyntaxValidator.js';
24
24
  import { validateAndFixSyntax, aiFuzzyMatch } from './agent/syntaxAgent.js';
25
25
  import { sanitizeForCDATA } from '../utils/contextPrompts.js';
26
- import { findDependencies, formatDependencyResult } from '../utils/dependencyTracer.js';
26
+ import { findDependencies, formatDependencyResult, invalidateDependencyGraph } from '../utils/dependencyTracer.js';
27
27
  import { atomicWriteFile } from '../utils/atomicWrite.js';
28
28
  import { EXCLUDED_EXTENSIONS } from '../utils/excludedExtensions.js';
29
29
  import { extractSymbols } from '../utils/symbolExtractor.js';
30
30
  import { getMetricCollector, recordRecoveryPrunedLogVolume } from './metrics.js';
31
31
  import { getCurrentAgentId } from '../utils/asyncContext.js';
32
32
  import { recordFileRead, hasAgentReadFile } from '../utils/fileReadGuard.js';
33
+ import { getCachedFileContent, invalidateFileReadCache, clearFileReadCache } from '../utils/fileReadCache.js';
34
+ import { detectAntiCheatingViolations, isCriticalConfigFile } from '../utils/antiCheatingGuard.js';
33
35
  import { runFuzzProbe, checkHeapDelta as runCheckHeapDelta, checkBehavioralDrift as runCheckBehavioralDrift, runEphemeralScript, } from '../utils/analysisRunner.js';
34
36
  import ignore from 'ignore';
35
37
  const execAsync = promisify(exec);
@@ -37,13 +39,24 @@ const execAsync = promisify(exec);
37
39
  /**
38
40
  * Returns the list of available function declarations for Gemini function calling.
39
41
  *
40
- * @param options - Configuration options such as `isExecutionAgent`.
42
+ * @param options - Configuration options such as `isExecutionAgent` or `isResearchAgent`.
41
43
  * @returns An array of Google Generative AI `FunctionDeclaration` objects.
42
44
  */
43
45
  export function getToolDeclarations(options) {
44
46
  if (options?.isExecutionAgent) {
45
47
  return toolDeclarations;
46
48
  }
49
+ if (options?.isResearchAgent) {
50
+ const excludedForResearch = new Set([
51
+ 'modify_file',
52
+ 'write_file',
53
+ 'delete_file',
54
+ 'rename_file',
55
+ 'create_todo_list',
56
+ 'update_todo_status',
57
+ ]);
58
+ return toolDeclarations.filter((tool) => !excludedForResearch.has(tool.name));
59
+ }
47
60
  return toolDeclarations.filter((tool) => tool.name !== 'create_todo_list' && tool.name !== 'update_todo_status' && tool.name !== 'finish_task');
48
61
  }
49
62
  /**
@@ -626,7 +639,7 @@ export async function readFile(workspaceRoot, filePath, startLine, endLine, targ
626
639
  };
627
640
  }
628
641
  }
629
- let content = await fs.readFile(absPath, 'utf-8');
642
+ let content = await getCachedFileContent(absPath);
630
643
  // Handle Jupyter Notebooks natively
631
644
  if (filePath.toLowerCase().endsWith('.ipynb')) {
632
645
  try {
@@ -829,9 +842,15 @@ export async function writeFile(workspaceRoot, filePath, content) {
829
842
  };
830
843
  }
831
844
  }
845
+ const antiCheatError = detectAntiCheatingViolations(filePath, finalContent, existingContent ?? undefined);
846
+ if (antiCheatError) {
847
+ return { output: '', error: antiCheatError };
848
+ }
832
849
  await fs.mkdir(path.dirname(absPath), { recursive: true });
833
850
  changeLogger.logChange(filePath, existingContent, existingContent !== null ? 'modify' : 'create');
834
851
  await atomicWriteFile(absPath, finalContent, 'utf-8');
852
+ invalidateFileReadCache(absPath);
853
+ invalidateDependencyGraph(workspaceRoot);
835
854
  return { output: `Successfully wrote to "${filePath}".` };
836
855
  }
837
856
  catch (err) {
@@ -854,6 +873,12 @@ export async function writeFile(workspaceRoot, filePath, content) {
854
873
  */
855
874
  export async function deleteFile(workspaceRoot, filePath) {
856
875
  try {
876
+ if (isCriticalConfigFile(filePath)) {
877
+ return {
878
+ output: '',
879
+ error: `Anti-cheating violation: Deleting critical configuration file "${filePath}" is strictly prohibited. You must fix all build, compiler, type, and lint errors head-on in the source code.`,
880
+ };
881
+ }
857
882
  const absPath = resolveAndValidatePath(workspaceRoot, filePath);
858
883
  let existingContent = null;
859
884
  try {
@@ -864,6 +889,8 @@ export async function deleteFile(workspaceRoot, filePath) {
864
889
  }
865
890
  changeLogger.logChange(filePath, existingContent, 'delete');
866
891
  await fs.rm(absPath, { force: true });
892
+ invalidateFileReadCache(absPath);
893
+ invalidateDependencyGraph(workspaceRoot);
867
894
  return { output: `Successfully deleted "${filePath}".` };
868
895
  }
869
896
  catch (err) {
@@ -881,6 +908,12 @@ export async function deleteFile(workspaceRoot, filePath) {
881
908
  */
882
909
  export async function renameFile(workspaceRoot, sourcePath, targetPath) {
883
910
  try {
911
+ if (isCriticalConfigFile(sourcePath)) {
912
+ return {
913
+ output: '',
914
+ error: `Anti-cheating violation: Moving or renaming critical configuration file "${sourcePath}" is strictly prohibited. You must fix all build, compiler, type, and lint errors head-on in the source code.`,
915
+ };
916
+ }
884
917
  const absSource = resolveAndValidatePath(workspaceRoot, sourcePath);
885
918
  const absTarget = resolveAndValidatePath(workspaceRoot, targetPath);
886
919
  let existingContent = null;
@@ -896,6 +929,9 @@ export async function renameFile(workspaceRoot, sourcePath, targetPath) {
896
929
  changeLogger.logChange(targetPath, null, 'create');
897
930
  await fs.mkdir(path.dirname(absTarget), { recursive: true });
898
931
  await fs.rename(absSource, absTarget);
932
+ invalidateFileReadCache(absSource);
933
+ invalidateFileReadCache(absTarget);
934
+ invalidateDependencyGraph(workspaceRoot);
899
935
  return { output: `Successfully moved/renamed "${sourcePath}" to "${targetPath}".` };
900
936
  }
901
937
  catch (err) {
@@ -1018,10 +1054,16 @@ export async function modifyFile(workspaceRoot, filePath, edits) {
1018
1054
  };
1019
1055
  }
1020
1056
  }
1057
+ const antiCheatError = detectAntiCheatingViolations(filePath, finalModified, existing);
1058
+ if (antiCheatError) {
1059
+ return { output: '', error: antiCheatError };
1060
+ }
1021
1061
  changeLogger.logChange(filePath, existing, 'modify');
1022
1062
  await atomicWriteFile(absPath, finalModified, 'utf-8');
1063
+ invalidateFileReadCache(absPath);
1064
+ invalidateDependencyGraph(workspaceRoot);
1023
1065
  return {
1024
- output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\\nStrategies used:\\n${strategies.join('\\n')}`,
1066
+ output: `Successfully applied ${edits.length} edit(s) to "${filePath}".\nStrategies used:\n${strategies.join('\n')}`,
1025
1067
  };
1026
1068
  }
1027
1069
  catch (err) {
@@ -1385,6 +1427,9 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
1385
1427
  maxBuffer: 1024 * 1024 * 2, // 2 MB buffer
1386
1428
  signal: abortSignal,
1387
1429
  });
1430
+ // Invalidate caches as shell command may have modified the workspace filesystem
1431
+ clearFileReadCache();
1432
+ invalidateDependencyGraph(workspaceRoot);
1388
1433
  const output = [stdout, stderr].filter(Boolean).join('\n');
1389
1434
  // Bounded head/tail window truncation with structured metadata receipt
1390
1435
  const windowedOutput = truncateWithHeadTailWindow(output, {
@@ -1395,6 +1440,9 @@ export async function runCommand(workspaceRoot, command, abortSignal) {
1395
1440
  return { output: windowedOutput || '(command produced no output)' };
1396
1441
  }
1397
1442
  catch (err) {
1443
+ // Also invalidate in case of partial command execution or errors that wrote files
1444
+ clearFileReadCache();
1445
+ invalidateDependencyGraph(workspaceRoot);
1398
1446
  const message = err instanceof Error ? err.message : String(err);
1399
1447
  // Condense error output with high-signal extraction and dynamic tail sizing
1400
1448
  const condensedError = extractHighSignalError(message);