minovative-mind-cli 2.10.0 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/commands/chat.d.ts +1 -1
- package/dist/commands/chat.js +2 -1
- package/dist/services/agent/commandApproval.js +5 -2
- package/dist/services/agent/slashCommands.js +213 -51
- package/dist/services/agent-tools.d.ts +4 -3
- package/dist/services/agent-tools.js +32 -79
- package/dist/services/agent.d.ts +5 -6
- package/dist/services/agent.js +38 -15
- package/dist/services/ai.d.ts +25 -0
- package/dist/services/ai.js +253 -2
- package/dist/services/chatHistoryService.d.ts +95 -2
- package/dist/services/chatHistoryService.js +236 -9
- package/dist/services/contextAgent.js +184 -89
- package/dist/services/orchestration/investigationAgent.js +100 -84
- package/dist/services/orchestration/investigationOrchestrator.js +6 -2
- package/dist/services/orchestration/orchestrator.js +6 -3
- package/dist/services/orchestration/scopedTools.js +5 -0
- package/dist/services/orchestration/subAgent.d.ts +31 -1
- package/dist/services/orchestration/subAgent.js +153 -2
- package/dist/services/userProfileService.d.ts +97 -0
- package/dist/services/userProfileService.js +410 -0
- package/dist/utils/analysisRunner.d.ts +29 -0
- package/dist/utils/analysisRunner.js +200 -5
- package/dist/utils/contextPrompts.d.ts +19 -3
- package/dist/utils/contextPrompts.js +144 -26
- package/dist/utils/historyPrompt.d.ts +92 -1
- package/dist/utils/historyPrompt.js +166 -2
- package/dist/utils/symbolExtractor.d.ts +12 -0
- package/dist/utils/symbolExtractor.js +946 -0
- package/dist/utils/systemPrompts.d.ts +6 -4
- package/dist/utils/systemPrompts.js +77 -9
- package/oclif.manifest.json +2 -2
- package/package.json +1 -1
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) {
|
|
@@ -544,11 +557,18 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
544
557
|
parts: [{ text: userInput }],
|
|
545
558
|
}, {
|
|
546
559
|
role: 'model',
|
|
547
|
-
parts: [{ text: handledByOrchestrator }],
|
|
560
|
+
parts: [{ text: `[ORCHESTRATOR_EXECUTION]\n${handledByOrchestrator}` }],
|
|
548
561
|
});
|
|
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();
|
|
@@ -670,6 +690,14 @@ export async function executeSingleTurn(workspaceRoot, userInput, chat, inputHan
|
|
|
670
690
|
if (cleanFinalText) {
|
|
671
691
|
console.log(`\n${pc.blue('◆')} ${pc.bold('Minovative Mind')} ${pc.dim(`(${chat.getModel()})`)}\n`);
|
|
672
692
|
console.log(renderTerminalMarkdown(cleanFinalText));
|
|
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
|
+
}
|
|
673
701
|
}
|
|
674
702
|
}
|
|
675
703
|
if (usage) {
|
|
@@ -989,15 +1017,14 @@ async function compressContextFiles(workspaceRoot, contextResult) {
|
|
|
989
1017
|
}
|
|
990
1018
|
/**
|
|
991
1019
|
* Raw chat history entry count threshold to trigger chat history summarization.
|
|
992
|
-
*
|
|
1020
|
+
* 2 entries = 1 complete conversational user/model turn.
|
|
993
1021
|
*/
|
|
994
|
-
export const HISTORY_SUMMARIZATION_THRESHOLD =
|
|
1022
|
+
export const HISTORY_SUMMARIZATION_THRESHOLD = 2;
|
|
995
1023
|
/**
|
|
996
|
-
* Summarizes
|
|
997
|
-
* exceeds {@link HISTORY_SUMMARIZATION_THRESHOLD} entries.
|
|
1024
|
+
* Summarizes chat history entries starting from the first full user/model turn.
|
|
998
1025
|
*
|
|
999
|
-
*
|
|
1000
|
-
*
|
|
1026
|
+
* Compresses preceding conversation history into a structured high-density summary pair
|
|
1027
|
+
* in the active session while permanently preserving uncompressed history in fullHistory.
|
|
1001
1028
|
*
|
|
1002
1029
|
* @param chat - The active ProxyChatSession instance.
|
|
1003
1030
|
* @returns A promise resolving to true if history was summarized and updated; false otherwise.
|
|
@@ -1008,12 +1035,8 @@ export async function summarizeHistoryIfNeeded(chat, abortSignal) {
|
|
|
1008
1035
|
return false;
|
|
1009
1036
|
}
|
|
1010
1037
|
try {
|
|
1011
|
-
|
|
1012
|
-
const
|
|
1013
|
-
const olderHistory = history.slice(0, history.length - recentCount);
|
|
1014
|
-
const recentHistory = history.slice(history.length - recentCount);
|
|
1015
|
-
debugLog(`Summarizing ${olderHistory.length} older chat history entries...`);
|
|
1016
|
-
const summaryText = await summarizeChatHistory(olderHistory, abortSignal);
|
|
1038
|
+
debugLog(`Summarizing ${history.length} chat history entries (Flash-Lite Summarizer triggered after first turn)...`);
|
|
1039
|
+
const summaryText = await summarizeChatHistory(history, abortSignal);
|
|
1017
1040
|
if (summaryText && summaryText.trim().length > 0) {
|
|
1018
1041
|
const summaryContent = [
|
|
1019
1042
|
{
|
|
@@ -1025,8 +1048,8 @@ export async function summarizeHistoryIfNeeded(chat, abortSignal) {
|
|
|
1025
1048
|
parts: [{ text: 'Understood. I have reviewed and absorbed the summary of our preceding conversation.' }],
|
|
1026
1049
|
},
|
|
1027
1050
|
];
|
|
1028
|
-
chat.loadCompressedHistory(
|
|
1029
|
-
debugLog(`Chat history compressed successfully from ${history.length} to ${summaryContent.length
|
|
1051
|
+
chat.loadCompressedHistory(summaryContent);
|
|
1052
|
+
debugLog(`Chat history compressed successfully from ${history.length} to ${summaryContent.length} entries.`);
|
|
1030
1053
|
return true;
|
|
1031
1054
|
}
|
|
1032
1055
|
}
|
package/dist/services/ai.d.ts
CHANGED
|
@@ -22,6 +22,12 @@ export declare class ProxyChatSession {
|
|
|
22
22
|
loadRawHistory(history: Content[]): void;
|
|
23
23
|
loadCompressedHistory(history: Content[]): void;
|
|
24
24
|
addTurn(userContent: Content, modelContent: Content): void;
|
|
25
|
+
/**
|
|
26
|
+
* Appends or merges the final conversational summary text into the active session history.
|
|
27
|
+
* Ensures that the final response produced by finish_task, PM reconciliation, or final turns
|
|
28
|
+
* is permanently preserved as a model text response in both working history and full transcript.
|
|
29
|
+
*/
|
|
30
|
+
appendFinalSummary(text: string): void;
|
|
25
31
|
/**
|
|
26
32
|
* Retrieves the most recent conversation history as a formatted string.
|
|
27
33
|
* Useful for passing conversation context to stateless background agents.
|
|
@@ -42,6 +48,19 @@ export declare class ProxyChatSession {
|
|
|
42
48
|
* conversational context while preventing OOM crashes.
|
|
43
49
|
*/
|
|
44
50
|
private pruneHistory;
|
|
51
|
+
/**
|
|
52
|
+
* Collapses oversized functionResponse outputs in historical turns (>1 turn old)
|
|
53
|
+
* while strictly maintaining Gemini functionCall and functionResponse pairing.
|
|
54
|
+
*
|
|
55
|
+
* Gemini requires every functionCall in a model turn to have a matching functionResponse
|
|
56
|
+
* in the immediately following user turn. Deleting parts or entries breaks this invariant and
|
|
57
|
+
* causes 400 Bad Request errors. This method mutates oversized response outputs in-place
|
|
58
|
+
* on older turns to drastically conserve context window tokens.
|
|
59
|
+
*
|
|
60
|
+
* @param threshold Maximum characters allowed for a historical tool response before collapsing (default: 1500).
|
|
61
|
+
* @param turnsToKeep Number of recent turns to preserve unpruned (default: 1).
|
|
62
|
+
*/
|
|
63
|
+
pruneToolOutputHistory(threshold?: number, turnsToKeep?: number): void;
|
|
45
64
|
sendMessage(message: string | Array<{
|
|
46
65
|
functionResponse: {
|
|
47
66
|
name: string;
|
|
@@ -98,6 +117,12 @@ export declare function createHistorySummarizerSession(): any;
|
|
|
98
117
|
* Summarizes an array of Content history entries using Gemini Flash Lite.
|
|
99
118
|
*/
|
|
100
119
|
export declare function summarizeChatHistory(history: Content[], abortSignal?: AbortSignal): Promise<string>;
|
|
120
|
+
export declare function createUserProfileExtractorSession(): any;
|
|
121
|
+
/**
|
|
122
|
+
* Creates a Flash-Lite session with structured output to classify whether a user message
|
|
123
|
+
* is trivial/low-signal (greetings, confirmations, terminal commands) or contains substantive signal.
|
|
124
|
+
*/
|
|
125
|
+
export declare function createTrivialMessageClassifierSession(): any;
|
|
101
126
|
/**
|
|
102
127
|
* Generates a concise title for a chat session based on the user's first message.
|
|
103
128
|
*/
|
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';
|
|
@@ -62,6 +62,14 @@ const MAX_HISTORY_ENTRIES = 500;
|
|
|
62
62
|
* payload stays within sane memory bounds.
|
|
63
63
|
*/
|
|
64
64
|
const MAX_PART_TEXT_LENGTH = 60_000;
|
|
65
|
+
const HISTORICAL_TOOL_OUTPUT_THRESHOLD = 1500;
|
|
66
|
+
const COLLAPSED_TOOL_OUTPUT_MARKER = '\n... [Historical tool output collapsed to save context]';
|
|
67
|
+
function collapseHistoricalOutput(val, threshold = HISTORICAL_TOOL_OUTPUT_THRESHOLD) {
|
|
68
|
+
if (val.length <= threshold || val.includes('[Historical tool output collapsed')) {
|
|
69
|
+
return val;
|
|
70
|
+
}
|
|
71
|
+
return `${val.substring(0, threshold)}${COLLAPSED_TOOL_OUTPUT_MARKER}`;
|
|
72
|
+
}
|
|
65
73
|
function truncatePartText(text) {
|
|
66
74
|
if (text.length <= MAX_PART_TEXT_LENGTH)
|
|
67
75
|
return text;
|
|
@@ -117,6 +125,64 @@ export class ProxyChatSession {
|
|
|
117
125
|
this.fullHistory.push(JSON.parse(JSON.stringify(userContent)));
|
|
118
126
|
this.fullHistory.push(JSON.parse(JSON.stringify(modelContent)));
|
|
119
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Appends or merges the final conversational summary text into the active session history.
|
|
130
|
+
* Ensures that the final response produced by finish_task, PM reconciliation, or final turns
|
|
131
|
+
* is permanently preserved as a model text response in both working history and full transcript.
|
|
132
|
+
*/
|
|
133
|
+
appendFinalSummary(text) {
|
|
134
|
+
if (!text || !text.trim())
|
|
135
|
+
return;
|
|
136
|
+
const clean = text.replace(/\[TASK_FINISHED\]/g, '').trim();
|
|
137
|
+
if (!clean)
|
|
138
|
+
return;
|
|
139
|
+
// 1. Ensure fullHistory has the model text response
|
|
140
|
+
if (this.fullHistory.length > 0) {
|
|
141
|
+
const lastFull = this.fullHistory[this.fullHistory.length - 1];
|
|
142
|
+
if (lastFull.role === 'model') {
|
|
143
|
+
const hasText = lastFull.parts?.some((p) => p.text && p.text.trim() === clean);
|
|
144
|
+
if (!hasText) {
|
|
145
|
+
lastFull.parts = lastFull.parts || [];
|
|
146
|
+
lastFull.parts.push({ text: clean });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
this.fullHistory.push({
|
|
151
|
+
role: 'model',
|
|
152
|
+
parts: [{ text: clean }],
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
this.fullHistory.push({
|
|
158
|
+
role: 'model',
|
|
159
|
+
parts: [{ text: clean }],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// 2. Ensure working history has the model text response
|
|
163
|
+
if (this.history.length > 0) {
|
|
164
|
+
const lastHist = this.history[this.history.length - 1];
|
|
165
|
+
if (lastHist.role === 'model') {
|
|
166
|
+
const hasText = lastHist.parts?.some((p) => p.text && p.text.trim() === clean);
|
|
167
|
+
if (!hasText) {
|
|
168
|
+
lastHist.parts = lastHist.parts || [];
|
|
169
|
+
lastHist.parts.push({ text: clean });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
this.history.push({
|
|
174
|
+
role: 'model',
|
|
175
|
+
parts: [{ text: clean }],
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
this.history.push({
|
|
181
|
+
role: 'model',
|
|
182
|
+
parts: [{ text: clean }],
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
120
186
|
/**
|
|
121
187
|
* Retrieves the most recent conversation history as a formatted string.
|
|
122
188
|
* Useful for passing conversation context to stateless background agents.
|
|
@@ -164,6 +230,7 @@ export class ProxyChatSession {
|
|
|
164
230
|
* conversational context while preventing OOM crashes.
|
|
165
231
|
*/
|
|
166
232
|
async pruneHistory() {
|
|
233
|
+
this.pruneToolOutputHistory();
|
|
167
234
|
if (this.history.length > MAX_HISTORY_ENTRIES) {
|
|
168
235
|
const excess = this.history.length - MAX_HISTORY_ENTRIES;
|
|
169
236
|
const trimCount = excess % 2 === 0 ? excess : excess + 1;
|
|
@@ -181,6 +248,46 @@ export class ProxyChatSession {
|
|
|
181
248
|
}
|
|
182
249
|
}
|
|
183
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Collapses oversized functionResponse outputs in historical turns (>1 turn old)
|
|
253
|
+
* while strictly maintaining Gemini functionCall and functionResponse pairing.
|
|
254
|
+
*
|
|
255
|
+
* Gemini requires every functionCall in a model turn to have a matching functionResponse
|
|
256
|
+
* in the immediately following user turn. Deleting parts or entries breaks this invariant and
|
|
257
|
+
* causes 400 Bad Request errors. This method mutates oversized response outputs in-place
|
|
258
|
+
* on older turns to drastically conserve context window tokens.
|
|
259
|
+
*
|
|
260
|
+
* @param threshold Maximum characters allowed for a historical tool response before collapsing (default: 1500).
|
|
261
|
+
* @param turnsToKeep Number of recent turns to preserve unpruned (default: 1).
|
|
262
|
+
*/
|
|
263
|
+
pruneToolOutputHistory(threshold = HISTORICAL_TOOL_OUTPUT_THRESHOLD, turnsToKeep = 1) {
|
|
264
|
+
// 1 turn = 1 user Content + 1 model Content pair (2 entries)
|
|
265
|
+
const cutoffIndex = Math.max(0, this.history.length - turnsToKeep * 2);
|
|
266
|
+
if (cutoffIndex <= 0)
|
|
267
|
+
return;
|
|
268
|
+
for (let i = 0; i < cutoffIndex; i++) {
|
|
269
|
+
const entry = this.history[i];
|
|
270
|
+
if (entry && entry.role === 'user' && Array.isArray(entry.parts)) {
|
|
271
|
+
for (const part of entry.parts) {
|
|
272
|
+
if (part && typeof part === 'object' && 'functionResponse' in part && part.functionResponse) {
|
|
273
|
+
const funcResp = part.functionResponse;
|
|
274
|
+
if (funcResp.response && typeof funcResp.response === 'object') {
|
|
275
|
+
const respObj = funcResp.response;
|
|
276
|
+
if (typeof respObj.output === 'string') {
|
|
277
|
+
respObj.output = collapseHistoricalOutput(respObj.output, threshold);
|
|
278
|
+
}
|
|
279
|
+
if (typeof respObj.error === 'string') {
|
|
280
|
+
respObj.error = collapseHistoricalOutput(respObj.error, threshold);
|
|
281
|
+
}
|
|
282
|
+
if (typeof respObj.result === 'string') {
|
|
283
|
+
respObj.result = collapseHistoricalOutput(respObj.result, threshold);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
184
291
|
async sendMessage(message, additionalText, abortSignal, onChunk) {
|
|
185
292
|
const idToken = await getAuthorizedIdToken();
|
|
186
293
|
if (!idToken) {
|
|
@@ -756,6 +863,150 @@ export async function summarizeChatHistory(history, abortSignal) {
|
|
|
756
863
|
return '';
|
|
757
864
|
}
|
|
758
865
|
}
|
|
866
|
+
// ─── User Profile Extractor Service ────────────────────────────────────
|
|
867
|
+
export function createUserProfileExtractorSession() {
|
|
868
|
+
// Configured exclusively with FLASH_LITE for lightweight zero-latency profiling
|
|
869
|
+
const model = GEMINI_MODELS.FLASH_LITE;
|
|
870
|
+
return new ProxyChatSession(model, USER_PROFILE_EXTRACTOR_SYSTEM_INSTRUCTION, [], // no tools
|
|
871
|
+
{
|
|
872
|
+
temperature: 0,
|
|
873
|
+
responseMimeType: 'application/json',
|
|
874
|
+
responseSchema: {
|
|
875
|
+
type: SchemaType.OBJECT,
|
|
876
|
+
properties: {
|
|
877
|
+
tonePreference: {
|
|
878
|
+
type: SchemaType.STRING,
|
|
879
|
+
description: 'Observed tone and demeanor traits of the user',
|
|
880
|
+
},
|
|
881
|
+
verbosity: {
|
|
882
|
+
type: SchemaType.STRING,
|
|
883
|
+
description: 'Observed verbosity and output formatting preference',
|
|
884
|
+
},
|
|
885
|
+
formulationStyle: {
|
|
886
|
+
type: SchemaType.STRING,
|
|
887
|
+
description: 'Observed thought formulation and prompting style',
|
|
888
|
+
},
|
|
889
|
+
cognitiveTraits: {
|
|
890
|
+
type: SchemaType.OBJECT,
|
|
891
|
+
properties: {
|
|
892
|
+
architecturalStyle: {
|
|
893
|
+
type: SchemaType.STRING,
|
|
894
|
+
description: 'e.g., "top-down-design", "bottom-up-code", "balanced"',
|
|
895
|
+
},
|
|
896
|
+
decisionPreference: {
|
|
897
|
+
type: SchemaType.STRING,
|
|
898
|
+
description: 'e.g., "direct-recommendation", "present-options"',
|
|
899
|
+
},
|
|
900
|
+
riskTolerance: {
|
|
901
|
+
type: SchemaType.STRING,
|
|
902
|
+
description: 'e.g., "defensive-rigor", "pragmatic-speed"',
|
|
903
|
+
},
|
|
904
|
+
delegationDepth: {
|
|
905
|
+
type: SchemaType.STRING,
|
|
906
|
+
description: 'e.g., "autonomous-delegation", "hands-on-stepwise"',
|
|
907
|
+
},
|
|
908
|
+
debuggingStyle: {
|
|
909
|
+
type: SchemaType.STRING,
|
|
910
|
+
description: 'e.g., "minimal-diff-fix", "root-cause-deep-dive"',
|
|
911
|
+
},
|
|
912
|
+
explanationFormat: {
|
|
913
|
+
type: SchemaType.STRING,
|
|
914
|
+
description: 'e.g., "code-first", "bullet-summaries", "conceptual-analogies"',
|
|
915
|
+
},
|
|
916
|
+
},
|
|
917
|
+
description: 'Observed cognitive decision-making, collaboration, and problem-solving traits',
|
|
918
|
+
},
|
|
919
|
+
strengths: {
|
|
920
|
+
type: SchemaType.ARRAY,
|
|
921
|
+
items: {
|
|
922
|
+
type: SchemaType.STRING,
|
|
923
|
+
},
|
|
924
|
+
description: 'Observed technical strengths or technologies the user is actively familiar with',
|
|
925
|
+
},
|
|
926
|
+
removeStrengths: {
|
|
927
|
+
type: SchemaType.ARRAY,
|
|
928
|
+
items: {
|
|
929
|
+
type: SchemaType.STRING,
|
|
930
|
+
},
|
|
931
|
+
description: 'Outdated or abandoned technologies to remove when a pivot/contradiction occurs',
|
|
932
|
+
},
|
|
933
|
+
conventions: {
|
|
934
|
+
type: SchemaType.ARRAY,
|
|
935
|
+
items: {
|
|
936
|
+
type: SchemaType.STRING,
|
|
937
|
+
},
|
|
938
|
+
description: 'Observed architectural habits or coding conventions',
|
|
939
|
+
},
|
|
940
|
+
removeConventions: {
|
|
941
|
+
type: SchemaType.ARRAY,
|
|
942
|
+
items: {
|
|
943
|
+
type: SchemaType.STRING,
|
|
944
|
+
},
|
|
945
|
+
description: 'Outdated coding conventions to remove when a pivot/contradiction occurs',
|
|
946
|
+
},
|
|
947
|
+
addNotes: {
|
|
948
|
+
type: SchemaType.ARRAY,
|
|
949
|
+
items: {
|
|
950
|
+
type: SchemaType.STRING,
|
|
951
|
+
},
|
|
952
|
+
description: 'New non-conflicting side-notes to add (empty if none)',
|
|
953
|
+
},
|
|
954
|
+
updateNotes: {
|
|
955
|
+
type: SchemaType.ARRAY,
|
|
956
|
+
items: {
|
|
957
|
+
type: SchemaType.OBJECT,
|
|
958
|
+
properties: {
|
|
959
|
+
index: {
|
|
960
|
+
type: SchemaType.INTEGER,
|
|
961
|
+
description: '0-based index of the existing note to update',
|
|
962
|
+
},
|
|
963
|
+
updatedText: {
|
|
964
|
+
type: SchemaType.STRING,
|
|
965
|
+
description: 'Revised note text that supersedes the outdated observation',
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
required: ['index', 'updatedText'],
|
|
969
|
+
},
|
|
970
|
+
description: 'Targeted updates to existing notes that evolved',
|
|
971
|
+
},
|
|
972
|
+
deleteNoteIndices: {
|
|
973
|
+
type: SchemaType.ARRAY,
|
|
974
|
+
items: {
|
|
975
|
+
type: SchemaType.INTEGER,
|
|
976
|
+
},
|
|
977
|
+
description: '0-based indices of contradicted or obsolete notes to delete',
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
required: ['addNotes', 'updateNotes', 'deleteNoteIndices'],
|
|
981
|
+
},
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Creates a Flash-Lite session with structured output to classify whether a user message
|
|
986
|
+
* is trivial/low-signal (greetings, confirmations, terminal commands) or contains substantive signal.
|
|
987
|
+
*/
|
|
988
|
+
export function createTrivialMessageClassifierSession() {
|
|
989
|
+
const model = GEMINI_MODELS.FLASH_LITE;
|
|
990
|
+
return new ProxyChatSession(model, TRIVIAL_MESSAGE_CLASSIFIER_SYSTEM_INSTRUCTION, [], // no tools
|
|
991
|
+
{
|
|
992
|
+
temperature: 0,
|
|
993
|
+
responseMimeType: 'application/json',
|
|
994
|
+
responseSchema: {
|
|
995
|
+
type: SchemaType.OBJECT,
|
|
996
|
+
properties: {
|
|
997
|
+
isTrivial: {
|
|
998
|
+
type: SchemaType.BOOLEAN,
|
|
999
|
+
description: 'True if the message is a low-signal greeting, confirmation, affirmation, or simple control command with zero technical/personal traits',
|
|
1000
|
+
},
|
|
1001
|
+
reason: {
|
|
1002
|
+
type: SchemaType.STRING,
|
|
1003
|
+
description: 'Brief reason for the classification decision',
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
required: ['isTrivial'],
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
759
1010
|
/**
|
|
760
1011
|
* Generates a concise title for a chat session based on the user's first message.
|
|
761
1012
|
*/
|
|
@@ -773,7 +1024,7 @@ export async function generateChatTitle(firstMessage, abortSignal) {
|
|
|
773
1024
|
const contents = [{ role: 'user', parts: [{ text: firstMessage.substring(0, 500) }] }];
|
|
774
1025
|
let model = getGlobalActiveModel();
|
|
775
1026
|
if (model === 'auto' || model.includes('claude'))
|
|
776
|
-
model = GEMINI_MODELS.
|
|
1027
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
777
1028
|
const byokEnabled = await isByokEnabled();
|
|
778
1029
|
let result;
|
|
779
1030
|
if (byokEnabled) {
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import type { Content } from '@google/generative-ai';
|
|
2
|
+
/**
|
|
3
|
+
* Options for configuring dynamic history pruning.
|
|
4
|
+
*/
|
|
5
|
+
export interface HistoryPruneOptions {
|
|
6
|
+
/** Maximum number of history entries (user + model contents) to keep in active memory. Defaults to 50. */
|
|
7
|
+
maxEntries?: number;
|
|
8
|
+
/** Maximum estimated tokens allowed for the active conversation history. Defaults to 64,000. */
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
/** Minimum number of recent entries to retain regardless of token limits. Defaults to 10 (5 full turns). */
|
|
11
|
+
minRecentEntries?: number;
|
|
12
|
+
/** Whether to automatically archive pruned entries to disk. Defaults to true. */
|
|
13
|
+
archivePruned?: boolean;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Represents the structure of a saved chat session.
|
|
4
17
|
* This data is persisted in the local workspace cache to allow users to resume
|
|
@@ -41,7 +54,16 @@ export interface ChatSessionData {
|
|
|
41
54
|
modelUsageCounts?: Record<string, number>;
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
44
|
-
*
|
|
57
|
+
* Estimates the token count for a Gemini Content object.
|
|
58
|
+
*
|
|
59
|
+
* @param content The Content object containing parts and role.
|
|
60
|
+
* @returns Estimated number of tokens.
|
|
61
|
+
*/
|
|
62
|
+
export declare function estimateContentTokens(content: Content): number;
|
|
63
|
+
/**
|
|
64
|
+
* Service responsible for managing the persistence, retrieval, history pruning,
|
|
65
|
+
* and deletion of chat session histories.
|
|
66
|
+
*
|
|
45
67
|
* It stores session metadata and message history in a local JSON cache (`chat_sessions.json`)
|
|
46
68
|
* within the project's storage directory and enforces a maximum limit of 250 sessions to prevent
|
|
47
69
|
* unbounded storage growth.
|
|
@@ -51,6 +73,10 @@ declare class ChatHistoryService {
|
|
|
51
73
|
private workspaceRoot;
|
|
52
74
|
/** The maximum number of chat sessions allowed in the cache. Oldest sessions are discarded when this limit is exceeded. */
|
|
53
75
|
private readonly MAX_SESSIONS;
|
|
76
|
+
/** Default maximum entries allowed in active history before automatic archiving. */
|
|
77
|
+
private readonly DEFAULT_MAX_HISTORY_ENTRIES;
|
|
78
|
+
/** Default maximum estimated tokens before pruning. */
|
|
79
|
+
private readonly DEFAULT_MAX_TOKENS;
|
|
54
80
|
/**
|
|
55
81
|
* Initializes the chat history service with the workspace root path.
|
|
56
82
|
* This must be called before attempting to read, save, or delete sessions.
|
|
@@ -65,15 +91,82 @@ declare class ChatHistoryService {
|
|
|
65
91
|
* @returns An array of saved `ChatSessionData` objects, ordered as stored in the cache.
|
|
66
92
|
*/
|
|
67
93
|
getSessions(): ChatSessionData[];
|
|
94
|
+
/**
|
|
95
|
+
* Retrieves a single chat session by ID.
|
|
96
|
+
*
|
|
97
|
+
* @param id - The unique session identifier.
|
|
98
|
+
* @returns The session data, or null if not found.
|
|
99
|
+
*/
|
|
100
|
+
getSession(id: string): ChatSessionData | null;
|
|
101
|
+
/**
|
|
102
|
+
* Estimates the total token count of all messages within a given chat session.
|
|
103
|
+
*
|
|
104
|
+
* @param session The chat session to evaluate.
|
|
105
|
+
* @returns Total estimated tokens.
|
|
106
|
+
*/
|
|
107
|
+
estimateSessionTokens(session: ChatSessionData): number;
|
|
108
|
+
/**
|
|
109
|
+
* Validates and cleans conversation turns by removing empty parts or invalid structures.
|
|
110
|
+
*
|
|
111
|
+
* @param history Array of Content objects.
|
|
112
|
+
* @returns Sanitized array of Content objects.
|
|
113
|
+
*/
|
|
114
|
+
sanitizeSessionHistory(history: Content[]): Content[];
|
|
115
|
+
/**
|
|
116
|
+
* Appends pruned history items to the session's archive JSON file on disk.
|
|
117
|
+
*
|
|
118
|
+
* @param id - The session identifier.
|
|
119
|
+
* @param prunedEntries - Array of pruned Content objects.
|
|
120
|
+
*/
|
|
121
|
+
archiveSessionHistory(id: string, prunedEntries: Content[]): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Retrieves previously archived history entries for a given session.
|
|
124
|
+
*
|
|
125
|
+
* @param id - The session identifier.
|
|
126
|
+
* @returns Array of archived Content objects.
|
|
127
|
+
*/
|
|
128
|
+
getArchivedSessionHistory(id: string): Promise<Content[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Prunes a session's history to satisfy entry and token bounds while preserving
|
|
131
|
+
* recent conversational context and alternating turn integrity.
|
|
132
|
+
*
|
|
133
|
+
* @param session The session to prune.
|
|
134
|
+
* @param options History pruning configuration options.
|
|
135
|
+
* @returns An object containing the pruned session and count of archived entries.
|
|
136
|
+
*/
|
|
137
|
+
pruneSessionHistory(session: ChatSessionData, options?: HistoryPruneOptions): Promise<{
|
|
138
|
+
prunedSession: ChatSessionData;
|
|
139
|
+
archivedCount: number;
|
|
140
|
+
}>;
|
|
68
141
|
/**
|
|
69
142
|
* Saves or updates a chat session in the local workspace cache.
|
|
70
143
|
* If a session with the same ID already exists, it is updated in place. Otherwise, it is appended.
|
|
144
|
+
* Automatically executes history pruning and archiving if the session exceeds token/turn thresholds.
|
|
71
145
|
* Enforces the maximum session limit of 250 by removing the oldest session if the limit is exceeded.
|
|
72
146
|
*
|
|
73
147
|
* @param session - The chat session data to be saved.
|
|
148
|
+
* @param pruneOptions - Optional pruning configuration overrides.
|
|
74
149
|
* @returns A promise that resolves when the session has been successfully written to the cache.
|
|
75
150
|
*/
|
|
76
|
-
saveSession(session: ChatSessionData): Promise<void>;
|
|
151
|
+
saveSession(session: ChatSessionData, pruneOptions?: HistoryPruneOptions): Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* Retrieves a chat session with its history constrained to a specific token budget.
|
|
154
|
+
*
|
|
155
|
+
* @param id - The unique session identifier.
|
|
156
|
+
* @param maxTokens - Maximum token budget for the returned history.
|
|
157
|
+
* @returns The session with budget-constrained history, or null if session not found.
|
|
158
|
+
*/
|
|
159
|
+
getSessionWithTokenBudget(id: string, maxTokens: number): Promise<ChatSessionData | null>;
|
|
160
|
+
/**
|
|
161
|
+
* Prunes stale sessions based on age (in days) or total count limit.
|
|
162
|
+
*
|
|
163
|
+
* @param options Pruning options specifying maxAgeDays and maxTotalSessions.
|
|
164
|
+
* @returns Number of sessions pruned.
|
|
165
|
+
*/
|
|
166
|
+
pruneOldSessions(options?: {
|
|
167
|
+
maxAgeDays?: number;
|
|
168
|
+
maxTotalSessions?: number;
|
|
169
|
+
}): Promise<number>;
|
|
77
170
|
/**
|
|
78
171
|
* Updates the user-friendly title of an existing chat session in the local workspace cache.
|
|
79
172
|
* If the session with the matching ID exists, its title is updated and persisted to `chat_sessions.json`.
|