minovative-mind-cli 2.11.0 → 2.11.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 +1 -0
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.js +2 -1
- package/dist/services/agent/slashCommands.js +135 -0
- package/dist/services/agent.js +27 -0
- package/dist/services/ai.d.ts +13 -1
- package/dist/services/ai.js +210 -4
- package/dist/services/contextAgent.js +33 -22
- package/dist/services/orchestration/investigationAgent.js +0 -1
- package/dist/services/proxyClient.js +3 -2
- package/dist/services/userProfileService.d.ts +97 -0
- package/dist/services/userProfileService.js +410 -0
- package/dist/utils/systemPrompts.d.ts +3 -1
- package/dist/utils/systemPrompts.js +67 -2
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -122,6 +122,7 @@ Background tasks automatically route to dedicated auxiliary models with native `
|
|
|
122
122
|
| Command | What it does |
|
|
123
123
|
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
124
|
| `/config-key` | Configure custom Google AI Studio API key (BYOK mode) |
|
|
125
|
+
| `/profile` | View, inspect, delete, or reset global adaptive persona memory and AI side-notes |
|
|
125
126
|
| `/models` | Hot-swap the active model |
|
|
126
127
|
| `/plan` | Toggle plan mode to review implementation strategies |
|
|
127
128
|
| `/paste` | Multi-line input mode (cancel with Ctrl+C) |
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { Command } from '@oclif/core';
|
|
|
9
9
|
export default class DefaultCommand extends Command {
|
|
10
10
|
/**
|
|
11
11
|
* The description displayed in the CLI help output.
|
|
12
|
-
* Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
|
|
12
|
+
* Details all supported slash commands (/config-key, /profile, /paste, /plan, /clear, /models,
|
|
13
13
|
* /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
|
|
14
14
|
* along with interactive chat controls.
|
|
15
15
|
*/
|
package/dist/commands/chat.js
CHANGED
|
@@ -17,7 +17,7 @@ import { updateWorkspaceStatus } from '../services/workspace.js';
|
|
|
17
17
|
export default class DefaultCommand extends Command {
|
|
18
18
|
/**
|
|
19
19
|
* The description displayed in the CLI help output.
|
|
20
|
-
* Details all supported slash commands (/config-key, /paste, /plan, /clear, /models,
|
|
20
|
+
* Details all supported slash commands (/config-key, /profile, /paste, /plan, /clear, /models,
|
|
21
21
|
* /debug, /auto-approve, /sub-agents, /stats, /revert, /chats, /workspaces, /commit)
|
|
22
22
|
* along with interactive chat controls.
|
|
23
23
|
*/
|
|
@@ -25,6 +25,7 @@ export default class DefaultCommand extends Command {
|
|
|
25
25
|
|
|
26
26
|
Inside the chat session, you can use the following commands in the slash menu:
|
|
27
27
|
/config-key - BYOK — Configure, clear, or view status of custom Google AI Studio API key
|
|
28
|
+
/profile - View, inspect, delete, or reset global adaptive persona memory and AI side-notes
|
|
28
29
|
/paste - Enter multi-line paste mode for long code snippets and prompts
|
|
29
30
|
/plan - Toggle plan mode to review step-by-step implementation strategies
|
|
30
31
|
/clear - Clear conversation history, reset terminal screen, and display logo
|
|
@@ -17,12 +17,14 @@ import { checkByokSubscription } from '../auth.js';
|
|
|
17
17
|
import { GEMINI_MODELS, isByokEnabled } from '../../utils/config.js';
|
|
18
18
|
import { loadCredentials, updateCredentialField } from '../../utils/credentialStore.js';
|
|
19
19
|
import { getGlobalActiveModel, setGlobalActiveModel, ProxyChatSession } from '../ai.js';
|
|
20
|
+
import { loadUserProfile, deleteSideNote, clearUserProfile, getUserProfilePath, } from '../userProfileService.js';
|
|
20
21
|
/**
|
|
21
22
|
* @file slashCommands.ts
|
|
22
23
|
* @description Interactive slash command handler module for Minovative Mind CLI.
|
|
23
24
|
*
|
|
24
25
|
* Supported slash commands:
|
|
25
26
|
* - `/config-key` : Configure, validate, toggle, or clear Bring Your Own Key (BYOK) Google AI Studio API credentials.
|
|
27
|
+
* - `/profile` : View, inspect, delete, or reset global adaptive persona memory and AI side-notes.
|
|
26
28
|
* - `/paste` : Enter multi-line paste mode using EOF tracking (`Ctrl+D` submission).
|
|
27
29
|
* - `/plan` : Toggle AI step-by-step implementation planning mode.
|
|
28
30
|
* - `/clear` : Clear conversation history, wipe terminal screen, and reset CLI header.
|
|
@@ -1146,5 +1148,138 @@ Strict Formatting Rules:
|
|
|
1146
1148
|
}
|
|
1147
1149
|
return { shouldContinue: true };
|
|
1148
1150
|
}
|
|
1151
|
+
/**
|
|
1152
|
+
* `/profile` - Displays and manages global adaptive user profile traits, persona memory, and AI side-notes.
|
|
1153
|
+
*/
|
|
1154
|
+
if (lowerCommand === '/profile') {
|
|
1155
|
+
const profile = await loadUserProfile();
|
|
1156
|
+
const action = await p.select({
|
|
1157
|
+
message: 'Adaptive User Profile & AI Memory Bank:',
|
|
1158
|
+
options: [
|
|
1159
|
+
{
|
|
1160
|
+
value: 'view',
|
|
1161
|
+
label: 'View Profile & Observations',
|
|
1162
|
+
hint: `${profile.agentNotes.length} side-note(s) recorded`,
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
value: 'delete_note',
|
|
1166
|
+
label: 'Delete Specific Side-Note',
|
|
1167
|
+
hint: 'Select and remove an individual note',
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
value: 'clear_all',
|
|
1171
|
+
label: 'Reset Profile & Clear Memory',
|
|
1172
|
+
hint: 'Wipe all persona traits and side-notes',
|
|
1173
|
+
},
|
|
1174
|
+
{ value: 'cancel', label: 'Back to Chat', hint: 'Return to active session' },
|
|
1175
|
+
],
|
|
1176
|
+
});
|
|
1177
|
+
if (p.isCancel(action) || action === 'cancel') {
|
|
1178
|
+
return { shouldContinue: true };
|
|
1179
|
+
}
|
|
1180
|
+
if (action === 'view') {
|
|
1181
|
+
const hasStyle = profile.communicationStyle?.tonePreference ||
|
|
1182
|
+
profile.communicationStyle?.verbosity ||
|
|
1183
|
+
profile.communicationStyle?.formulationStyle;
|
|
1184
|
+
const hasCognitive = profile.cognitiveTraits?.architecturalStyle ||
|
|
1185
|
+
profile.cognitiveTraits?.decisionPreference ||
|
|
1186
|
+
profile.cognitiveTraits?.riskTolerance ||
|
|
1187
|
+
profile.cognitiveTraits?.delegationDepth ||
|
|
1188
|
+
profile.cognitiveTraits?.debuggingStyle ||
|
|
1189
|
+
profile.cognitiveTraits?.explanationFormat;
|
|
1190
|
+
const hasStrengths = profile.technicalPreferences?.strengths && profile.technicalPreferences.strengths.length > 0;
|
|
1191
|
+
const hasConventions = profile.technicalPreferences?.conventions && profile.technicalPreferences.conventions.length > 0;
|
|
1192
|
+
const hasNotes = profile.agentNotes && profile.agentNotes.length > 0;
|
|
1193
|
+
console.log(`\n${pc.bold(pc.cyan('🧠 Global Adaptive User Profile & Persona Memory'))}`);
|
|
1194
|
+
console.log(`${pc.dim('Storage:')} ${pc.dim(getUserProfilePath())}\n`);
|
|
1195
|
+
if (!hasStyle && !hasCognitive && !hasStrengths && !hasConventions && !hasNotes) {
|
|
1196
|
+
p.log.info(pc.yellow('No personalized observations recorded yet. Mino will learn your communication style and preferences organically as you chat.'));
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
if (hasStyle) {
|
|
1200
|
+
console.log(pc.bold('Communication Style:'));
|
|
1201
|
+
if (profile.communicationStyle?.tonePreference) {
|
|
1202
|
+
console.log(` ${pc.dim('•')} Tone & Demeanor: ${pc.green(profile.communicationStyle.tonePreference)}`);
|
|
1203
|
+
}
|
|
1204
|
+
if (profile.communicationStyle?.verbosity) {
|
|
1205
|
+
console.log(` ${pc.dim('•')} Output Formatting: ${pc.green(profile.communicationStyle.verbosity)}`);
|
|
1206
|
+
}
|
|
1207
|
+
if (profile.communicationStyle?.formulationStyle) {
|
|
1208
|
+
console.log(` ${pc.dim('•')} Thought Formulation: ${pc.green(profile.communicationStyle.formulationStyle)}`);
|
|
1209
|
+
}
|
|
1210
|
+
console.log('');
|
|
1211
|
+
}
|
|
1212
|
+
if (hasCognitive) {
|
|
1213
|
+
console.log(pc.bold('Decision-Making & Cognitive Traits:'));
|
|
1214
|
+
if (profile.cognitiveTraits?.architecturalStyle) {
|
|
1215
|
+
console.log(` ${pc.dim('•')} Architectural Orientation: ${pc.magenta(profile.cognitiveTraits.architecturalStyle)}`);
|
|
1216
|
+
}
|
|
1217
|
+
if (profile.cognitiveTraits?.decisionPreference) {
|
|
1218
|
+
console.log(` ${pc.dim('•')} Decision Autonomy: ${pc.magenta(profile.cognitiveTraits.decisionPreference)}`);
|
|
1219
|
+
}
|
|
1220
|
+
if (profile.cognitiveTraits?.riskTolerance) {
|
|
1221
|
+
console.log(` ${pc.dim('•')} Risk & Velocity: ${pc.magenta(profile.cognitiveTraits.riskTolerance)}`);
|
|
1222
|
+
}
|
|
1223
|
+
if (profile.cognitiveTraits?.delegationDepth) {
|
|
1224
|
+
console.log(` ${pc.dim('•')} Delegation Depth: ${pc.magenta(profile.cognitiveTraits.delegationDepth)}`);
|
|
1225
|
+
}
|
|
1226
|
+
if (profile.cognitiveTraits?.debuggingStyle) {
|
|
1227
|
+
console.log(` ${pc.dim('•')} Debugging Preference: ${pc.magenta(profile.cognitiveTraits.debuggingStyle)}`);
|
|
1228
|
+
}
|
|
1229
|
+
if (profile.cognitiveTraits?.explanationFormat) {
|
|
1230
|
+
console.log(` ${pc.dim('•')} Explanation Preference: ${pc.magenta(profile.cognitiveTraits.explanationFormat)}`);
|
|
1231
|
+
}
|
|
1232
|
+
console.log('');
|
|
1233
|
+
}
|
|
1234
|
+
if (hasStrengths || hasConventions) {
|
|
1235
|
+
console.log(pc.bold('Technical Preferences:'));
|
|
1236
|
+
if (hasStrengths) {
|
|
1237
|
+
console.log(` ${pc.dim('•')} Strengths: ${pc.cyan(profile.technicalPreferences.strengths.join(', '))}`);
|
|
1238
|
+
}
|
|
1239
|
+
if (hasConventions) {
|
|
1240
|
+
console.log(` ${pc.dim('•')} Conventions: ${pc.cyan(profile.technicalPreferences.conventions.join(', '))}`);
|
|
1241
|
+
}
|
|
1242
|
+
console.log('');
|
|
1243
|
+
}
|
|
1244
|
+
if (hasNotes) {
|
|
1245
|
+
console.log(pc.bold(`AI Observations & Side-Notes (${profile.agentNotes.length}):`));
|
|
1246
|
+
profile.agentNotes.forEach((note, idx) => {
|
|
1247
|
+
console.log(` ${pc.yellow(`[${idx + 1}]`)} ${note}`);
|
|
1248
|
+
});
|
|
1249
|
+
console.log('');
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
else if (action === 'delete_note') {
|
|
1254
|
+
if (profile.agentNotes.length === 0) {
|
|
1255
|
+
p.log.warn('No side-notes recorded yet to delete.');
|
|
1256
|
+
}
|
|
1257
|
+
else {
|
|
1258
|
+
const noteChoices = profile.agentNotes.map((note, idx) => ({
|
|
1259
|
+
value: idx,
|
|
1260
|
+
label: `${idx + 1}. ${note.length > 70 ? note.slice(0, 67) + '...' : note}`,
|
|
1261
|
+
}));
|
|
1262
|
+
const selectedIndex = await p.select({
|
|
1263
|
+
message: 'Select a side-note to delete:',
|
|
1264
|
+
options: [...noteChoices, { value: -1, label: 'Cancel' }],
|
|
1265
|
+
});
|
|
1266
|
+
if (!p.isCancel(selectedIndex) && typeof selectedIndex === 'number' && selectedIndex >= 0) {
|
|
1267
|
+
await deleteSideNote(selectedIndex);
|
|
1268
|
+
p.log.success(`Deleted side-note [${selectedIndex + 1}].`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
else if (action === 'clear_all') {
|
|
1273
|
+
const confirmClear = await p.confirm({
|
|
1274
|
+
message: 'Are you sure you want to clear your global profile and all AI side-notes?',
|
|
1275
|
+
initialValue: false,
|
|
1276
|
+
});
|
|
1277
|
+
if (confirmClear && !p.isCancel(confirmClear)) {
|
|
1278
|
+
await clearUserProfile();
|
|
1279
|
+
p.log.success('User profile memory and side-notes have been reset.');
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
return { shouldContinue: true };
|
|
1283
|
+
}
|
|
1149
1284
|
return { shouldContinue: true };
|
|
1150
1285
|
}
|
package/dist/services/agent.js
CHANGED
|
@@ -35,6 +35,7 @@ import { verifyChangedFiles } from './verificationService.js';
|
|
|
35
35
|
import { buildContextInjection } from '../utils/contextPrompts.js';
|
|
36
36
|
import { registerContextFiles } from '../utils/fileReadGuard.js';
|
|
37
37
|
import { historyText } from '../utils/historyPrompt.js';
|
|
38
|
+
import { loadUserProfile, formatUserProfileForContext, extractAndSaveUserInsights, } from './userProfileService.js';
|
|
38
39
|
// Submodule Imports
|
|
39
40
|
import { AsyncInputHandler } from './agent/inputHandler.js';
|
|
40
41
|
import { processResponse } from './agent/toolLoop.js';
|
|
@@ -207,6 +208,7 @@ export async function startAgentLoop(workspaceRoot, version) {
|
|
|
207
208
|
hint: 'Manage external workspaces for cross-project development',
|
|
208
209
|
},
|
|
209
210
|
{ value: '/config-key', label: '/config-key', hint: 'BYOK — Use your own Google AI Studio API key' },
|
|
211
|
+
{ value: '/profile', label: '/profile', hint: 'View or manage AI observations and persona side-notes' },
|
|
210
212
|
{ value: '/stats', label: '/stats', hint: 'View current session statistics and configuration' },
|
|
211
213
|
{ value: '/commit', label: '/commit', hint: 'Auto-commit changes with AI message' },
|
|
212
214
|
{ value: '/revert', label: '/revert', hint: 'Undo last change' },
|
|
@@ -451,6 +453,17 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
451
453
|
collector.recordCompressedContextSize(contextInjection.length);
|
|
452
454
|
dynamicSystemInstruction += '\n\n' + contextInjection;
|
|
453
455
|
}
|
|
456
|
+
// Inject global adaptive user persona and learned preferences
|
|
457
|
+
try {
|
|
458
|
+
const userProfile = await loadUserProfile();
|
|
459
|
+
const profileInjection = formatUserProfileForContext(userProfile);
|
|
460
|
+
if (profileInjection) {
|
|
461
|
+
dynamicSystemInstruction += '\n\n' + profileInjection;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
debugLog(`Failed to inject user profile context: ${e}`);
|
|
466
|
+
}
|
|
454
467
|
// Apply the dynamic prompt updates and tool registrations to the active chat session
|
|
455
468
|
chat.setAgentConfig(dynamicSystemInstruction, config.tools);
|
|
456
469
|
if (getGlobalActiveModel() === GEMINI_MODELS.AUTO) {
|
|
@@ -549,6 +562,13 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
549
562
|
// Print the summary text just like single-agent mode
|
|
550
563
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(Orchestrator)`)}\n`);
|
|
551
564
|
console.log(renderTerminalMarkdown(handledByOrchestrator));
|
|
565
|
+
// Observe user interaction traits and update global user profile before next prompt
|
|
566
|
+
try {
|
|
567
|
+
await extractAndSaveUserInsights(userInput, handledByOrchestrator, ac.signal);
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
debugLog(`Background user profile insight extraction failed: ${err}`);
|
|
571
|
+
}
|
|
552
572
|
}
|
|
553
573
|
// Print Usage Stats
|
|
554
574
|
const usage = getAndResetTurnUsage();
|
|
@@ -671,6 +691,13 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
671
691
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(${chat.getModel()})`)}\n`);
|
|
672
692
|
console.log(renderTerminalMarkdown(cleanFinalText));
|
|
673
693
|
chat.appendFinalSummary(cleanFinalText);
|
|
694
|
+
// Observe user interaction traits and update global user profile before next prompt
|
|
695
|
+
try {
|
|
696
|
+
await extractAndSaveUserInsights(userInput, cleanFinalText, ac.signal);
|
|
697
|
+
}
|
|
698
|
+
catch (err) {
|
|
699
|
+
debugLog(`Background user profile insight extraction failed: ${err}`);
|
|
700
|
+
}
|
|
674
701
|
}
|
|
675
702
|
}
|
|
676
703
|
if (usage) {
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -114,9 +114,21 @@ export declare function createInvestigationSemanticSession(): any;
|
|
|
114
114
|
export declare function createWebSearchAgentSession(): any;
|
|
115
115
|
export declare function createHistorySummarizerSession(): any;
|
|
116
116
|
/**
|
|
117
|
-
*
|
|
117
|
+
* Sanitizes and formats raw conversation history entries into a structured text transcript
|
|
118
|
+
* for safe consumption by the History Summarizer without requiring tool declarations or
|
|
119
|
+
* risking multi-turn schema rejections from the API.
|
|
120
|
+
*/
|
|
121
|
+
export declare function formatHistoryForSummarization(history: Content[]): string;
|
|
122
|
+
/**
|
|
123
|
+
* Summarizes an array of Content history entries using Gemini Flash.
|
|
118
124
|
*/
|
|
119
125
|
export declare function summarizeChatHistory(history: Content[], abortSignal?: AbortSignal): Promise<string>;
|
|
126
|
+
export declare function createUserProfileExtractorSession(): any;
|
|
127
|
+
/**
|
|
128
|
+
* Creates a Flash-Lite session with structured output to classify whether a user message
|
|
129
|
+
* is trivial/low-signal (greetings, confirmations, terminal commands) or contains substantive signal.
|
|
130
|
+
*/
|
|
131
|
+
export declare function createTrivialMessageClassifierSession(): any;
|
|
120
132
|
/**
|
|
121
133
|
* Generates a concise title for a chat session based on the user's first message.
|
|
122
134
|
*/
|
package/dist/services/ai.js
CHANGED
|
@@ -5,7 +5,7 @@ import { getMetricCollector } from './metrics.js';
|
|
|
5
5
|
import { getAuthorizedIdToken, checkByokSubscription } from './auth.js';
|
|
6
6
|
import { debugLog } from '../utils/logger.js';
|
|
7
7
|
import { readCache, writeCache } from '../utils/projectStorage.js';
|
|
8
|
-
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_SEMANTIC_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
8
|
+
import { GENERAL_CHAT_INSTRUCTION, PLAN_EXECUTION_INSTRUCTION, PLAN_MODE_INSTRUCTION, CONTEXT_SYSTEM_INSTRUCTION, INTENT_ROUTER_SYSTEM_INSTRUCTION, WEB_SEARCH_SYSTEM_INSTRUCTION, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, INVESTIGATION_SEMANTIC_SYSTEM_INSTRUCTION, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, USER_PROFILE_EXTRACTOR_SYSTEM_INSTRUCTION, TRIVIAL_MESSAGE_CLASSIFIER_SYSTEM_INSTRUCTION, } from '../utils/systemPrompts.js';
|
|
9
9
|
import { workspaceRegistry } from './workspaceRegistry.js';
|
|
10
10
|
import { loadCredentials } from '../utils/credentialStore.js';
|
|
11
11
|
import { ProxyClient } from './proxyClient.js';
|
|
@@ -836,7 +836,65 @@ export function createHistorySummarizerSession() {
|
|
|
836
836
|
});
|
|
837
837
|
}
|
|
838
838
|
/**
|
|
839
|
-
*
|
|
839
|
+
* Sanitizes and formats raw conversation history entries into a structured text transcript
|
|
840
|
+
* for safe consumption by the History Summarizer without requiring tool declarations or
|
|
841
|
+
* risking multi-turn schema rejections from the API.
|
|
842
|
+
*/
|
|
843
|
+
export function formatHistoryForSummarization(history) {
|
|
844
|
+
if (!history || history.length === 0)
|
|
845
|
+
return '';
|
|
846
|
+
const lines = [];
|
|
847
|
+
for (const entry of history) {
|
|
848
|
+
if (!entry || !Array.isArray(entry.parts))
|
|
849
|
+
continue;
|
|
850
|
+
const roleLabel = entry.role === 'model' ? 'Assistant' : 'User';
|
|
851
|
+
const textPieces = [];
|
|
852
|
+
for (const part of entry.parts) {
|
|
853
|
+
if (!part)
|
|
854
|
+
continue;
|
|
855
|
+
if (typeof part.text === 'string' && part.text.trim()) {
|
|
856
|
+
textPieces.push(part.text.trim());
|
|
857
|
+
}
|
|
858
|
+
else if (part.functionCall) {
|
|
859
|
+
const name = part.functionCall.name || 'unknown_tool';
|
|
860
|
+
const argsStr = part.functionCall.args ? JSON.stringify(part.functionCall.args) : '';
|
|
861
|
+
const compactArgs = argsStr.length > 500 ? argsStr.substring(0, 500) + '...' : argsStr;
|
|
862
|
+
textPieces.push(`[Tool Invoked: ${name}(${compactArgs})]`);
|
|
863
|
+
}
|
|
864
|
+
else if (part.functionResponse) {
|
|
865
|
+
const name = part.functionResponse.name || 'unknown_tool';
|
|
866
|
+
const respObj = part.functionResponse.response;
|
|
867
|
+
let respStr = '';
|
|
868
|
+
if (respObj) {
|
|
869
|
+
if (typeof respObj.output === 'string') {
|
|
870
|
+
respStr = respObj.output;
|
|
871
|
+
}
|
|
872
|
+
else if (typeof respObj.result === 'string') {
|
|
873
|
+
respStr = respObj.result;
|
|
874
|
+
}
|
|
875
|
+
else if (typeof respObj.error === 'string') {
|
|
876
|
+
respStr = `Error: ${respObj.error}`;
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
respStr = JSON.stringify(respObj);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const compactResp = respStr.length > 1000 ? respStr.substring(0, 1000) + '...' : respStr;
|
|
883
|
+
textPieces.push(`[Tool Output (${name})]: ${compactResp}`);
|
|
884
|
+
}
|
|
885
|
+
else if (part.inlineData) {
|
|
886
|
+
textPieces.push(`[Inline Data Attachment: ${part.inlineData.mimeType || 'unknown'}]`);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
const turnText = textPieces.join('\n').trim();
|
|
890
|
+
if (turnText) {
|
|
891
|
+
lines.push(`[${roleLabel}]:\n${turnText}`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return lines.join('\n\n');
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Summarizes an array of Content history entries using Gemini Flash.
|
|
840
898
|
*/
|
|
841
899
|
export async function summarizeChatHistory(history, abortSignal) {
|
|
842
900
|
if (!history || history.length === 0) {
|
|
@@ -848,9 +906,13 @@ export async function summarizeChatHistory(history, abortSignal) {
|
|
|
848
906
|
throw err;
|
|
849
907
|
}
|
|
850
908
|
try {
|
|
909
|
+
const formattedTranscript = formatHistoryForSummarization(history);
|
|
910
|
+
if (!formattedTranscript || !formattedTranscript.trim()) {
|
|
911
|
+
return '';
|
|
912
|
+
}
|
|
851
913
|
const session = createHistorySummarizerSession();
|
|
852
|
-
|
|
853
|
-
const result = await session.sendMessage(
|
|
914
|
+
const prompt = `Summarize the preceding conversation history following your compression rules:\n\n<conversation_history>\n${formattedTranscript}\n</conversation_history>`;
|
|
915
|
+
const result = await session.sendMessage(prompt, undefined, abortSignal);
|
|
854
916
|
return result.response.text() || '';
|
|
855
917
|
}
|
|
856
918
|
catch (error) {
|
|
@@ -863,6 +925,150 @@ export async function summarizeChatHistory(history, abortSignal) {
|
|
|
863
925
|
return '';
|
|
864
926
|
}
|
|
865
927
|
}
|
|
928
|
+
// ─── User Profile Extractor Service ────────────────────────────────────
|
|
929
|
+
export function createUserProfileExtractorSession() {
|
|
930
|
+
// Configured exclusively with FLASH_LITE for lightweight zero-latency profiling
|
|
931
|
+
const model = GEMINI_MODELS.FLASH_LITE;
|
|
932
|
+
return new ProxyChatSession(model, USER_PROFILE_EXTRACTOR_SYSTEM_INSTRUCTION, [], // no tools
|
|
933
|
+
{
|
|
934
|
+
temperature: 0,
|
|
935
|
+
responseMimeType: 'application/json',
|
|
936
|
+
responseSchema: {
|
|
937
|
+
type: SchemaType.OBJECT,
|
|
938
|
+
properties: {
|
|
939
|
+
tonePreference: {
|
|
940
|
+
type: SchemaType.STRING,
|
|
941
|
+
description: 'Observed tone and demeanor traits of the user',
|
|
942
|
+
},
|
|
943
|
+
verbosity: {
|
|
944
|
+
type: SchemaType.STRING,
|
|
945
|
+
description: 'Observed verbosity and output formatting preference',
|
|
946
|
+
},
|
|
947
|
+
formulationStyle: {
|
|
948
|
+
type: SchemaType.STRING,
|
|
949
|
+
description: 'Observed thought formulation and prompting style',
|
|
950
|
+
},
|
|
951
|
+
cognitiveTraits: {
|
|
952
|
+
type: SchemaType.OBJECT,
|
|
953
|
+
properties: {
|
|
954
|
+
architecturalStyle: {
|
|
955
|
+
type: SchemaType.STRING,
|
|
956
|
+
description: 'e.g., "top-down-design", "bottom-up-code", "balanced"',
|
|
957
|
+
},
|
|
958
|
+
decisionPreference: {
|
|
959
|
+
type: SchemaType.STRING,
|
|
960
|
+
description: 'e.g., "direct-recommendation", "present-options"',
|
|
961
|
+
},
|
|
962
|
+
riskTolerance: {
|
|
963
|
+
type: SchemaType.STRING,
|
|
964
|
+
description: 'e.g., "defensive-rigor", "pragmatic-speed"',
|
|
965
|
+
},
|
|
966
|
+
delegationDepth: {
|
|
967
|
+
type: SchemaType.STRING,
|
|
968
|
+
description: 'e.g., "autonomous-delegation", "hands-on-stepwise"',
|
|
969
|
+
},
|
|
970
|
+
debuggingStyle: {
|
|
971
|
+
type: SchemaType.STRING,
|
|
972
|
+
description: 'e.g., "minimal-diff-fix", "root-cause-deep-dive"',
|
|
973
|
+
},
|
|
974
|
+
explanationFormat: {
|
|
975
|
+
type: SchemaType.STRING,
|
|
976
|
+
description: 'e.g., "code-first", "bullet-summaries", "conceptual-analogies"',
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
description: 'Observed cognitive decision-making, collaboration, and problem-solving traits',
|
|
980
|
+
},
|
|
981
|
+
strengths: {
|
|
982
|
+
type: SchemaType.ARRAY,
|
|
983
|
+
items: {
|
|
984
|
+
type: SchemaType.STRING,
|
|
985
|
+
},
|
|
986
|
+
description: 'Observed technical strengths or technologies the user is actively familiar with',
|
|
987
|
+
},
|
|
988
|
+
removeStrengths: {
|
|
989
|
+
type: SchemaType.ARRAY,
|
|
990
|
+
items: {
|
|
991
|
+
type: SchemaType.STRING,
|
|
992
|
+
},
|
|
993
|
+
description: 'Outdated or abandoned technologies to remove when a pivot/contradiction occurs',
|
|
994
|
+
},
|
|
995
|
+
conventions: {
|
|
996
|
+
type: SchemaType.ARRAY,
|
|
997
|
+
items: {
|
|
998
|
+
type: SchemaType.STRING,
|
|
999
|
+
},
|
|
1000
|
+
description: 'Observed architectural habits or coding conventions',
|
|
1001
|
+
},
|
|
1002
|
+
removeConventions: {
|
|
1003
|
+
type: SchemaType.ARRAY,
|
|
1004
|
+
items: {
|
|
1005
|
+
type: SchemaType.STRING,
|
|
1006
|
+
},
|
|
1007
|
+
description: 'Outdated coding conventions to remove when a pivot/contradiction occurs',
|
|
1008
|
+
},
|
|
1009
|
+
addNotes: {
|
|
1010
|
+
type: SchemaType.ARRAY,
|
|
1011
|
+
items: {
|
|
1012
|
+
type: SchemaType.STRING,
|
|
1013
|
+
},
|
|
1014
|
+
description: 'New non-conflicting side-notes to add (empty if none)',
|
|
1015
|
+
},
|
|
1016
|
+
updateNotes: {
|
|
1017
|
+
type: SchemaType.ARRAY,
|
|
1018
|
+
items: {
|
|
1019
|
+
type: SchemaType.OBJECT,
|
|
1020
|
+
properties: {
|
|
1021
|
+
index: {
|
|
1022
|
+
type: SchemaType.INTEGER,
|
|
1023
|
+
description: '0-based index of the existing note to update',
|
|
1024
|
+
},
|
|
1025
|
+
updatedText: {
|
|
1026
|
+
type: SchemaType.STRING,
|
|
1027
|
+
description: 'Revised note text that supersedes the outdated observation',
|
|
1028
|
+
},
|
|
1029
|
+
},
|
|
1030
|
+
required: ['index', 'updatedText'],
|
|
1031
|
+
},
|
|
1032
|
+
description: 'Targeted updates to existing notes that evolved',
|
|
1033
|
+
},
|
|
1034
|
+
deleteNoteIndices: {
|
|
1035
|
+
type: SchemaType.ARRAY,
|
|
1036
|
+
items: {
|
|
1037
|
+
type: SchemaType.INTEGER,
|
|
1038
|
+
},
|
|
1039
|
+
description: '0-based indices of contradicted or obsolete notes to delete',
|
|
1040
|
+
},
|
|
1041
|
+
},
|
|
1042
|
+
required: ['addNotes', 'updateNotes', 'deleteNoteIndices'],
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Creates a Flash-Lite session with structured output to classify whether a user message
|
|
1048
|
+
* is trivial/low-signal (greetings, confirmations, terminal commands) or contains substantive signal.
|
|
1049
|
+
*/
|
|
1050
|
+
export function createTrivialMessageClassifierSession() {
|
|
1051
|
+
const model = GEMINI_MODELS.FLASH_LITE;
|
|
1052
|
+
return new ProxyChatSession(model, TRIVIAL_MESSAGE_CLASSIFIER_SYSTEM_INSTRUCTION, [], // no tools
|
|
1053
|
+
{
|
|
1054
|
+
temperature: 0,
|
|
1055
|
+
responseMimeType: 'application/json',
|
|
1056
|
+
responseSchema: {
|
|
1057
|
+
type: SchemaType.OBJECT,
|
|
1058
|
+
properties: {
|
|
1059
|
+
isTrivial: {
|
|
1060
|
+
type: SchemaType.BOOLEAN,
|
|
1061
|
+
description: 'True if the message is a low-signal greeting, confirmation, affirmation, or simple control command with zero technical/personal traits',
|
|
1062
|
+
},
|
|
1063
|
+
reason: {
|
|
1064
|
+
type: SchemaType.STRING,
|
|
1065
|
+
description: 'Brief reason for the classification decision',
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
required: ['isTrivial'],
|
|
1069
|
+
},
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
866
1072
|
/**
|
|
867
1073
|
* Generates a concise title for a chat session based on the user's first message.
|
|
868
1074
|
*/
|
|
@@ -322,24 +322,26 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
322
322
|
onProgress(`⚡ Memory Bank HIT — loaded ${cacheHit.entry.relevantFiles.length} files from cache`);
|
|
323
323
|
const cachedFiles = new Map();
|
|
324
324
|
let cumulativeFileTokens = 0;
|
|
325
|
-
|
|
326
|
-
if (abortSignal.aborted)
|
|
327
|
-
|
|
328
|
-
err.name = 'AbortError';
|
|
329
|
-
throw err;
|
|
330
|
-
}
|
|
331
|
-
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
332
|
-
break;
|
|
325
|
+
const readResults = await Promise.all(cacheHit.entry.relevantFiles.map(async (filePath) => {
|
|
326
|
+
if (abortSignal.aborted)
|
|
327
|
+
return { filePath, error: 'aborted' };
|
|
333
328
|
const readResult = await executeTool(workspaceRoot, 'read_file', { filePath });
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
329
|
+
return { filePath, readResult };
|
|
330
|
+
}));
|
|
331
|
+
for (const item of readResults) {
|
|
332
|
+
if (item.readResult && !item.readResult.error) {
|
|
333
|
+
const boundedText = boundFileContent(item.filePath, item.readResult.output);
|
|
334
|
+
const tokens = estimateTokenCount(boundedText);
|
|
335
|
+
if (cumulativeFileTokens + tokens <= MAX_TOTAL_FILE_TOKENS) {
|
|
336
|
+
cachedFiles.set(item.filePath, { text: boundedText, inlineData: item.readResult.inlineData });
|
|
337
|
+
cumulativeFileTokens += tokens;
|
|
338
|
+
}
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
341
|
const MAX_TOTAL_FILES = 30;
|
|
341
342
|
try {
|
|
342
343
|
const { resolveAndValidateMultiWorkspacePath } = await import('../utils/pathSecurity.js');
|
|
344
|
+
const depsToRead = [];
|
|
343
345
|
const autoDiscovered = new Set();
|
|
344
346
|
for (const filePath of cacheHit.entry.relevantFiles) {
|
|
345
347
|
if (abortSignal.aborted)
|
|
@@ -349,25 +351,34 @@ export async function gatherContext(workspaceRoot, userRequest, chatHistory = ''
|
|
|
349
351
|
const graph = await buildDependencyGraph(resolved.workspaceRoot);
|
|
350
352
|
const reverseDeps = graph.getImportedBy(resolved.relativePath);
|
|
351
353
|
for (const dep of reverseDeps) {
|
|
352
|
-
if (abortSignal.aborted)
|
|
353
|
-
break;
|
|
354
354
|
if (cachedFiles.has(dep) || autoDiscovered.has(dep))
|
|
355
355
|
continue;
|
|
356
356
|
if (cachedFiles.size + autoDiscovered.size >= MAX_TOTAL_FILES)
|
|
357
357
|
break;
|
|
358
|
-
if (cumulativeFileTokens >= MAX_TOTAL_FILE_TOKENS)
|
|
359
|
-
break;
|
|
360
358
|
autoDiscovered.add(dep);
|
|
361
|
-
|
|
362
|
-
if (!depReadRes.error) {
|
|
363
|
-
const boundedText = boundFileContent(dep, depReadRes.output);
|
|
364
|
-
cachedFiles.set(dep, { text: boundedText, inlineData: depReadRes.inlineData });
|
|
365
|
-
cumulativeFileTokens += estimateTokenCount(boundedText);
|
|
366
|
-
}
|
|
359
|
+
depsToRead.push({ dep, resolvedWorkspace: resolved.workspaceRoot });
|
|
367
360
|
}
|
|
368
361
|
}
|
|
369
362
|
catch (e) { }
|
|
370
363
|
}
|
|
364
|
+
if (depsToRead.length > 0) {
|
|
365
|
+
const depResults = await Promise.all(depsToRead.map(async ({ dep, resolvedWorkspace }) => {
|
|
366
|
+
if (abortSignal.aborted)
|
|
367
|
+
return { dep, error: 'aborted' };
|
|
368
|
+
const depReadRes = await executeTool(resolvedWorkspace, 'read_file', { filePath: dep });
|
|
369
|
+
return { dep, depReadRes };
|
|
370
|
+
}));
|
|
371
|
+
for (const item of depResults) {
|
|
372
|
+
if (item.depReadRes && !item.depReadRes.error) {
|
|
373
|
+
const boundedText = boundFileContent(item.dep, item.depReadRes.output);
|
|
374
|
+
const tokens = estimateTokenCount(boundedText);
|
|
375
|
+
if (cumulativeFileTokens + tokens <= MAX_TOTAL_FILE_TOKENS) {
|
|
376
|
+
cachedFiles.set(item.dep, { text: boundedText, inlineData: item.depReadRes.inlineData });
|
|
377
|
+
cumulativeFileTokens += tokens;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
371
382
|
}
|
|
372
383
|
catch (e) { }
|
|
373
384
|
const { accumulateUsage } = await import('./metrics.js');
|
|
@@ -359,11 +359,12 @@ export class ProxyClient {
|
|
|
359
359
|
// ignore parsing error
|
|
360
360
|
}
|
|
361
361
|
const errorMsg = errorData.error?.message || response.statusText;
|
|
362
|
-
if (response.status ===
|
|
363
|
-
response.status === 401 ||
|
|
362
|
+
if (response.status === 401 ||
|
|
364
363
|
response.status === 403 ||
|
|
365
364
|
errorMsg.includes('API_KEY_INVALID') ||
|
|
365
|
+
errorMsg.includes('API_KEY_EXPIRED') ||
|
|
366
366
|
errorMsg.includes('quota') ||
|
|
367
|
+
errorMsg.includes('RESOURCE_EXHAUSTED') ||
|
|
367
368
|
errorMsg.includes('PERMISSION_DENIED')) {
|
|
368
369
|
throw new Error('AI_BYOK_ERROR: Your API key or quota is invalid. Please run /config-key to update your settings.');
|
|
369
370
|
}
|