erosolar-cli 1.5.12 → 1.5.14
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/dist/core/contextManager.d.ts +98 -0
- package/dist/core/contextManager.d.ts.map +1 -1
- package/dist/core/contextManager.js +470 -0
- package/dist/core/contextManager.js.map +1 -1
- package/dist/shell/interactiveShell.d.ts +1 -0
- package/dist/shell/interactiveShell.d.ts.map +1 -1
- package/dist/shell/interactiveShell.js +34 -1
- package/dist/shell/interactiveShell.js.map +1 -1
- package/dist/ui/persistentPrompt.d.ts +172 -0
- package/dist/ui/persistentPrompt.d.ts.map +1 -1
- package/dist/ui/persistentPrompt.js +397 -0
- package/dist/ui/persistentPrompt.js.map +1 -1
- package/package.json +1 -1
|
@@ -14,6 +14,25 @@ import type { ConversationMessage } from './types.js';
|
|
|
14
14
|
* Takes messages to summarize and returns a concise summary string
|
|
15
15
|
*/
|
|
16
16
|
export type SummarizationCallback = (messages: ConversationMessage[]) => Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* Signals that indicate a good compaction point in the conversation
|
|
19
|
+
*/
|
|
20
|
+
export interface CompactionSignal {
|
|
21
|
+
type: 'task_boundary' | 'topic_shift' | 'milestone' | 'context_saturation' | 'user_pivot';
|
|
22
|
+
confidence: number;
|
|
23
|
+
messageIndex: number;
|
|
24
|
+
reason: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Result of intelligent compaction analysis
|
|
28
|
+
*/
|
|
29
|
+
export interface CompactionAnalysis {
|
|
30
|
+
shouldCompact: boolean;
|
|
31
|
+
signals: CompactionSignal[];
|
|
32
|
+
recommendedCompactionPoint: number | null;
|
|
33
|
+
urgency: 'none' | 'low' | 'medium' | 'high' | 'critical';
|
|
34
|
+
preserveFromIndex: number;
|
|
35
|
+
}
|
|
17
36
|
/**
|
|
18
37
|
* Summarization prompt template
|
|
19
38
|
*/
|
|
@@ -29,6 +48,11 @@ export interface ContextManagerConfig {
|
|
|
29
48
|
useLLMSummarization?: boolean;
|
|
30
49
|
summarizationCallback?: SummarizationCallback;
|
|
31
50
|
model?: string;
|
|
51
|
+
enableIntelligentCompaction?: boolean;
|
|
52
|
+
compactionThreshold?: number;
|
|
53
|
+
minSignalConfidence?: number;
|
|
54
|
+
taskBoundaryPatterns?: string[];
|
|
55
|
+
topicShiftSensitivity?: number;
|
|
32
56
|
}
|
|
33
57
|
export interface TruncationResult {
|
|
34
58
|
content: string;
|
|
@@ -106,6 +130,80 @@ export declare class ContextManager {
|
|
|
106
130
|
* Update configuration
|
|
107
131
|
*/
|
|
108
132
|
updateConfig(config: Partial<ContextManagerConfig>): void;
|
|
133
|
+
/**
|
|
134
|
+
* Default patterns that indicate task boundaries
|
|
135
|
+
*/
|
|
136
|
+
private static readonly DEFAULT_TASK_BOUNDARY_PATTERNS;
|
|
137
|
+
/**
|
|
138
|
+
* Patterns indicating topic/task shifts
|
|
139
|
+
*/
|
|
140
|
+
private static readonly TOPIC_SHIFT_PATTERNS;
|
|
141
|
+
/**
|
|
142
|
+
* Patterns indicating user pivots (abandoning current direction)
|
|
143
|
+
*/
|
|
144
|
+
private static readonly USER_PIVOT_PATTERNS;
|
|
145
|
+
/**
|
|
146
|
+
* Analyze the conversation to detect intelligent compaction points
|
|
147
|
+
*/
|
|
148
|
+
analyzeCompactionPoints(messages: ConversationMessage[]): CompactionAnalysis;
|
|
149
|
+
/**
|
|
150
|
+
* Detect task boundary signals
|
|
151
|
+
*/
|
|
152
|
+
private detectTaskBoundary;
|
|
153
|
+
/**
|
|
154
|
+
* Detect topic shift signals
|
|
155
|
+
*/
|
|
156
|
+
private detectTopicShift;
|
|
157
|
+
/**
|
|
158
|
+
* Detect user pivot signals (abandoning current direction)
|
|
159
|
+
*/
|
|
160
|
+
private detectUserPivot;
|
|
161
|
+
/**
|
|
162
|
+
* Detect context saturation (heavy tool output regions)
|
|
163
|
+
*/
|
|
164
|
+
private detectContextSaturation;
|
|
165
|
+
/**
|
|
166
|
+
* Detect milestone signals (significant accomplishments)
|
|
167
|
+
*/
|
|
168
|
+
private detectMilestone;
|
|
169
|
+
/**
|
|
170
|
+
* Check if content looks like a task conclusion
|
|
171
|
+
*/
|
|
172
|
+
private looksLikeConclusion;
|
|
173
|
+
/**
|
|
174
|
+
* Check if two contents represent different topics (simple heuristic)
|
|
175
|
+
*/
|
|
176
|
+
private isNewTopic;
|
|
177
|
+
/**
|
|
178
|
+
* Calculate urgency level based on token percentage
|
|
179
|
+
*/
|
|
180
|
+
private calculateUrgency;
|
|
181
|
+
/**
|
|
182
|
+
* Find the best compaction point from signals
|
|
183
|
+
*/
|
|
184
|
+
private findBestCompactionPoint;
|
|
185
|
+
/**
|
|
186
|
+
* Find a safe preservation point that doesn't break tool call chains
|
|
187
|
+
*/
|
|
188
|
+
private findSafePreservePoint;
|
|
189
|
+
/**
|
|
190
|
+
* Perform intelligent compaction based on analysis
|
|
191
|
+
* This method analyzes the conversation and compacts at the optimal point
|
|
192
|
+
*/
|
|
193
|
+
intelligentCompact(messages: ConversationMessage[]): Promise<{
|
|
194
|
+
compacted: ConversationMessage[];
|
|
195
|
+
analysis: CompactionAnalysis;
|
|
196
|
+
summarized: boolean;
|
|
197
|
+
}>;
|
|
198
|
+
/**
|
|
199
|
+
* Check if intelligent compaction should be triggered
|
|
200
|
+
* Call this before generation to proactively manage context
|
|
201
|
+
*/
|
|
202
|
+
shouldTriggerCompaction(messages: ConversationMessage[]): {
|
|
203
|
+
shouldCompact: boolean;
|
|
204
|
+
urgency: CompactionAnalysis['urgency'];
|
|
205
|
+
reason: string | null;
|
|
206
|
+
};
|
|
109
207
|
}
|
|
110
208
|
/**
|
|
111
209
|
* Create a default context manager instance with model-aware limits
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contextManager.d.ts","sourceRoot":"","sources":["../../src/core/contextManager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAGtD;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,QAAQ,EAAE,mBAAmB,EAAE,KAC5B,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB;;GAEG;AACH,eAAO,MAAM,oBAAoB,qhCA6BlB,CAAC;AAEhB,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"contextManager.d.ts","sourceRoot":"","sources":["../../src/core/contextManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -468,6 +468,471 @@ export class ContextManager {
|
|
|
468
468
|
updateConfig(config) {
|
|
469
469
|
this.config = { ...this.config, ...config };
|
|
470
470
|
}
|
|
471
|
+
// ============================================================================
|
|
472
|
+
// INTELLIGENT COMPACTION SYSTEM
|
|
473
|
+
// Automatically detects optimal points for conversation compaction
|
|
474
|
+
// ============================================================================
|
|
475
|
+
/**
|
|
476
|
+
* Default patterns that indicate task boundaries
|
|
477
|
+
*/
|
|
478
|
+
static DEFAULT_TASK_BOUNDARY_PATTERNS = [
|
|
479
|
+
// Completion indicators
|
|
480
|
+
/\b(done|completed|finished|fixed|resolved|implemented|added|created|updated)\b/i,
|
|
481
|
+
/\b(all\s+(?:tests?\s+)?pass(?:ing|ed)?)\b/i,
|
|
482
|
+
/\b(successfully|works?\s+(?:now|correctly))\b/i,
|
|
483
|
+
// Transition indicators
|
|
484
|
+
/\b(next|now\s+(?:let's|we\s+can)|moving\s+on)\b/i,
|
|
485
|
+
/\b(that's\s+(?:it|all|done))\b/i,
|
|
486
|
+
// Acknowledgment patterns
|
|
487
|
+
/^(?:great|perfect|thanks|thank\s+you|got\s+it|understood)\b/i,
|
|
488
|
+
];
|
|
489
|
+
/**
|
|
490
|
+
* Patterns indicating topic/task shifts
|
|
491
|
+
*/
|
|
492
|
+
static TOPIC_SHIFT_PATTERNS = [
|
|
493
|
+
/\b(different|another|new|separate|unrelated)\s+(?:task|thing|topic|issue|question)\b/i,
|
|
494
|
+
/\b(can\s+you|could\s+you|please|now|let's)\s+(?:also|help|do|make|create|fix|add)\b/i,
|
|
495
|
+
/\b(switching|changing|moving)\s+to\b/i,
|
|
496
|
+
/\b(forget|ignore|never\s*mind)\s+(?:that|the|about)\b/i,
|
|
497
|
+
/^(?:ok|okay|alright|anyway|so)\s*[,.]?\s*(?:can|could|now|let|please)/i,
|
|
498
|
+
];
|
|
499
|
+
/**
|
|
500
|
+
* Patterns indicating user pivots (abandoning current direction)
|
|
501
|
+
*/
|
|
502
|
+
static USER_PIVOT_PATTERNS = [
|
|
503
|
+
/\b(actually|wait|hold\s+on|stop|cancel|scratch\s+that)\b/i,
|
|
504
|
+
/\b(let's\s+(?:try|do)\s+(?:something|it)\s+(?:else|differently))\b/i,
|
|
505
|
+
/\b(go\s+back|revert|undo|start\s+over)\b/i,
|
|
506
|
+
/\b(wrong|not\s+(?:what|right)|that's\s+not)\b/i,
|
|
507
|
+
];
|
|
508
|
+
/**
|
|
509
|
+
* Analyze the conversation to detect intelligent compaction points
|
|
510
|
+
*/
|
|
511
|
+
analyzeCompactionPoints(messages) {
|
|
512
|
+
const signals = [];
|
|
513
|
+
const totalTokens = this.estimateTotalTokens(messages);
|
|
514
|
+
const tokenPercentage = totalTokens / this.config.maxTokens;
|
|
515
|
+
const compactionThreshold = this.config.compactionThreshold ?? 0.5;
|
|
516
|
+
const minConfidence = this.config.minSignalConfidence ?? 0.6;
|
|
517
|
+
// Don't analyze if below threshold
|
|
518
|
+
if (tokenPercentage < compactionThreshold) {
|
|
519
|
+
return {
|
|
520
|
+
shouldCompact: false,
|
|
521
|
+
signals: [],
|
|
522
|
+
recommendedCompactionPoint: null,
|
|
523
|
+
urgency: 'none',
|
|
524
|
+
preserveFromIndex: 0,
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
// Analyze each message for compaction signals
|
|
528
|
+
for (let i = 0; i < messages.length; i++) {
|
|
529
|
+
const msg = messages[i];
|
|
530
|
+
if (!msg)
|
|
531
|
+
continue;
|
|
532
|
+
// Detect task boundaries
|
|
533
|
+
const taskBoundary = this.detectTaskBoundary(msg, i, messages);
|
|
534
|
+
if (taskBoundary && taskBoundary.confidence >= minConfidence) {
|
|
535
|
+
signals.push(taskBoundary);
|
|
536
|
+
}
|
|
537
|
+
// Detect topic shifts
|
|
538
|
+
const topicShift = this.detectTopicShift(msg, i, messages);
|
|
539
|
+
if (topicShift && topicShift.confidence >= minConfidence) {
|
|
540
|
+
signals.push(topicShift);
|
|
541
|
+
}
|
|
542
|
+
// Detect user pivots
|
|
543
|
+
const userPivot = this.detectUserPivot(msg, i);
|
|
544
|
+
if (userPivot && userPivot.confidence >= minConfidence) {
|
|
545
|
+
signals.push(userPivot);
|
|
546
|
+
}
|
|
547
|
+
// Detect context saturation (tool output heavy regions)
|
|
548
|
+
const saturation = this.detectContextSaturation(msg, i, messages);
|
|
549
|
+
if (saturation && saturation.confidence >= minConfidence) {
|
|
550
|
+
signals.push(saturation);
|
|
551
|
+
}
|
|
552
|
+
// Detect milestones
|
|
553
|
+
const milestone = this.detectMilestone(msg, i, messages);
|
|
554
|
+
if (milestone && milestone.confidence >= minConfidence) {
|
|
555
|
+
signals.push(milestone);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Determine urgency based on token percentage
|
|
559
|
+
const urgency = this.calculateUrgency(tokenPercentage);
|
|
560
|
+
// Find the best compaction point
|
|
561
|
+
const recommendedPoint = this.findBestCompactionPoint(signals, messages, urgency);
|
|
562
|
+
// Calculate preserve index (everything after this should be kept)
|
|
563
|
+
const preserveFromIndex = recommendedPoint !== null
|
|
564
|
+
? this.findSafePreservePoint(recommendedPoint, messages)
|
|
565
|
+
: messages.length;
|
|
566
|
+
return {
|
|
567
|
+
shouldCompact: signals.length > 0 && urgency !== 'none',
|
|
568
|
+
signals,
|
|
569
|
+
recommendedCompactionPoint: recommendedPoint,
|
|
570
|
+
urgency,
|
|
571
|
+
preserveFromIndex,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Detect task boundary signals
|
|
576
|
+
*/
|
|
577
|
+
detectTaskBoundary(msg, index, messages) {
|
|
578
|
+
if (msg.role !== 'user' && msg.role !== 'assistant')
|
|
579
|
+
return null;
|
|
580
|
+
const content = msg.content || '';
|
|
581
|
+
const patterns = this.config.taskBoundaryPatterns
|
|
582
|
+
? this.config.taskBoundaryPatterns.map(p => new RegExp(p, 'i'))
|
|
583
|
+
: ContextManager.DEFAULT_TASK_BOUNDARY_PATTERNS;
|
|
584
|
+
let matchCount = 0;
|
|
585
|
+
const reasons = [];
|
|
586
|
+
for (const pattern of patterns) {
|
|
587
|
+
if (pattern.test(content)) {
|
|
588
|
+
matchCount++;
|
|
589
|
+
reasons.push(pattern.source.slice(0, 30));
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
if (matchCount === 0)
|
|
593
|
+
return null;
|
|
594
|
+
// Higher confidence if followed by a new user message with different intent
|
|
595
|
+
let confidence = Math.min(0.4 + matchCount * 0.2, 0.9);
|
|
596
|
+
// Boost confidence if this looks like a conclusion
|
|
597
|
+
if (msg.role === 'assistant' && this.looksLikeConclusion(content)) {
|
|
598
|
+
confidence = Math.min(confidence + 0.2, 0.95);
|
|
599
|
+
}
|
|
600
|
+
// Boost if next user message starts a new topic
|
|
601
|
+
const nextUserMsg = messages.slice(index + 1).find(m => m.role === 'user');
|
|
602
|
+
if (nextUserMsg && this.isNewTopic(content, nextUserMsg.content || '')) {
|
|
603
|
+
confidence = Math.min(confidence + 0.15, 0.95);
|
|
604
|
+
}
|
|
605
|
+
return {
|
|
606
|
+
type: 'task_boundary',
|
|
607
|
+
confidence,
|
|
608
|
+
messageIndex: index,
|
|
609
|
+
reason: `Task completion detected: ${reasons.slice(0, 2).join(', ')}`,
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Detect topic shift signals
|
|
614
|
+
*/
|
|
615
|
+
detectTopicShift(msg, index, messages) {
|
|
616
|
+
if (msg.role !== 'user')
|
|
617
|
+
return null;
|
|
618
|
+
const content = msg.content || '';
|
|
619
|
+
const sensitivity = this.config.topicShiftSensitivity ?? 0.7;
|
|
620
|
+
// Check explicit shift patterns
|
|
621
|
+
for (const pattern of ContextManager.TOPIC_SHIFT_PATTERNS) {
|
|
622
|
+
if (pattern.test(content)) {
|
|
623
|
+
return {
|
|
624
|
+
type: 'topic_shift',
|
|
625
|
+
confidence: 0.7 + sensitivity * 0.2,
|
|
626
|
+
messageIndex: index,
|
|
627
|
+
reason: 'Explicit topic shift language detected',
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Check semantic shift from previous context
|
|
632
|
+
const prevMessages = messages.slice(Math.max(0, index - 5), index);
|
|
633
|
+
const prevContent = prevMessages
|
|
634
|
+
.filter(m => m.role === 'user' || m.role === 'assistant')
|
|
635
|
+
.map(m => m.content || '')
|
|
636
|
+
.join(' ');
|
|
637
|
+
if (prevContent && this.isNewTopic(prevContent, content)) {
|
|
638
|
+
return {
|
|
639
|
+
type: 'topic_shift',
|
|
640
|
+
confidence: 0.6 + sensitivity * 0.2,
|
|
641
|
+
messageIndex: index,
|
|
642
|
+
reason: 'Semantic topic shift detected',
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Detect user pivot signals (abandoning current direction)
|
|
649
|
+
*/
|
|
650
|
+
detectUserPivot(msg, index) {
|
|
651
|
+
if (msg.role !== 'user')
|
|
652
|
+
return null;
|
|
653
|
+
const content = msg.content || '';
|
|
654
|
+
for (const pattern of ContextManager.USER_PIVOT_PATTERNS) {
|
|
655
|
+
if (pattern.test(content)) {
|
|
656
|
+
return {
|
|
657
|
+
type: 'user_pivot',
|
|
658
|
+
confidence: 0.85,
|
|
659
|
+
messageIndex: index,
|
|
660
|
+
reason: 'User pivot/direction change detected',
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* Detect context saturation (heavy tool output regions)
|
|
668
|
+
*/
|
|
669
|
+
detectContextSaturation(msg, index, messages) {
|
|
670
|
+
if (msg.role !== 'tool')
|
|
671
|
+
return null;
|
|
672
|
+
// Look at the surrounding region
|
|
673
|
+
const windowStart = Math.max(0, index - 10);
|
|
674
|
+
const windowEnd = Math.min(messages.length, index + 5);
|
|
675
|
+
const window = messages.slice(windowStart, windowEnd);
|
|
676
|
+
// Count tool messages and their sizes
|
|
677
|
+
let toolCount = 0;
|
|
678
|
+
let totalToolSize = 0;
|
|
679
|
+
for (const m of window) {
|
|
680
|
+
if (m.role === 'tool') {
|
|
681
|
+
toolCount++;
|
|
682
|
+
totalToolSize += (m.content || '').length;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// High saturation if many tool outputs with large content
|
|
686
|
+
if (toolCount >= 5 && totalToolSize > 20000) {
|
|
687
|
+
// Find the last tool message in this cluster as compaction point
|
|
688
|
+
let lastToolIndex = index;
|
|
689
|
+
for (let i = index + 1; i < windowEnd; i++) {
|
|
690
|
+
if (messages[i]?.role === 'tool') {
|
|
691
|
+
lastToolIndex = i;
|
|
692
|
+
}
|
|
693
|
+
else if (messages[i]?.role === 'user') {
|
|
694
|
+
break; // Stop at next user message
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
type: 'context_saturation',
|
|
699
|
+
confidence: Math.min(0.5 + toolCount * 0.05, 0.85),
|
|
700
|
+
messageIndex: lastToolIndex,
|
|
701
|
+
reason: `Heavy tool output region (${toolCount} tools, ${Math.round(totalToolSize / 1000)}k chars)`,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Detect milestone signals (significant accomplishments)
|
|
708
|
+
*/
|
|
709
|
+
detectMilestone(msg, index, _messages) {
|
|
710
|
+
if (msg.role !== 'assistant')
|
|
711
|
+
return null;
|
|
712
|
+
const content = msg.content || '';
|
|
713
|
+
// Look for milestone indicators
|
|
714
|
+
const milestonePatterns = [
|
|
715
|
+
/\b(commit(?:ted)?|pushed|deployed|merged|released)\b/i,
|
|
716
|
+
/\b(all\s+tests?\s+pass(?:ing|ed)?)\b/i,
|
|
717
|
+
/\b(build\s+(?:succeed|success|pass))\b/i,
|
|
718
|
+
/\b(feature\s+(?:complete|done|ready))\b/i,
|
|
719
|
+
/\b(pr\s+(?:created|opened|merged))\b/i,
|
|
720
|
+
];
|
|
721
|
+
for (const pattern of milestonePatterns) {
|
|
722
|
+
if (pattern.test(content)) {
|
|
723
|
+
return {
|
|
724
|
+
type: 'milestone',
|
|
725
|
+
confidence: 0.9,
|
|
726
|
+
messageIndex: index,
|
|
727
|
+
reason: 'Significant milestone achieved',
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Check if content looks like a task conclusion
|
|
735
|
+
*/
|
|
736
|
+
looksLikeConclusion(content) {
|
|
737
|
+
const conclusionPatterns = [
|
|
738
|
+
/\b(let\s+me\s+know|feel\s+free|if\s+you\s+(?:need|have|want))\b/i,
|
|
739
|
+
/\b(anything\s+else|other\s+questions?)\b/i,
|
|
740
|
+
/\b(should\s+be\s+(?:good|working|ready|done))\b/i,
|
|
741
|
+
/\b(that\s+should|this\s+(?:should|will))\s+(?:fix|solve|work)/i,
|
|
742
|
+
];
|
|
743
|
+
return conclusionPatterns.some(p => p.test(content));
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Check if two contents represent different topics (simple heuristic)
|
|
747
|
+
*/
|
|
748
|
+
isNewTopic(prevContent, newContent) {
|
|
749
|
+
// Extract key terms (simple tokenization)
|
|
750
|
+
const extractTerms = (text) => {
|
|
751
|
+
const words = text.toLowerCase()
|
|
752
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
753
|
+
.split(/\s+/)
|
|
754
|
+
.filter(w => w.length > 3);
|
|
755
|
+
return new Set(words);
|
|
756
|
+
};
|
|
757
|
+
const prevTerms = extractTerms(prevContent);
|
|
758
|
+
const newTerms = extractTerms(newContent);
|
|
759
|
+
if (prevTerms.size === 0 || newTerms.size === 0)
|
|
760
|
+
return false;
|
|
761
|
+
// Calculate overlap
|
|
762
|
+
let overlap = 0;
|
|
763
|
+
for (const term of newTerms) {
|
|
764
|
+
if (prevTerms.has(term))
|
|
765
|
+
overlap++;
|
|
766
|
+
}
|
|
767
|
+
const overlapRatio = overlap / Math.min(prevTerms.size, newTerms.size);
|
|
768
|
+
// Low overlap suggests new topic
|
|
769
|
+
return overlapRatio < 0.2;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Calculate urgency level based on token percentage
|
|
773
|
+
*/
|
|
774
|
+
calculateUrgency(tokenPercentage) {
|
|
775
|
+
if (tokenPercentage >= 0.9)
|
|
776
|
+
return 'critical';
|
|
777
|
+
if (tokenPercentage >= 0.75)
|
|
778
|
+
return 'high';
|
|
779
|
+
if (tokenPercentage >= 0.6)
|
|
780
|
+
return 'medium';
|
|
781
|
+
if (tokenPercentage >= 0.5)
|
|
782
|
+
return 'low';
|
|
783
|
+
return 'none';
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Find the best compaction point from signals
|
|
787
|
+
*/
|
|
788
|
+
findBestCompactionPoint(signals, messages, urgency) {
|
|
789
|
+
if (signals.length === 0)
|
|
790
|
+
return null;
|
|
791
|
+
// Score each signal based on type priority and confidence
|
|
792
|
+
const typePriority = {
|
|
793
|
+
milestone: 1.0,
|
|
794
|
+
task_boundary: 0.9,
|
|
795
|
+
user_pivot: 0.85,
|
|
796
|
+
topic_shift: 0.8,
|
|
797
|
+
context_saturation: 0.7,
|
|
798
|
+
};
|
|
799
|
+
// Urgency affects how far back we're willing to compact
|
|
800
|
+
const urgencyDepth = {
|
|
801
|
+
none: 0,
|
|
802
|
+
low: 0.3, // Compact only recent 30%
|
|
803
|
+
medium: 0.5,
|
|
804
|
+
high: 0.7,
|
|
805
|
+
critical: 0.9,
|
|
806
|
+
};
|
|
807
|
+
const maxDepth = urgencyDepth[urgency] ?? 0.5;
|
|
808
|
+
const minIndex = Math.floor(messages.length * (1 - maxDepth));
|
|
809
|
+
// Find highest scoring signal within allowed depth
|
|
810
|
+
let bestSignal = null;
|
|
811
|
+
let bestScore = 0;
|
|
812
|
+
for (const signal of signals) {
|
|
813
|
+
if (signal.messageIndex < minIndex)
|
|
814
|
+
continue;
|
|
815
|
+
const score = signal.confidence * typePriority[signal.type];
|
|
816
|
+
if (score > bestScore) {
|
|
817
|
+
bestScore = score;
|
|
818
|
+
bestSignal = signal;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return bestSignal?.messageIndex ?? null;
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Find a safe preservation point that doesn't break tool call chains
|
|
825
|
+
*/
|
|
826
|
+
findSafePreservePoint(compactionPoint, messages) {
|
|
827
|
+
// Start from compaction point and move forward to find a safe break
|
|
828
|
+
for (let i = compactionPoint + 1; i < messages.length; i++) {
|
|
829
|
+
const msg = messages[i];
|
|
830
|
+
if (!msg)
|
|
831
|
+
continue;
|
|
832
|
+
// Safe if it's a user message
|
|
833
|
+
if (msg.role === 'user') {
|
|
834
|
+
return i;
|
|
835
|
+
}
|
|
836
|
+
// Safe if it's an assistant without pending tool calls
|
|
837
|
+
if (msg.role === 'assistant' && !msg.toolCalls?.length) {
|
|
838
|
+
return i;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// If no safe point found, keep more messages
|
|
842
|
+
return Math.min(compactionPoint + 1, messages.length);
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Perform intelligent compaction based on analysis
|
|
846
|
+
* This method analyzes the conversation and compacts at the optimal point
|
|
847
|
+
*/
|
|
848
|
+
async intelligentCompact(messages) {
|
|
849
|
+
// Analyze for compaction points
|
|
850
|
+
const analysis = this.analyzeCompactionPoints(messages);
|
|
851
|
+
// If no compaction needed or no good point found
|
|
852
|
+
if (!analysis.shouldCompact || analysis.recommendedCompactionPoint === null) {
|
|
853
|
+
return {
|
|
854
|
+
compacted: messages,
|
|
855
|
+
analysis,
|
|
856
|
+
summarized: false,
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
// Separate messages to summarize and preserve
|
|
860
|
+
const firstMessage = messages[0];
|
|
861
|
+
const systemMessage = firstMessage?.role === 'system' ? firstMessage : null;
|
|
862
|
+
const startIndex = systemMessage ? 1 : 0;
|
|
863
|
+
const toSummarize = messages.slice(startIndex, analysis.preserveFromIndex);
|
|
864
|
+
const toPreserve = messages.slice(analysis.preserveFromIndex);
|
|
865
|
+
// If nothing to summarize, return as-is
|
|
866
|
+
if (toSummarize.length === 0) {
|
|
867
|
+
return {
|
|
868
|
+
compacted: messages,
|
|
869
|
+
analysis,
|
|
870
|
+
summarized: false,
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
// Build result
|
|
874
|
+
const compacted = [];
|
|
875
|
+
if (systemMessage) {
|
|
876
|
+
compacted.push(systemMessage);
|
|
877
|
+
}
|
|
878
|
+
// Try LLM summarization if available
|
|
879
|
+
if (this.config.summarizationCallback && this.config.useLLMSummarization !== false) {
|
|
880
|
+
try {
|
|
881
|
+
const summary = await this.config.summarizationCallback(toSummarize);
|
|
882
|
+
compacted.push({
|
|
883
|
+
role: 'system',
|
|
884
|
+
content: [
|
|
885
|
+
'=== Intelligent Context Summary ===',
|
|
886
|
+
`Compaction triggered: ${analysis.signals[0]?.reason || 'Context optimization'}`,
|
|
887
|
+
'',
|
|
888
|
+
summary.trim(),
|
|
889
|
+
'',
|
|
890
|
+
`[Summarized ${toSummarize.length} messages. ${toPreserve.length} recent messages preserved.]`,
|
|
891
|
+
].join('\n'),
|
|
892
|
+
});
|
|
893
|
+
compacted.push(...toPreserve);
|
|
894
|
+
return {
|
|
895
|
+
compacted,
|
|
896
|
+
analysis,
|
|
897
|
+
summarized: true,
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
catch {
|
|
901
|
+
// Fall through to simple compaction
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
// Simple compaction without LLM
|
|
905
|
+
compacted.push({
|
|
906
|
+
role: 'system',
|
|
907
|
+
content: `[Context Manager: Intelligently compacted ${toSummarize.length} messages at "${analysis.signals[0]?.reason || 'optimal point'}". ${toPreserve.length} recent messages preserved.]`,
|
|
908
|
+
});
|
|
909
|
+
compacted.push(...toPreserve);
|
|
910
|
+
return {
|
|
911
|
+
compacted,
|
|
912
|
+
analysis,
|
|
913
|
+
summarized: false,
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Check if intelligent compaction should be triggered
|
|
918
|
+
* Call this before generation to proactively manage context
|
|
919
|
+
*/
|
|
920
|
+
shouldTriggerCompaction(messages) {
|
|
921
|
+
if (this.config.enableIntelligentCompaction === false) {
|
|
922
|
+
return { shouldCompact: false, urgency: 'none', reason: null };
|
|
923
|
+
}
|
|
924
|
+
const analysis = this.analyzeCompactionPoints(messages);
|
|
925
|
+
if (!analysis.shouldCompact) {
|
|
926
|
+
return { shouldCompact: false, urgency: analysis.urgency, reason: null };
|
|
927
|
+
}
|
|
928
|
+
const topSignal = analysis.signals
|
|
929
|
+
.sort((a, b) => b.confidence - a.confidence)[0];
|
|
930
|
+
return {
|
|
931
|
+
shouldCompact: true,
|
|
932
|
+
urgency: analysis.urgency,
|
|
933
|
+
reason: topSignal?.reason || 'Context optimization recommended',
|
|
934
|
+
};
|
|
935
|
+
}
|
|
471
936
|
}
|
|
472
937
|
/**
|
|
473
938
|
* Create a default context manager instance with model-aware limits
|
|
@@ -484,6 +949,11 @@ export function createDefaultContextManager(overrides, model) {
|
|
|
484
949
|
preserveRecentMessages: 5, // Keep last 5 exchanges
|
|
485
950
|
estimatedCharsPerToken: 3.5, // More aggressive estimate (accounts for special tokens, JSON overhead)
|
|
486
951
|
useLLMSummarization: true, // Enable LLM summarization by default
|
|
952
|
+
// Intelligent compaction defaults
|
|
953
|
+
enableIntelligentCompaction: true,
|
|
954
|
+
compactionThreshold: 0.5, // Start analyzing at 50% context usage
|
|
955
|
+
minSignalConfidence: 0.6, // Require 60% confidence for compaction signals
|
|
956
|
+
topicShiftSensitivity: 0.7, // Moderately sensitive to topic changes
|
|
487
957
|
model,
|
|
488
958
|
...overrides,
|
|
489
959
|
});
|