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.
- package/README.md +40 -59
- package/dist/services/agent/slashCommands.js +144 -13
- package/dist/services/agent/toolLoop.js +11 -2
- package/dist/services/agent/types.d.ts +9 -2
- package/dist/services/agent-tools.d.ts +20 -1
- package/dist/services/agent-tools.js +272 -47
- package/dist/services/agent.js +43 -18
- package/dist/services/ai.d.ts +9 -0
- package/dist/services/ai.js +71 -21
- package/dist/services/chatHistoryService.d.ts +5 -0
- package/dist/services/contextAgent.d.ts +14 -6
- package/dist/services/contextAgent.js +36 -10
- package/dist/services/orchestration/investigationAgent.js +31 -7
- package/dist/services/orchestration/investigationCache.js +19 -9
- package/dist/services/orchestration/readCache.d.ts +1 -0
- package/dist/services/orchestration/readCache.js +5 -2
- package/dist/services/orchestration/scopedTools.js +37 -10
- package/dist/services/orchestration/subAgent.js +16 -2
- package/dist/services/proxyClient.d.ts +6 -0
- package/dist/services/proxyClient.js +24 -10
- package/dist/services/sessionSettings.d.ts +66 -0
- package/dist/services/sessionSettings.js +126 -0
- package/dist/services/userProfileService.d.ts +14 -0
- package/dist/services/userProfileService.js +105 -3
- package/dist/services/verificationService.js +24 -2
- package/dist/utils/analysisRunner.d.ts +120 -8
- package/dist/utils/analysisRunner.js +946 -125
- package/dist/utils/antiCheatingGuard.d.ts +21 -0
- package/dist/utils/antiCheatingGuard.js +554 -0
- package/dist/utils/contextPrompts.d.ts +39 -0
- package/dist/utils/contextPrompts.js +81 -9
- package/dist/utils/contextRanker.d.ts +216 -0
- package/dist/utils/contextRanker.js +603 -0
- package/dist/utils/dependencyTracer/modules/graph.d.ts +4 -1
- package/dist/utils/dependencyTracer/modules/graph.js +11 -0
- package/dist/utils/dependencyTracer/modules/types.d.ts +10 -0
- package/dist/utils/dependencyTracer.d.ts +40 -2
- package/dist/utils/dependencyTracer.js +95 -3
- package/dist/utils/fileReadCache.d.ts +58 -0
- package/dist/utils/fileReadCache.js +162 -0
- package/dist/utils/projectStorage.js +2 -1
- package/dist/utils/symbolExtractor.d.ts +12 -0
- package/dist/utils/symbolExtractor.js +111 -15
- package/dist/utils/systemPrompts.d.ts +4 -3
- package/dist/utils/systemPrompts.js +66 -8
- package/oclif.manifest.json +1 -1
- 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
|
|
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
|
-
|
|
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 (
|
|
220
|
-
if (
|
|
221
|
-
if (
|
|
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
|
-
|
|
226
|
-
|
|
230
|
+
}
|
|
231
|
+
else if (name === 'rename_file') {
|
|
232
|
+
const affected = [];
|
|
233
|
+
if (args?.sourcePath) {
|
|
234
|
+
if (readCache)
|
|
227
235
|
readCache.invalidate(args.sourcePath);
|
|
228
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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 || []);
|