minovative-mind-cli 2.9.1 → 2.11.0
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 +14 -6
- package/dist/services/agent/commandApproval.js +5 -2
- package/dist/services/agent/slashCommands.js +90 -53
- 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 +11 -15
- package/dist/services/ai.d.ts +21 -1
- package/dist/services/ai.js +236 -11
- package/dist/services/chatHistoryService.d.ts +95 -2
- package/dist/services/chatHistoryService.js +236 -9
- package/dist/services/contextAgent.js +196 -81
- package/dist/services/investigationComplexity.d.ts +1 -1
- package/dist/services/investigationComplexity.js +1 -1
- package/dist/services/orchestration/investigationAgent.js +101 -84
- package/dist/services/orchestration/investigationCache.d.ts +80 -5
- package/dist/services/orchestration/investigationCache.js +570 -41
- package/dist/services/orchestration/investigationOrchestrator.js +17 -4
- 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/utils/analysisRunner.d.ts +29 -0
- package/dist/utils/analysisRunner.js +200 -5
- package/dist/utils/contextPrompts.d.ts +20 -4
- package/dist/utils/contextPrompts.js +158 -23
- 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 +5 -4
- package/dist/utils/systemPrompts.js +46 -14
- package/oclif.manifest.json +1 -1
- package/package.json +2 -2
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, 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, } 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) {
|
|
@@ -345,7 +452,7 @@ export function getPlanModeConfig() {
|
|
|
345
452
|
};
|
|
346
453
|
}
|
|
347
454
|
/**
|
|
348
|
-
* Compresses a large string of text using
|
|
455
|
+
* Compresses a large string of text using Gemini Flash.
|
|
349
456
|
* Used for shrinking context payloads to prevent OOM/choking.
|
|
350
457
|
*/
|
|
351
458
|
export async function compressTextUsingFlashLite(text, instruction = "<directives>\nSummarize the following text concisely. Preserve the most critical technical details, function names, and architecture logic. Make sure it's understandable without the fluff.\n</directives>", inlineData, force = false, abortSignal) {
|
|
@@ -357,7 +464,7 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
357
464
|
const idToken = await getAuthorizedIdToken();
|
|
358
465
|
if (!idToken)
|
|
359
466
|
return text;
|
|
360
|
-
let model = GEMINI_MODELS.
|
|
467
|
+
let model = GEMINI_MODELS.FLASH;
|
|
361
468
|
const parts = [{ text }];
|
|
362
469
|
if (inlineData) {
|
|
363
470
|
parts.push({ inlineData });
|
|
@@ -397,7 +504,7 @@ export async function compressTextUsingFlashLite(text, instruction = "<directive
|
|
|
397
504
|
if (abortSignal?.aborted || error?.name === 'AbortError' || error?.message?.includes('abort')) {
|
|
398
505
|
return text;
|
|
399
506
|
}
|
|
400
|
-
debugLog(`Failed to compress text using flash
|
|
507
|
+
debugLog(`Failed to compress text using flash: ${error}`);
|
|
401
508
|
if (error?.status === 401 ||
|
|
402
509
|
error?.status === 403 ||
|
|
403
510
|
error?.message?.includes('API_KEY_INVALID') ||
|
|
@@ -568,25 +675,143 @@ export function createContextAgentSession() {
|
|
|
568
675
|
export function createIntentRouterSession() {
|
|
569
676
|
let model = getGlobalActiveModel();
|
|
570
677
|
if (model === 'auto' || model.includes('claude'))
|
|
571
|
-
model = GEMINI_MODELS.
|
|
678
|
+
model = GEMINI_MODELS.FLASH;
|
|
572
679
|
return new ProxyChatSession(model, INTENT_ROUTER_SYSTEM_INSTRUCTION, [], // no tools
|
|
573
|
-
{
|
|
680
|
+
{
|
|
681
|
+
temperature: 0,
|
|
682
|
+
responseMimeType: 'application/json',
|
|
683
|
+
responseSchema: {
|
|
684
|
+
type: SchemaType.OBJECT,
|
|
685
|
+
properties: {
|
|
686
|
+
context: {
|
|
687
|
+
type: SchemaType.STRING,
|
|
688
|
+
enum: ['SEARCH', 'SKIP'],
|
|
689
|
+
description: 'Whether workspace context search is required',
|
|
690
|
+
},
|
|
691
|
+
agent: {
|
|
692
|
+
type: SchemaType.STRING,
|
|
693
|
+
enum: ['CHAT', 'EXECUTE'],
|
|
694
|
+
description: 'Target agent to route the request to',
|
|
695
|
+
},
|
|
696
|
+
},
|
|
697
|
+
required: ['context', 'agent'],
|
|
698
|
+
},
|
|
699
|
+
});
|
|
574
700
|
}
|
|
575
701
|
// ─── Execution Complexity Router Service ──────────────────────────────
|
|
576
702
|
export function createExecutionComplexitySession() {
|
|
577
703
|
let model = getGlobalActiveModel();
|
|
578
704
|
if (model === 'auto' || model.includes('claude'))
|
|
579
|
-
model = GEMINI_MODELS.
|
|
705
|
+
model = GEMINI_MODELS.FLASH;
|
|
580
706
|
return new ProxyChatSession(model, EXECUTION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
581
|
-
{
|
|
707
|
+
{
|
|
708
|
+
temperature: 0,
|
|
709
|
+
responseMimeType: 'application/json',
|
|
710
|
+
responseSchema: {
|
|
711
|
+
type: SchemaType.OBJECT,
|
|
712
|
+
properties: {
|
|
713
|
+
complexity: {
|
|
714
|
+
type: SchemaType.STRING,
|
|
715
|
+
enum: ['EASY', 'HARD'],
|
|
716
|
+
description: 'Execution task complexity rating',
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
required: ['complexity'],
|
|
720
|
+
},
|
|
721
|
+
});
|
|
582
722
|
}
|
|
583
723
|
// ─── Investigation Complexity Router Service ─────────────────────────
|
|
584
724
|
export function createInvestigationComplexitySession() {
|
|
585
725
|
let model = getGlobalActiveModel();
|
|
586
726
|
if (model === 'auto' || model.includes('claude'))
|
|
587
|
-
model = GEMINI_MODELS.
|
|
727
|
+
model = GEMINI_MODELS.FLASH;
|
|
588
728
|
return new ProxyChatSession(model, INVESTIGATION_COMPLEXITY_SYSTEM_INSTRUCTION, [], // no tools
|
|
589
|
-
{
|
|
729
|
+
{
|
|
730
|
+
temperature: 0,
|
|
731
|
+
responseMimeType: 'application/json',
|
|
732
|
+
responseSchema: {
|
|
733
|
+
type: SchemaType.OBJECT,
|
|
734
|
+
properties: {
|
|
735
|
+
strategy: {
|
|
736
|
+
type: SchemaType.STRING,
|
|
737
|
+
enum: ['SINGLE', 'PARALLEL'],
|
|
738
|
+
description: 'Investigation strategy (SINGLE or PARALLEL)',
|
|
739
|
+
},
|
|
740
|
+
domains: {
|
|
741
|
+
type: SchemaType.ARRAY,
|
|
742
|
+
items: {
|
|
743
|
+
type: SchemaType.STRING,
|
|
744
|
+
},
|
|
745
|
+
description: 'All identified investigation domains',
|
|
746
|
+
},
|
|
747
|
+
agentAssignments: {
|
|
748
|
+
type: SchemaType.ARRAY,
|
|
749
|
+
items: {
|
|
750
|
+
type: SchemaType.OBJECT,
|
|
751
|
+
properties: {
|
|
752
|
+
agentLabel: {
|
|
753
|
+
type: SchemaType.STRING,
|
|
754
|
+
description: 'Label of the sub-agent',
|
|
755
|
+
},
|
|
756
|
+
domains: {
|
|
757
|
+
type: SchemaType.ARRAY,
|
|
758
|
+
items: {
|
|
759
|
+
type: SchemaType.STRING,
|
|
760
|
+
},
|
|
761
|
+
description: 'Domains assigned to this sub-agent',
|
|
762
|
+
},
|
|
763
|
+
},
|
|
764
|
+
required: ['agentLabel', 'domains'],
|
|
765
|
+
},
|
|
766
|
+
description: 'Sub-agent domain group assignments',
|
|
767
|
+
},
|
|
768
|
+
reasoning: {
|
|
769
|
+
type: SchemaType.STRING,
|
|
770
|
+
description: 'Brief justification for the chosen strategy and grouping',
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
required: ['strategy', 'domains', 'agentAssignments', 'reasoning'],
|
|
774
|
+
},
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
// ─── Investigation Semantic Router Service ───────────────────────────
|
|
778
|
+
export function createInvestigationSemanticSession() {
|
|
779
|
+
let model = getGlobalActiveModel();
|
|
780
|
+
if (model === 'auto' || model.includes('claude'))
|
|
781
|
+
model = GEMINI_MODELS.FLASH_LITE;
|
|
782
|
+
return new ProxyChatSession(model, INVESTIGATION_SEMANTIC_SYSTEM_INSTRUCTION, [], // no tools
|
|
783
|
+
{
|
|
784
|
+
temperature: 0,
|
|
785
|
+
responseMimeType: 'application/json',
|
|
786
|
+
responseSchema: {
|
|
787
|
+
type: SchemaType.OBJECT,
|
|
788
|
+
properties: {
|
|
789
|
+
topics: {
|
|
790
|
+
type: SchemaType.ARRAY,
|
|
791
|
+
items: {
|
|
792
|
+
type: SchemaType.STRING,
|
|
793
|
+
},
|
|
794
|
+
description: 'Technical topic or domain identifiers',
|
|
795
|
+
},
|
|
796
|
+
components: {
|
|
797
|
+
type: SchemaType.ARRAY,
|
|
798
|
+
items: {
|
|
799
|
+
type: SchemaType.STRING,
|
|
800
|
+
},
|
|
801
|
+
description: 'Target component, file, function, or endpoint names',
|
|
802
|
+
},
|
|
803
|
+
intent: {
|
|
804
|
+
type: SchemaType.STRING,
|
|
805
|
+
description: 'Primary intent category (e.g. bug_fix, feature_addition, explanation, refactoring)',
|
|
806
|
+
},
|
|
807
|
+
reasoning: {
|
|
808
|
+
type: SchemaType.STRING,
|
|
809
|
+
description: 'Brief justification for the extracted metadata',
|
|
810
|
+
},
|
|
811
|
+
},
|
|
812
|
+
required: ['topics', 'components', 'intent'],
|
|
813
|
+
},
|
|
814
|
+
});
|
|
590
815
|
}
|
|
591
816
|
// ─── Web Search Agent Service ───────────────────────────────────────────
|
|
592
817
|
export function createWebSearchAgentSession() {
|
|
@@ -604,7 +829,7 @@ export function createWebSearchAgentSession() {
|
|
|
604
829
|
export function createHistorySummarizerSession() {
|
|
605
830
|
let model = getGlobalActiveModel();
|
|
606
831
|
if (model === 'auto' || model.includes('claude'))
|
|
607
|
-
model = GEMINI_MODELS.
|
|
832
|
+
model = GEMINI_MODELS.FLASH;
|
|
608
833
|
return new ProxyChatSession(model, HISTORY_SUMMARIZER_SYSTEM_INSTRUCTION, [], {
|
|
609
834
|
temperature: 0.2,
|
|
610
835
|
maxOutputTokens: MAX_OUTPUT_TOKENS,
|
|
@@ -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`.
|