minovative-mind-cli 2.14.1 → 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.
Files changed (47) hide show
  1. package/README.md +40 -59
  2. package/dist/services/agent/slashCommands.js +144 -13
  3. package/dist/services/agent/toolLoop.js +11 -2
  4. package/dist/services/agent/types.d.ts +9 -2
  5. package/dist/services/agent-tools.d.ts +20 -1
  6. package/dist/services/agent-tools.js +272 -47
  7. package/dist/services/agent.js +43 -18
  8. package/dist/services/ai.d.ts +9 -0
  9. package/dist/services/ai.js +71 -21
  10. package/dist/services/chatHistoryService.d.ts +5 -0
  11. package/dist/services/contextAgent.d.ts +14 -6
  12. package/dist/services/contextAgent.js +36 -10
  13. package/dist/services/orchestration/investigationAgent.js +31 -7
  14. package/dist/services/orchestration/investigationCache.js +19 -9
  15. package/dist/services/orchestration/readCache.d.ts +1 -0
  16. package/dist/services/orchestration/readCache.js +5 -2
  17. package/dist/services/orchestration/scopedTools.js +37 -10
  18. package/dist/services/orchestration/subAgent.js +16 -2
  19. package/dist/services/proxyClient.d.ts +6 -0
  20. package/dist/services/proxyClient.js +24 -10
  21. package/dist/services/sessionSettings.d.ts +66 -0
  22. package/dist/services/sessionSettings.js +126 -0
  23. package/dist/services/userProfileService.d.ts +14 -0
  24. package/dist/services/userProfileService.js +105 -3
  25. package/dist/services/verificationService.js +24 -2
  26. package/dist/utils/analysisRunner.d.ts +120 -8
  27. package/dist/utils/analysisRunner.js +946 -125
  28. package/dist/utils/antiCheatingGuard.d.ts +21 -0
  29. package/dist/utils/antiCheatingGuard.js +554 -0
  30. package/dist/utils/contextPrompts.d.ts +39 -0
  31. package/dist/utils/contextPrompts.js +81 -9
  32. package/dist/utils/contextRanker.d.ts +216 -0
  33. package/dist/utils/contextRanker.js +603 -0
  34. package/dist/utils/dependencyTracer/modules/graph.d.ts +4 -1
  35. package/dist/utils/dependencyTracer/modules/graph.js +11 -0
  36. package/dist/utils/dependencyTracer/modules/types.d.ts +10 -0
  37. package/dist/utils/dependencyTracer.d.ts +40 -2
  38. package/dist/utils/dependencyTracer.js +95 -3
  39. package/dist/utils/fileReadCache.d.ts +58 -0
  40. package/dist/utils/fileReadCache.js +162 -0
  41. package/dist/utils/projectStorage.js +2 -1
  42. package/dist/utils/symbolExtractor.d.ts +12 -0
  43. package/dist/utils/symbolExtractor.js +111 -15
  44. package/dist/utils/systemPrompts.d.ts +4 -3
  45. package/dist/utils/systemPrompts.js +66 -8
  46. package/oclif.manifest.json +1 -1
  47. package/package.json +1 -1
@@ -115,16 +115,19 @@ export class ReadCache {
115
115
  /**
116
116
  * Invalidates and evicts all cache entries associated with a file path,
117
117
  * including full-file reads and any ranged or targeted sub-reads.
118
+ * Uses case-insensitive and normalized path comparison.
118
119
  *
119
120
  * @param filePath - The file path that was modified or deleted.
120
121
  */
121
122
  invalidate(filePath) {
122
123
  if (!filePath)
123
124
  return;
124
- const prefix = `${filePath}::`;
125
+ const normalizedTarget = filePath.replace(/\\/g, '/').toLowerCase();
126
+ const prefix = `${normalizedTarget}::`;
125
127
  const keysToRemove = [];
126
128
  for (const key of this.cache.keys()) {
127
- if (key === filePath || key.startsWith(prefix)) {
129
+ const normKey = key.replace(/\\/g, '/').toLowerCase();
130
+ if (normKey === normalizedTarget || normKey.startsWith(prefix)) {
128
131
  keysToRemove.push(key);
129
132
  }
130
133
  }
@@ -215,18 +215,37 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
215
215
  inlineData: result.inlineData,
216
216
  });
217
217
  }
218
- // If a file was modified/written/deleted/renamed, invalidate readCache entries for affected file(s)
219
- if (readCache) {
220
- if (name === 'write_file' || name === 'modify_file' || name === 'delete_file') {
221
- if (args?.filePath) {
218
+ // If a file was modified/written/deleted/renamed, invalidate readCache and investigationCache entries for affected file(s)
219
+ if (name === 'write_file' || name === 'modify_file' || name === 'delete_file') {
220
+ if (args?.filePath) {
221
+ if (readCache) {
222
222
  readCache.invalidate(args.filePath);
223
223
  }
224
+ import('./investigationCache.js')
225
+ .then(({ invalidateFilesFromInvestigationCache }) => {
226
+ invalidateFilesFromInvestigationCache(workspaceRoot, [args.filePath]).catch(() => { });
227
+ })
228
+ .catch(() => { });
224
229
  }
225
- else if (name === 'rename_file') {
226
- if (args?.sourcePath)
230
+ }
231
+ else if (name === 'rename_file') {
232
+ const affected = [];
233
+ if (args?.sourcePath) {
234
+ if (readCache)
227
235
  readCache.invalidate(args.sourcePath);
228
- if (args?.targetPath)
236
+ affected.push(args.sourcePath);
237
+ }
238
+ if (args?.targetPath) {
239
+ if (readCache)
229
240
  readCache.invalidate(args.targetPath);
241
+ affected.push(args.targetPath);
242
+ }
243
+ if (affected.length > 0) {
244
+ import('./investigationCache.js')
245
+ .then(({ invalidateFilesFromInvestigationCache }) => {
246
+ invalidateFilesFromInvestigationCache(workspaceRoot, affected).catch(() => { });
247
+ })
248
+ .catch(() => { });
230
249
  }
231
250
  }
232
251
  // If we got a file lock and had context from a previous writer, we should
@@ -307,10 +326,18 @@ export async function executeScopedTool(name, args, workspaceRoot, agentId, bus,
307
326
  targetDesc = String(args.filePath);
308
327
  actionDesc = 'Deleted';
309
328
  }
310
- else if (name === 'run_debug_script') {
329
+ else if (name === 'run_debug_script' || name === 'run_analysis_script' || name === 'debug_script' || name === 'analysis_script') {
311
330
  targetDesc = String(args.language ?? 'node');
312
- actionDesc = 'Ran debug script';
313
- resultSummary = typeof result === 'object' && result?.error ? result.error : 'Completed debug script';
331
+ actionDesc = name.includes('analysis') ? 'Ran analysis script' : 'Ran debug script';
332
+ if (typeof result === 'object' && result?.error) {
333
+ resultSummary = result.error;
334
+ }
335
+ else if (typeof result === 'object' && result?.structuredResult !== undefined) {
336
+ resultSummary = 'Completed script (structured result)';
337
+ }
338
+ else {
339
+ resultSummary = 'Completed script';
340
+ }
314
341
  }
315
342
  else if (name === 'run_fuzz_probe') {
316
343
  targetDesc = String(args.language ?? 'node');
@@ -4,7 +4,7 @@
4
4
  * Implements the lifecycle, health monitoring, and tool-loop execution for a single
5
5
  * parallelized sub-agent.
6
6
  */
7
- import { ProxyChatSession, getGlobalActiveModel, getModelThinkingLevel } from '../ai.js';
7
+ import { ProxyChatSession, getGlobalActiveModel, getModelThinkingLevel, HISTORICAL_TOOL_OUTPUT_THRESHOLD } from '../ai.js';
8
8
  import { GEMINI_MODELS, MAX_OUTPUT_TOKENS } from '../../utils/config.js';
9
9
  import { ReadCache } from './readCache.js';
10
10
  import { executeScopedTool, getScopedToolDeclarations } from './scopedTools.js';
@@ -99,6 +99,15 @@ export function createCompactToolReference(toolName, output, args) {
99
99
  const file = args?.filePath ? `${args.filePath}` : 'file';
100
100
  return `[Wrote ${file} - tool execution completed]`;
101
101
  }
102
+ if (toolName === 'delete_file') {
103
+ const file = args?.filePath ? `${args.filePath}` : 'file';
104
+ return `[Deleted ${file} - tool execution completed]`;
105
+ }
106
+ if (toolName === 'rename_file') {
107
+ const src = args?.sourcePath || 'source';
108
+ const tgt = args?.targetPath || 'target';
109
+ return `[Renamed ${src} to ${tgt} - tool execution completed]`;
110
+ }
102
111
  if (toolName === 'find_dependencies') {
103
112
  const file = args?.filePath ? `for ${args.filePath}` : '';
104
113
  return `[Dependency trace ${file} completed]`.replace(/\s+/g, ' ');
@@ -237,7 +246,7 @@ export class SubAgentRunner {
237
246
  }
238
247
  }
239
248
  // Also trigger proxy chat session pruning to collapse any remaining long fields
240
- this.chat.pruneToolOutputHistory();
249
+ this.chat.pruneToolOutputHistory(HISTORICAL_TOOL_OUTPUT_THRESHOLD, turnsToKeep);
241
250
  }
242
251
  /**
243
252
  * Executes the sub-agent with a health monitor harness.
@@ -389,6 +398,11 @@ export class SubAgentRunner {
389
398
  `DO NOT IMPLEMENT THIS FULL REQUEST. THIS IS JUST FOR CONTEXT.\n\n` +
390
399
  `${scopedContext}\n` +
391
400
  `</reference_context>\n\n` +
401
+ `<documentation_and_quality_standards>\n` +
402
+ `- Comprehensive Documentation: Write documentation for senior engineers: explain the 'why', document edge-cases/private states, use precise types, and avoid restating the code.\n` +
403
+ `- Provide JSDoc/TSDoc/DocStrings (as appropriate for the language) for all newly added or modified APIs, functions, classes, interfaces, and types (documenting parameters, return values, and behavior).\n` +
404
+ `- Use clean inline comments to explain complex or non-obvious logic, keeping implementation code highly organized and maintainable.\n` +
405
+ `</documentation_and_quality_standards>\n\n` +
392
406
  `<critical_guidelines>\n` +
393
407
  `1. You are ONE worker in a team. Focus EXCLUSIVELY on your specific objective: "${this.intent}".\n` +
394
408
  `2. DO NOT attempt to fulfill the entire original user request in the reference context. Other agents handle other tasks.\n` +
@@ -76,6 +76,12 @@ export declare function accumulateTurnUsage(usage: {
76
76
  candidatesTokenCount?: number;
77
77
  cachedContentTokenCount?: number;
78
78
  }, modelName: string): void;
79
+ /**
80
+ * Detects whether an error message relates to an expired or mismatched Gemini context cache.
81
+ * When models transition (e.g. Flash to Flash-Lite) or a server cache expires,
82
+ * this triggers a fast retry to refresh/recreate the cache under the active model.
83
+ */
84
+ export declare function isCacheMismatchOrExpiredError(errMsg: string | undefined): boolean;
79
85
  /**
80
86
  * Client service interacting directly with the serverless Gemini proxy endpoint.
81
87
  * Ensures authorization via Firebase token passing and parses streamed content.
@@ -82,6 +82,22 @@ export function accumulateTurnUsage(usage, modelName) {
82
82
  globalSessionAccumulatedUsage.totalTokenCount += pTokens + cachedTokens + cTokens;
83
83
  globalSessionAccumulatedUsage.modelsUsed[modelName] = (globalSessionAccumulatedUsage.modelsUsed[modelName] || 0) + 1;
84
84
  }
85
+ /**
86
+ * Detects whether an error message relates to an expired or mismatched Gemini context cache.
87
+ * When models transition (e.g. Flash to Flash-Lite) or a server cache expires,
88
+ * this triggers a fast retry to refresh/recreate the cache under the active model.
89
+ */
90
+ export function isCacheMismatchOrExpiredError(errMsg) {
91
+ if (!errMsg)
92
+ return false;
93
+ const lower = errMsg.toLowerCase();
94
+ return (lower.includes('is expired') ||
95
+ lower.includes('cache content') ||
96
+ lower.includes('cached content') ||
97
+ lower.includes('cached_content') ||
98
+ lower.includes('does not match the model in the cached content') ||
99
+ lower.includes('model in the inference request'));
100
+ }
85
101
  /**
86
102
  * Client service interacting directly with the serverless Gemini proxy endpoint.
87
103
  * Ensures authorization via Firebase token passing and parses streamed content.
@@ -194,9 +210,9 @@ export class ProxyClient {
194
210
  if (!response.ok) {
195
211
  const errorData = await response.json().catch(() => ({}));
196
212
  const errorMsg = JSON.stringify(errorData) || response.statusText;
197
- if (errorMsg.includes('is expired') || errorMsg.includes('Cache content')) {
213
+ if (isCacheMismatchOrExpiredError(errorMsg)) {
198
214
  if (attempt < MAX_RETRIES) {
199
- debugLog(`Gemini Context Cache expired on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
215
+ debugLog(`Gemini Context Cache expired or mismatched on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
200
216
  await delay(500, abortSignal);
201
217
  attempt++;
202
218
  continue retryLoop;
@@ -293,8 +309,7 @@ export class ProxyClient {
293
309
  err.name = 'AbortError';
294
310
  throw err;
295
311
  }
296
- const isCacheExpired = streamError.message?.includes('is expired') ||
297
- streamError.message?.includes('Cache content');
312
+ const isCacheExpired = isCacheMismatchOrExpiredError(streamError.message);
298
313
  if (streamError.message?.includes('429') ||
299
314
  streamError.message?.includes('502') ||
300
315
  streamError.message?.includes('503') ||
@@ -312,7 +327,7 @@ export class ProxyClient {
312
327
  if (isDebugOn()) {
313
328
  process.stdout.write('\n');
314
329
  console.warn(isCacheExpired
315
- ? `Gemini Context Cache expired on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
330
+ ? `Gemini Context Cache expired or mismatched on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
316
331
  : `Server error or rate limit hit during stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
317
332
  }
318
333
  await delay(delayTime, abortSignal);
@@ -435,9 +450,9 @@ export class ProxyClient {
435
450
  // ignore parsing error
436
451
  }
437
452
  const errorMsg = errorData.error?.message || JSON.stringify(errorData) || response.statusText;
438
- if (errorMsg.includes('is expired') || errorMsg.includes('Cache content')) {
453
+ if (isCacheMismatchOrExpiredError(errorMsg)) {
439
454
  if (attempt < MAX_RETRIES) {
440
- debugLog(`BYOK Gemini Context Cache expired on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
455
+ debugLog(`BYOK Gemini Context Cache expired or mismatched on server. Retrying request to refresh cache (Attempt ${attempt + 1}/${MAX_RETRIES})...`);
441
456
  await delay(500, abortSignal);
442
457
  attempt++;
443
458
  continue retryLoop;
@@ -526,8 +541,7 @@ export class ProxyClient {
526
541
  err.name = 'AbortError';
527
542
  throw err;
528
543
  }
529
- const isCacheExpired = streamError.message?.includes('is expired') ||
530
- streamError.message?.includes('Cache content');
544
+ const isCacheExpired = isCacheMismatchOrExpiredError(streamError.message);
531
545
  if (streamError.message?.includes('429') ||
532
546
  streamError.message?.includes('502') ||
533
547
  streamError.message?.includes('503') ||
@@ -545,7 +559,7 @@ export class ProxyClient {
545
559
  if (isDebugOn()) {
546
560
  process.stdout.write('\n');
547
561
  console.warn(isCacheExpired
548
- ? `BYOK Gemini Context Cache expired on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
562
+ ? `BYOK Gemini Context Cache expired or mismatched on server. Refreshing and retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`
549
563
  : `Server error or rate limit hit during BYOK stream. Retrying in ${(delayTime / 1000).toFixed(1)}s... (Attempt ${attempt + 1}/${MAX_RETRIES})`);
550
564
  }
551
565
  await delay(delayTime, abortSignal);
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @file sessionSettings.ts
3
+ * @description Manages session-scoped slash command settings and operational toggles.
4
+ *
5
+ * Operational toggles scoped to an individual chat session include:
6
+ * - `autoApprove` : Whether automatic confirmation is enabled for shell execution commands (/auto-approve)
7
+ * - `subAgents` : Whether the MMAAK parallel multi-agent orchestration engine is enabled (/sub-agents)
8
+ * - `debugMode` : Whether internal diagnostic telemetry logging is visible (/debug)
9
+ * - `modelName` : The generative AI model selected for this session (/models)
10
+ * - `thinkingLevel`: The reasoning budget allocation level for the active model (/models)
11
+ *
12
+ * Safe Lifecycle:
13
+ * - On new chat sessions or CLI boot, settings automatically reset to safe defaults.
14
+ * - On resuming a saved session, previous settings are restored and verified.
15
+ */
16
+ import { type ThinkingLevel } from '../utils/config.js';
17
+ import { type ProxyChatSession } from './ai.js';
18
+ import { type ChatSessionData } from './chatHistoryService.js';
19
+ /**
20
+ * Session-scoped operational settings container.
21
+ */
22
+ export interface SessionSettings {
23
+ /** Whether auto-approval mode is enabled for command execution ('skip-all' vs 'ask'). */
24
+ autoApprove: boolean;
25
+ /** Whether MMAAK parallel sub-agent orchestration is enabled. */
26
+ subAgents: boolean;
27
+ /** Whether internal telemetry and diagnostic logging is enabled. */
28
+ debugMode: boolean;
29
+ /** Active generative AI model identifier for the session. */
30
+ modelName: string;
31
+ /** Active thinking/reasoning level for the model. */
32
+ thinkingLevel: ThinkingLevel;
33
+ }
34
+ /**
35
+ * Default safe settings used when creating a new chat session or resetting state.
36
+ */
37
+ export declare const DEFAULT_SESSION_SETTINGS: Readonly<SessionSettings>;
38
+ /**
39
+ * Applies saved or specified settings to the active environment and chat session.
40
+ *
41
+ * @param session - A partial session object or settings container.
42
+ * @param chat - Optional active ProxyChatSession instance to update.
43
+ * @returns The resulting applied SessionSettings.
44
+ */
45
+ export declare function applySessionSettings(session: Partial<ChatSessionData> | Partial<SessionSettings>, chat?: ProxyChatSession): SessionSettings;
46
+ /**
47
+ * Resets all session operational toggles back to default safe values.
48
+ *
49
+ * @param chat - Optional active ProxyChatSession instance to reset.
50
+ * @returns The default SessionSettings.
51
+ */
52
+ export declare function resetSessionSettingsToDefault(chat?: ProxyChatSession): SessionSettings;
53
+ /**
54
+ * Inspects and retrieves the current operational settings across modules.
55
+ *
56
+ * @param chat - Optional active ProxyChatSession instance.
57
+ * @returns The current active SessionSettings.
58
+ */
59
+ export declare function getCurrentSessionSettings(chat?: ProxyChatSession): SessionSettings;
60
+ /**
61
+ * Persists current operational settings to the saved session cache if the session exists on disk.
62
+ *
63
+ * @param sessionId - The unique identifier of the chat session to update.
64
+ * @param chat - Optional active ProxyChatSession instance.
65
+ */
66
+ export declare function syncActiveSessionSettings(sessionId?: string, chat?: ProxyChatSession): Promise<void>;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @file sessionSettings.ts
3
+ * @description Manages session-scoped slash command settings and operational toggles.
4
+ *
5
+ * Operational toggles scoped to an individual chat session include:
6
+ * - `autoApprove` : Whether automatic confirmation is enabled for shell execution commands (/auto-approve)
7
+ * - `subAgents` : Whether the MMAAK parallel multi-agent orchestration engine is enabled (/sub-agents)
8
+ * - `debugMode` : Whether internal diagnostic telemetry logging is visible (/debug)
9
+ * - `modelName` : The generative AI model selected for this session (/models)
10
+ * - `thinkingLevel`: The reasoning budget allocation level for the active model (/models)
11
+ *
12
+ * Safe Lifecycle:
13
+ * - On new chat sessions or CLI boot, settings automatically reset to safe defaults.
14
+ * - On resuming a saved session, previous settings are restored and verified.
15
+ */
16
+ import { setApprovalMode, getApprovalMode, isSubAgentsEnabled, setSubAgentsEnabled } from './agent-tools.js';
17
+ import { isDebugOn, setDebugMode } from '../utils/logger.js';
18
+ import { DEFAULT_MODEL, DEFAULT_MODEL_THINKING_LEVELS, } from '../utils/config.js';
19
+ import { getGlobalActiveModel, setGlobalActiveModel, getModelThinkingLevel, setModelThinkingLevel, } from './ai.js';
20
+ import { chatHistoryService } from './chatHistoryService.js';
21
+ /**
22
+ * Default safe settings used when creating a new chat session or resetting state.
23
+ */
24
+ export const DEFAULT_SESSION_SETTINGS = Object.freeze({
25
+ autoApprove: false,
26
+ subAgents: true,
27
+ debugMode: false,
28
+ modelName: DEFAULT_MODEL,
29
+ thinkingLevel: (DEFAULT_MODEL_THINKING_LEVELS[DEFAULT_MODEL] || 'MEDIUM'),
30
+ });
31
+ /**
32
+ * Applies saved or specified settings to the active environment and chat session.
33
+ *
34
+ * @param session - A partial session object or settings container.
35
+ * @param chat - Optional active ProxyChatSession instance to update.
36
+ * @returns The resulting applied SessionSettings.
37
+ */
38
+ export function applySessionSettings(session, chat) {
39
+ // 1. Auto-approve mode
40
+ const autoApprove = session.autoApprove ?? DEFAULT_SESSION_SETTINGS.autoApprove;
41
+ setApprovalMode(autoApprove ? 'skip-all' : 'ask');
42
+ // 2. MMAAK sub-agents
43
+ const subAgents = session.subAgents ?? DEFAULT_SESSION_SETTINGS.subAgents;
44
+ setSubAgentsEnabled(subAgents);
45
+ // 3. Debug logging
46
+ const debugMode = session.debugMode ?? DEFAULT_SESSION_SETTINGS.debugMode;
47
+ setDebugMode(debugMode);
48
+ // 4. Model selection
49
+ const modelName = session.modelName ?? DEFAULT_SESSION_SETTINGS.modelName;
50
+ setGlobalActiveModel(modelName);
51
+ if (chat && typeof chat.setModel === 'function') {
52
+ chat.setModel(modelName);
53
+ }
54
+ // 5. Thinking level
55
+ const targetThinking = session.thinkingLevel ??
56
+ getModelThinkingLevel(modelName) ??
57
+ DEFAULT_MODEL_THINKING_LEVELS[modelName] ??
58
+ DEFAULT_SESSION_SETTINGS.thinkingLevel;
59
+ setModelThinkingLevel(modelName, targetThinking);
60
+ if (chat && typeof chat.setThinkingLevel === 'function') {
61
+ chat.setThinkingLevel(targetThinking);
62
+ }
63
+ return {
64
+ autoApprove,
65
+ subAgents,
66
+ debugMode,
67
+ modelName,
68
+ thinkingLevel: targetThinking,
69
+ };
70
+ }
71
+ /**
72
+ * Resets all session operational toggles back to default safe values.
73
+ *
74
+ * @param chat - Optional active ProxyChatSession instance to reset.
75
+ * @returns The default SessionSettings.
76
+ */
77
+ export function resetSessionSettingsToDefault(chat) {
78
+ return applySessionSettings(DEFAULT_SESSION_SETTINGS, chat);
79
+ }
80
+ /**
81
+ * Inspects and retrieves the current operational settings across modules.
82
+ *
83
+ * @param chat - Optional active ProxyChatSession instance.
84
+ * @returns The current active SessionSettings.
85
+ */
86
+ export function getCurrentSessionSettings(chat) {
87
+ const autoApprove = getApprovalMode() === 'skip-all';
88
+ const subAgents = isSubAgentsEnabled();
89
+ const debugMode = isDebugOn();
90
+ const modelName = (chat && typeof chat.getModel === 'function' ? chat.getModel() : undefined) || getGlobalActiveModel();
91
+ const thinkingLevel = (chat && typeof chat.getThinkingLevel === 'function' ? chat.getThinkingLevel() : undefined) ||
92
+ getModelThinkingLevel(modelName) ||
93
+ 'MEDIUM';
94
+ return {
95
+ autoApprove,
96
+ subAgents,
97
+ debugMode,
98
+ modelName,
99
+ thinkingLevel,
100
+ };
101
+ }
102
+ /**
103
+ * Persists current operational settings to the saved session cache if the session exists on disk.
104
+ *
105
+ * @param sessionId - The unique identifier of the chat session to update.
106
+ * @param chat - Optional active ProxyChatSession instance.
107
+ */
108
+ export async function syncActiveSessionSettings(sessionId, chat) {
109
+ if (!sessionId)
110
+ return;
111
+ try {
112
+ const session = chatHistoryService.getSession(sessionId);
113
+ if (!session)
114
+ return;
115
+ const current = getCurrentSessionSettings(chat);
116
+ session.autoApprove = current.autoApprove;
117
+ session.subAgents = current.subAgents;
118
+ session.debugMode = current.debugMode;
119
+ session.modelName = current.modelName;
120
+ session.thinkingLevel = current.thinkingLevel;
121
+ await chatHistoryService.saveSession(session);
122
+ }
123
+ catch {
124
+ // Non-blocking sync failure ignored
125
+ }
126
+ }
@@ -30,6 +30,19 @@ export interface UserProfileCognitiveTraits {
30
30
  debuggingStyle?: string;
31
31
  explanationFormat?: string;
32
32
  }
33
+ /**
34
+ * Deep interpersonal, psychological, and conversational personality traits observed by the agent.
35
+ * Captures how the user speaks to the AI, relationship posture, banter, apology tolerance, and quirks.
36
+ */
37
+ export interface UserProfileConversationalPersona {
38
+ relationshipModel?: string;
39
+ banterAffinity?: string;
40
+ formalityLevel?: string;
41
+ apologyTolerance?: string;
42
+ stressCadence?: string;
43
+ promptingHabit?: string;
44
+ conversationalQuirks?: string[];
45
+ }
33
46
  /**
34
47
  * Technical habits and technology strengths observed by the agent.
35
48
  */
@@ -43,6 +56,7 @@ export interface UserProfileTechnicalPreferences {
43
56
  export interface UserProfile {
44
57
  communicationStyle?: UserProfileCommunicationStyle;
45
58
  cognitiveTraits?: UserProfileCognitiveTraits;
59
+ conversationalPersona?: UserProfileConversationalPersona;
46
60
  technicalPreferences?: UserProfileTechnicalPreferences;
47
61
  agentNotes: string[];
48
62
  lastUpdated?: number;
@@ -50,6 +50,7 @@ export async function loadUserProfile() {
50
50
  return {
51
51
  communicationStyle: {},
52
52
  cognitiveTraits: {},
53
+ conversationalPersona: { conversationalQuirks: [] },
53
54
  technicalPreferences: { strengths: [], conventions: [] },
54
55
  agentNotes: [],
55
56
  lastUpdated: Date.now(),
@@ -61,6 +62,17 @@ export async function loadUserProfile() {
61
62
  return {
62
63
  communicationStyle: parsed.communicationStyle || {},
63
64
  cognitiveTraits: parsed.cognitiveTraits || {},
65
+ conversationalPersona: {
66
+ relationshipModel: parsed.conversationalPersona?.relationshipModel,
67
+ banterAffinity: parsed.conversationalPersona?.banterAffinity,
68
+ formalityLevel: parsed.conversationalPersona?.formalityLevel,
69
+ apologyTolerance: parsed.conversationalPersona?.apologyTolerance,
70
+ stressCadence: parsed.conversationalPersona?.stressCadence,
71
+ promptingHabit: parsed.conversationalPersona?.promptingHabit,
72
+ conversationalQuirks: Array.isArray(parsed.conversationalPersona?.conversationalQuirks)
73
+ ? parsed.conversationalPersona.conversationalQuirks
74
+ : [],
75
+ },
64
76
  technicalPreferences: {
65
77
  strengths: Array.isArray(parsed.technicalPreferences?.strengths) ? parsed.technicalPreferences.strengths : [],
66
78
  conventions: Array.isArray(parsed.technicalPreferences?.conventions)
@@ -76,6 +88,7 @@ export async function loadUserProfile() {
76
88
  return {
77
89
  communicationStyle: {},
78
90
  cognitiveTraits: {},
91
+ conversationalPersona: { conversationalQuirks: [] },
79
92
  technicalPreferences: { strengths: [], conventions: [] },
80
93
  agentNotes: [],
81
94
  lastUpdated: Date.now(),
@@ -133,6 +146,8 @@ export async function deleteSideNote(index) {
133
146
  export async function clearUserProfile() {
134
147
  const emptyProfile = {
135
148
  communicationStyle: {},
149
+ cognitiveTraits: {},
150
+ conversationalPersona: { conversationalQuirks: [] },
136
151
  technicalPreferences: { strengths: [], conventions: [] },
137
152
  agentNotes: [],
138
153
  lastUpdated: Date.now(),
@@ -153,15 +168,43 @@ export function formatUserProfileForContext(profile) {
153
168
  profile.cognitiveTraits?.delegationDepth ||
154
169
  profile.cognitiveTraits?.debuggingStyle ||
155
170
  profile.cognitiveTraits?.explanationFormat;
171
+ const hasPersona = profile.conversationalPersona?.relationshipModel ||
172
+ profile.conversationalPersona?.banterAffinity ||
173
+ profile.conversationalPersona?.formalityLevel ||
174
+ profile.conversationalPersona?.apologyTolerance ||
175
+ profile.conversationalPersona?.stressCadence ||
176
+ profile.conversationalPersona?.promptingHabit ||
177
+ (profile.conversationalPersona?.conversationalQuirks && profile.conversationalPersona.conversationalQuirks.length > 0);
156
178
  const hasStrengths = profile.technicalPreferences?.strengths && profile.technicalPreferences.strengths.length > 0;
157
179
  const hasConventions = profile.technicalPreferences?.conventions && profile.technicalPreferences.conventions.length > 0;
158
180
  const hasNotes = profile.agentNotes && profile.agentNotes.length > 0;
159
- if (!hasStyle && !hasCognitive && !hasStrengths && !hasConventions && !hasNotes) {
181
+ if (!hasStyle && !hasCognitive && !hasPersona && !hasStrengths && !hasConventions && !hasNotes) {
160
182
  return '';
161
183
  }
162
184
  let block = '<user_profile>\n';
163
185
  block +=
164
- 'The following are continuous, adaptive insights and preferences learned from past interactions with this user:\n';
186
+ 'The following are continuous, adaptive insights, conversational personality traits, and technical preferences learned from past interactions with this user:\n';
187
+ if (profile.conversationalPersona?.relationshipModel) {
188
+ block += `- Teammate & Pairing Dynamic: ${profile.conversationalPersona.relationshipModel}\n`;
189
+ }
190
+ if (profile.conversationalPersona?.banterAffinity) {
191
+ block += `- Banter & Humor Affinity: ${profile.conversationalPersona.banterAffinity}\n`;
192
+ }
193
+ if (profile.conversationalPersona?.formalityLevel) {
194
+ block += `- Speech Formality: ${profile.conversationalPersona.formalityLevel}\n`;
195
+ }
196
+ if (profile.conversationalPersona?.apologyTolerance) {
197
+ block += `- Apology & Error Reaction: ${profile.conversationalPersona.apologyTolerance}\n`;
198
+ }
199
+ if (profile.conversationalPersona?.stressCadence) {
200
+ block += `- Stress & Urgency Cadence: ${profile.conversationalPersona.stressCadence}\n`;
201
+ }
202
+ if (profile.conversationalPersona?.promptingHabit) {
203
+ block += `- Prompting & Input Style: ${profile.conversationalPersona.promptingHabit}\n`;
204
+ }
205
+ if (profile.conversationalPersona?.conversationalQuirks && profile.conversationalPersona.conversationalQuirks.length > 0) {
206
+ block += `- Conversational Quirks & Catchphrases: ${profile.conversationalPersona.conversationalQuirks.join('; ')}\n`;
207
+ }
165
208
  if (profile.communicationStyle?.tonePreference) {
166
209
  block += `- Tone & Demeanor: ${profile.communicationStyle.tonePreference}\n`;
167
210
  }
@@ -202,7 +245,7 @@ export function formatUserProfileForContext(profile) {
202
245
  }
203
246
  }
204
247
  block +=
205
- 'Adopt these communication, cognitive, and technical preferences naturally without ever explicitly mentioning this profile block.\n';
248
+ 'Adopt these conversational personality traits, tone, and technical preferences naturally as an intuitive pair programming partner without ever explicitly mentioning this profile block.\n';
206
249
  block += '</user_profile>';
207
250
  return block;
208
251
  }
@@ -314,6 +357,65 @@ AI Response Summary:
314
357
  }
315
358
  }
316
359
  }
360
+ // Process conversational persona & teammate dynamics
361
+ if ((parsed.conversationalPersona && typeof parsed.conversationalPersona === 'object') ||
362
+ parsed.relationshipModel ||
363
+ parsed.banterAffinity ||
364
+ parsed.formalityLevel ||
365
+ parsed.apologyTolerance ||
366
+ parsed.stressCadence ||
367
+ parsed.promptingHabit ||
368
+ parsed.conversationalQuirks) {
369
+ currentProfile.conversationalPersona = currentProfile.conversationalPersona || { conversationalQuirks: [] };
370
+ const sourceObj = (parsed.conversationalPersona && typeof parsed.conversationalPersona === 'object')
371
+ ? parsed.conversationalPersona
372
+ : {};
373
+ const personaKeys = [
374
+ 'relationshipModel',
375
+ 'banterAffinity',
376
+ 'formalityLevel',
377
+ 'apologyTolerance',
378
+ 'stressCadence',
379
+ 'promptingHabit',
380
+ ];
381
+ for (const k of personaKeys) {
382
+ const val = sourceObj[k] || parsed[k];
383
+ if (typeof val === 'string' && val.trim().length > 0) {
384
+ currentProfile.conversationalPersona[k] = val.trim();
385
+ changed = true;
386
+ }
387
+ }
388
+ const quirksToAdd = Array.isArray(sourceObj.conversationalQuirks)
389
+ ? sourceObj.conversationalQuirks
390
+ : Array.isArray(parsed.conversationalQuirks)
391
+ ? parsed.conversationalQuirks
392
+ : [];
393
+ if (quirksToAdd.length > 0) {
394
+ const existingQuirks = new Set(currentProfile.conversationalPersona.conversationalQuirks || []);
395
+ for (const q of quirksToAdd) {
396
+ if (typeof q === 'string' && q.trim().length > 0) {
397
+ existingQuirks.add(q.trim());
398
+ changed = true;
399
+ }
400
+ }
401
+ currentProfile.conversationalPersona.conversationalQuirks = Array.from(existingQuirks).slice(0, 10);
402
+ }
403
+ const quirksToRemove = Array.isArray(sourceObj.removeQuirks)
404
+ ? sourceObj.removeQuirks
405
+ : Array.isArray(parsed.removeQuirks)
406
+ ? parsed.removeQuirks
407
+ : [];
408
+ if (quirksToRemove.length > 0) {
409
+ if (currentProfile.conversationalPersona.conversationalQuirks) {
410
+ const toRemove = new Set(quirksToRemove.map((q) => q.toLowerCase().trim()));
411
+ const filtered = currentProfile.conversationalPersona.conversationalQuirks.filter((q) => !toRemove.has(q.toLowerCase().trim()));
412
+ if (filtered.length !== currentProfile.conversationalPersona.conversationalQuirks.length) {
413
+ currentProfile.conversationalPersona.conversationalQuirks = filtered;
414
+ changed = true;
415
+ }
416
+ }
417
+ }
418
+ }
317
419
  if (Array.isArray(parsed.strengths) && parsed.strengths.length > 0) {
318
420
  currentProfile.technicalPreferences = currentProfile.technicalPreferences || { strengths: [], conventions: [] };
319
421
  const existing = new Set(currentProfile.technicalPreferences.strengths || []);