@codebolt/agent 5.0.9 → 6.0.1
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/index.d.ts +2 -0
- package/dist/index.js +41 -0
- package/dist/processor-pieces/messageModifiers/chatHistoryMessageModifier.js +2 -4
- package/dist/processor-pieces/messageModifiers/contextAssemblyModifier.d.ts +81 -0
- package/dist/processor-pieces/messageModifiers/contextAssemblyModifier.js +316 -0
- package/dist/processor-pieces/messageModifiers/coreSystemPromptModifier.js +2 -19
- package/dist/processor-pieces/messageModifiers/directoryContextModifier.js +2 -6
- package/dist/processor-pieces/messageModifiers/environmentContextModifier.js +2 -7
- package/dist/processor-pieces/messageModifiers/ideContextModifier.js +2 -21
- package/dist/processor-pieces/messageModifiers/index.d.ts +3 -0
- package/dist/processor-pieces/messageModifiers/index.js +7 -1
- package/dist/processor-pieces/messageModifiers/toolInjectionModifier.js +2 -1
- package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.d.ts +34 -5
- package/dist/processor-pieces/postToolCallProcessors/conversationCompactorModifier.js +374 -89
- package/dist/processor-pieces/preInferenceProcessors/chatCompressionModifier.d.ts +3 -0
- package/dist/processor-pieces/preInferenceProcessors/chatCompressionModifier.js +50 -27
- package/dist/unified/agent/agent.d.ts +10 -0
- package/dist/unified/agent/agent.js +198 -10
- package/dist/unified/agent/codeboltAgent.d.ts +19 -100
- package/dist/unified/agent/codeboltAgent.js +209 -109
- package/dist/unified/base/agentStep.js +17 -17
- package/dist/unified/base/initialPromptGenerator.js +13 -31
- package/dist/unified/base/promptContext.d.ts +13 -0
- package/dist/unified/base/promptContext.js +213 -0
- package/dist/unified/base/responseExecutor.d.ts +7 -19
- package/dist/unified/base/responseExecutor.js +351 -258
- package/dist/unified/index.d.ts +9 -0
- package/dist/unified/index.js +20 -13
- package/dist/unified/services/CompressionCoordinator.d.ts +67 -0
- package/dist/unified/services/CompressionCoordinator.js +214 -0
- package/dist/unified/services/compaction/autoCompact.d.ts +52 -0
- package/dist/unified/services/compaction/autoCompact.js +294 -0
- package/dist/unified/services/compaction/compactionOrchestrator.d.ts +78 -0
- package/dist/unified/services/compaction/compactionOrchestrator.js +230 -0
- package/dist/unified/services/compaction/contextCollapse.d.ts +63 -0
- package/dist/unified/services/compaction/contextCollapse.js +291 -0
- package/dist/unified/services/compaction/microCompact.d.ts +34 -0
- package/dist/unified/services/compaction/microCompact.js +195 -0
- package/dist/unified/services/compaction/postCompactCleanup.d.ts +15 -0
- package/dist/unified/services/compaction/postCompactCleanup.js +37 -0
- package/dist/unified/services/compaction/reactiveCompact.d.ts +65 -0
- package/dist/unified/services/compaction/reactiveCompact.js +301 -0
- package/dist/unified/services/compaction/snipCompact.d.ts +31 -0
- package/dist/unified/services/compaction/snipCompact.js +124 -0
- package/dist/unified/services/compaction/types.d.ts +66 -0
- package/dist/unified/services/compaction/types.js +39 -0
- package/package.json +22 -30
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer 4: Auto Compact
|
|
4
|
+
*
|
|
5
|
+
* Full conversation summary via LLM when approaching the context window limit.
|
|
6
|
+
* Two sub-paths:
|
|
7
|
+
* 4a. Session memory compaction (using continuously-extracted memory)
|
|
8
|
+
* 4b. Legacy full summary (sends conversation to LLM for detailed summary)
|
|
9
|
+
*
|
|
10
|
+
* Key properties:
|
|
11
|
+
* - Triggers at effectiveContextWindow - bufferTokens (default 13K)
|
|
12
|
+
* - Circuit breaker: stops after N consecutive failures
|
|
13
|
+
* - Produces a CompactBoundaryMessage for boundary tracking
|
|
14
|
+
* - Post-compact cleanup resets all tracking state
|
|
15
|
+
* - When context collapse is active, auto-compact is suppressed
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.AutoCompact = void 0;
|
|
19
|
+
const types_1 = require("./types");
|
|
20
|
+
/** Tokens below context window to trigger proactive auto-compact */
|
|
21
|
+
const DEFAULT_AUTOCOMPACT_BUFFER = 13000;
|
|
22
|
+
/** Max consecutive failures before circuit breaker trips */
|
|
23
|
+
const DEFAULT_MAX_CONSECUTIVE_FAILURES = 3;
|
|
24
|
+
/** Default model token limit */
|
|
25
|
+
const DEFAULT_MODEL_TOKEN_LIMIT = 128000;
|
|
26
|
+
class AutoCompact {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
var _a, _b, _c, _d, _e, _f;
|
|
29
|
+
this.name = 'auto';
|
|
30
|
+
this.consecutiveFailures = 0;
|
|
31
|
+
this.options = {
|
|
32
|
+
bufferTokens: (_a = options === null || options === void 0 ? void 0 : options.bufferTokens) !== null && _a !== void 0 ? _a : DEFAULT_AUTOCOMPACT_BUFFER,
|
|
33
|
+
maxConsecutiveFailures: (_b = options === null || options === void 0 ? void 0 : options.maxConsecutiveFailures) !== null && _b !== void 0 ? _b : DEFAULT_MAX_CONSECUTIVE_FAILURES,
|
|
34
|
+
modelTokenLimit: (_c = options === null || options === void 0 ? void 0 : options.modelTokenLimit) !== null && _c !== void 0 ? _c : DEFAULT_MODEL_TOKEN_LIMIT,
|
|
35
|
+
preserveThreshold: (_d = options === null || options === void 0 ? void 0 : options.preserveThreshold) !== null && _d !== void 0 ? _d : 0.3,
|
|
36
|
+
llmRole: (_e = options === null || options === void 0 ? void 0 : options.llmRole) !== null && _e !== void 0 ? _e : 'summarizer',
|
|
37
|
+
enableLogging: (_f = options === null || options === void 0 ? void 0 : options.enableLogging) !== null && _f !== void 0 ? _f : false,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
shouldApply(ctx) {
|
|
41
|
+
// Don't auto-compact if context collapse is handling it
|
|
42
|
+
if (ctx.contextCollapseEnabled) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Circuit breaker
|
|
46
|
+
if (this.consecutiveFailures >= this.options.maxConsecutiveFailures) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
// Check threshold
|
|
50
|
+
const estimator = new types_1.TokenEstimator();
|
|
51
|
+
const tokenCount = estimator.estimateForMessages(ctx.messages) - (ctx.snipTokensFreed || 0);
|
|
52
|
+
const threshold = this.options.modelTokenLimit - this.options.bufferTokens;
|
|
53
|
+
return tokenCount >= threshold;
|
|
54
|
+
}
|
|
55
|
+
async apply(ctx) {
|
|
56
|
+
const messages = ctx.messages;
|
|
57
|
+
const estimator = new types_1.TokenEstimator();
|
|
58
|
+
const originalTokens = estimator.estimateForMessages(messages) - (ctx.snipTokensFreed || 0);
|
|
59
|
+
try {
|
|
60
|
+
const result = await this.compactConversation(messages, ctx);
|
|
61
|
+
if (!result) {
|
|
62
|
+
this.consecutiveFailures++;
|
|
63
|
+
return ctx;
|
|
64
|
+
}
|
|
65
|
+
// Success — reset failure counter
|
|
66
|
+
this.consecutiveFailures = 0;
|
|
67
|
+
const newTokens = estimator.estimateForMessages(result);
|
|
68
|
+
const boundary = {
|
|
69
|
+
layer: 'auto',
|
|
70
|
+
tokensFreed: originalTokens - newTokens,
|
|
71
|
+
messagesRemoved: messages.length - result.length,
|
|
72
|
+
timestamp: new Date().toISOString(),
|
|
73
|
+
summaryIncluded: true,
|
|
74
|
+
};
|
|
75
|
+
this.tracking = {
|
|
76
|
+
compacted: true,
|
|
77
|
+
turnId: `auto-${Date.now()}`,
|
|
78
|
+
turnCounter: 0,
|
|
79
|
+
consecutiveFailures: 0,
|
|
80
|
+
};
|
|
81
|
+
if (this.options.enableLogging) {
|
|
82
|
+
console.log(`[AutoCompact] Compacted: ${originalTokens} -> ${newTokens} tokens (${boundary.tokensFreed} freed, ${boundary.messagesRemoved} messages removed)`);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
...ctx,
|
|
86
|
+
messages: result,
|
|
87
|
+
compactionHistory: [...(ctx.compactionHistory || []), boundary],
|
|
88
|
+
autoCompactTracking: this.tracking,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.consecutiveFailures++;
|
|
93
|
+
if (this.options.enableLogging) {
|
|
94
|
+
console.error('[AutoCompact] Compaction failed:', error);
|
|
95
|
+
}
|
|
96
|
+
return ctx;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
reset() {
|
|
100
|
+
this.consecutiveFailures = 0;
|
|
101
|
+
this.tracking = undefined;
|
|
102
|
+
}
|
|
103
|
+
getTracking() {
|
|
104
|
+
return this.tracking;
|
|
105
|
+
}
|
|
106
|
+
getConsecutiveFailures() {
|
|
107
|
+
return this.consecutiveFailures;
|
|
108
|
+
}
|
|
109
|
+
// ─── Core Compaction Logic ────────────────────────────────────────
|
|
110
|
+
async compactConversation(messages, ctx) {
|
|
111
|
+
const estimator = new types_1.TokenEstimator();
|
|
112
|
+
// Find split point: preserve the most recent portion of conversation
|
|
113
|
+
const splitIndex = this.findSplitIndex(messages);
|
|
114
|
+
if (splitIndex === 0) {
|
|
115
|
+
return null; // Nothing to compact
|
|
116
|
+
}
|
|
117
|
+
// Separate into history-to-compact and history-to-keep
|
|
118
|
+
const systemMessages = messages.filter(m => m.role === 'system');
|
|
119
|
+
const historyToCompress = messages
|
|
120
|
+
.slice(0, splitIndex)
|
|
121
|
+
.filter(m => m.role !== 'system');
|
|
122
|
+
const preservedMessages = messages.slice(splitIndex);
|
|
123
|
+
if (historyToCompress.length === 0) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
// Generate summary
|
|
127
|
+
const summary = await this.generateSummary(historyToCompress);
|
|
128
|
+
if (!summary || summary.trim().length === 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Build post-compact message array
|
|
132
|
+
const postCompactMessages = [
|
|
133
|
+
...systemMessages,
|
|
134
|
+
{
|
|
135
|
+
role: 'user',
|
|
136
|
+
content: `This session is being continued from a previous conversation that ran out of context. Below is a summary of the conversation so far.\n\n${summary}\n\nPlease continue from where the previous conversation left off. Do not ask the user to re-explain what they already told you — the summary above contains all the context you need.`,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
role: 'assistant',
|
|
140
|
+
content: 'Understood. I have the summary of the previous conversation and will continue from where we left off.',
|
|
141
|
+
},
|
|
142
|
+
...preservedMessages,
|
|
143
|
+
];
|
|
144
|
+
// Verify compression was effective
|
|
145
|
+
const newTokens = estimator.estimateForMessages(postCompactMessages);
|
|
146
|
+
const originalTokens = estimator.estimateForMessages(messages) - (ctx.snipTokensFreed || 0);
|
|
147
|
+
if (newTokens >= originalTokens) {
|
|
148
|
+
if (this.options.enableLogging) {
|
|
149
|
+
console.warn(`[AutoCompact] Summary inflated token count: ${originalTokens} -> ${newTokens}`);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
return postCompactMessages;
|
|
154
|
+
}
|
|
155
|
+
findSplitIndex(messages) {
|
|
156
|
+
var _a;
|
|
157
|
+
// Find turn boundaries (user messages that aren't tool responses)
|
|
158
|
+
const turnStarts = [];
|
|
159
|
+
for (let i = 0; i < messages.length; i++) {
|
|
160
|
+
const msg = messages[i];
|
|
161
|
+
if (!msg)
|
|
162
|
+
continue;
|
|
163
|
+
if (msg.role === 'user' &&
|
|
164
|
+
!msg.tool_call_id &&
|
|
165
|
+
!this.isToolResponse(msg)) {
|
|
166
|
+
turnStarts.push(i);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (turnStarts.length <= 3) {
|
|
170
|
+
return 0; // Too few turns to compress
|
|
171
|
+
}
|
|
172
|
+
// Keep the last preserveThreshold fraction of turns
|
|
173
|
+
const turnsToKeep = Math.max(3, Math.ceil(turnStarts.length * this.options.preserveThreshold));
|
|
174
|
+
const turnsToRemove = turnStarts.length - turnsToKeep;
|
|
175
|
+
if (turnsToRemove <= 0) {
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
// Adjust to avoid splitting in the middle of a tool-response chain
|
|
179
|
+
let splitIndex = (_a = turnStarts[turnsToRemove]) !== null && _a !== void 0 ? _a : messages.length;
|
|
180
|
+
// Walk forward to skip tool responses that belong to the previous turn
|
|
181
|
+
while (splitIndex < messages.length) {
|
|
182
|
+
const msg = messages[splitIndex];
|
|
183
|
+
if (!msg)
|
|
184
|
+
break;
|
|
185
|
+
if (msg.role === 'tool' || msg.tool_call_id) {
|
|
186
|
+
splitIndex++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
return splitIndex;
|
|
192
|
+
}
|
|
193
|
+
async generateSummary(messages) {
|
|
194
|
+
var _a, _b, _c, _d, _e;
|
|
195
|
+
try {
|
|
196
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
197
|
+
const codebolt = require('@codebolt/codeboltjs');
|
|
198
|
+
// Build compression prompt
|
|
199
|
+
const historyText = messages
|
|
200
|
+
.map((msg, i) => {
|
|
201
|
+
var _a;
|
|
202
|
+
const content = typeof msg.content === 'string'
|
|
203
|
+
? msg.content
|
|
204
|
+
: JSON.stringify((_a = msg.content) !== null && _a !== void 0 ? _a : '');
|
|
205
|
+
// Truncate very long messages for the prompt
|
|
206
|
+
const truncated = content.length > 2000
|
|
207
|
+
? content.slice(0, 1000) +
|
|
208
|
+
`\n...[${content.length - 2000} chars truncated]...\n` +
|
|
209
|
+
content.slice(-1000)
|
|
210
|
+
: content;
|
|
211
|
+
return `[${i + 1}] ${msg.role}: ${truncated}`;
|
|
212
|
+
})
|
|
213
|
+
.join('\n\n');
|
|
214
|
+
const prompt = `You are a conversation compression assistant. Create a detailed, structured summary that preserves ALL critical context so work can continue seamlessly.
|
|
215
|
+
|
|
216
|
+
CONVERSATION HISTORY TO COMPRESS:
|
|
217
|
+
${historyText}
|
|
218
|
+
|
|
219
|
+
Generate the summary with ALL of the following sections:
|
|
220
|
+
|
|
221
|
+
<summary>
|
|
222
|
+
1. PRIMARY REQUEST
|
|
223
|
+
[The user's original, complete request]
|
|
224
|
+
|
|
225
|
+
2. TASK EVOLUTION
|
|
226
|
+
[How the task evolved. Include key user instructions that changed direction]
|
|
227
|
+
|
|
228
|
+
3. KEY TECHNICAL CONCEPTS
|
|
229
|
+
[Architecture decisions, design patterns, algorithms, data structures, API contracts]
|
|
230
|
+
|
|
231
|
+
4. FILES AND CODE
|
|
232
|
+
[Every file that was read, created, or modified. Include file paths and key changes]
|
|
233
|
+
|
|
234
|
+
5. PROBLEM SOLVING
|
|
235
|
+
[Errors encountered and solutions applied]
|
|
236
|
+
|
|
237
|
+
6. PENDING TASKS
|
|
238
|
+
[Tasks mentioned but NOT yet completed]
|
|
239
|
+
|
|
240
|
+
7. CURRENT WORK STATE
|
|
241
|
+
[Exactly where the work left off]
|
|
242
|
+
|
|
243
|
+
8. NEXT STEP
|
|
244
|
+
[The most logical next action]
|
|
245
|
+
</summary>
|
|
246
|
+
|
|
247
|
+
CRITICAL RULES:
|
|
248
|
+
- Preserve ALL file paths exactly
|
|
249
|
+
- Preserve ALL code snippets, function names, error messages verbatim
|
|
250
|
+
- Include the FULL original user request, not a paraphrase
|
|
251
|
+
- Do not summarize away technical details
|
|
252
|
+
- Focus on facts and specifics`;
|
|
253
|
+
const response = await codebolt.llm.inference({
|
|
254
|
+
messages: [
|
|
255
|
+
{
|
|
256
|
+
role: 'system',
|
|
257
|
+
content: 'You are a precise conversation compression assistant. Be comprehensive but concise.',
|
|
258
|
+
},
|
|
259
|
+
{ role: 'user', content: prompt },
|
|
260
|
+
],
|
|
261
|
+
});
|
|
262
|
+
// Extract summary from response
|
|
263
|
+
let summary;
|
|
264
|
+
if (typeof ((_a = response === null || response === void 0 ? void 0 : response.completion) === null || _a === void 0 ? void 0 : _a.content) === 'string') {
|
|
265
|
+
summary = response.completion.content;
|
|
266
|
+
}
|
|
267
|
+
else if ((_e = (_d = (_c = (_b = response === null || response === void 0 ? void 0 : response.completion) === null || _b === void 0 ? void 0 : _b.choices) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.message) === null || _e === void 0 ? void 0 : _e.content) {
|
|
268
|
+
summary = response.completion.choices[0].message.content;
|
|
269
|
+
}
|
|
270
|
+
if (summary && summary.trim().length > 0) {
|
|
271
|
+
return summary.trim();
|
|
272
|
+
}
|
|
273
|
+
return '';
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error('[AutoCompact] Error generating summary:', error);
|
|
277
|
+
return '';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
isToolResponse(msg) {
|
|
281
|
+
if (msg.role === 'tool')
|
|
282
|
+
return true;
|
|
283
|
+
if (msg.tool_call_id)
|
|
284
|
+
return true;
|
|
285
|
+
if (typeof msg.content === 'object' && msg.content !== null) {
|
|
286
|
+
if (Array.isArray(msg.content)) {
|
|
287
|
+
return msg.content.some((block) => block &&
|
|
288
|
+
(block.type === 'tool_result' || block.type === 'function_response'));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
exports.AutoCompact = AutoCompact;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compaction Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates the 5-layer defense-in-depth compaction system.
|
|
5
|
+
* Layers execute cheapest-first: snip -> micro -> collapse -> auto -> reactive
|
|
6
|
+
*
|
|
7
|
+
* The orchestrator:
|
|
8
|
+
* 1. Runs each layer's shouldApply() check
|
|
9
|
+
* 2. Applies layers in priority order
|
|
10
|
+
* 3. Passes each layer's savings to subsequent layers via CompactionContext
|
|
11
|
+
* 4. Runs post-compact cleanup after any layer is applied
|
|
12
|
+
* 5. Provides reactive recovery for API 413 errors
|
|
13
|
+
*/
|
|
14
|
+
import type { MessageObject } from '@codebolt/types/sdk';
|
|
15
|
+
import { type CompactionBoundary, type CompactionLayerKind, type CompactionOrchestratorOptions } from './types';
|
|
16
|
+
import { SnipCompact } from './snipCompact';
|
|
17
|
+
import { MicroCompact } from './microCompact';
|
|
18
|
+
import { ContextCollapse } from './contextCollapse';
|
|
19
|
+
import { AutoCompact } from './autoCompact';
|
|
20
|
+
import { ReactiveCompact } from './reactiveCompact';
|
|
21
|
+
export interface CompactionPipelineResult {
|
|
22
|
+
/** Final messages after all applied layers */
|
|
23
|
+
messages: MessageObject[];
|
|
24
|
+
/** Total tokens freed across all layers */
|
|
25
|
+
totalTokensFreed: number;
|
|
26
|
+
/** Layers that were applied, in order */
|
|
27
|
+
layersApplied: CompactionLayerKind[];
|
|
28
|
+
/** Compaction boundaries for telemetry */
|
|
29
|
+
boundaries: CompactionBoundary[];
|
|
30
|
+
/** Whether any compaction occurred */
|
|
31
|
+
wasCompacted: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare class CompactionOrchestrator {
|
|
34
|
+
private readonly snip;
|
|
35
|
+
private readonly micro;
|
|
36
|
+
private readonly collapse;
|
|
37
|
+
private readonly auto;
|
|
38
|
+
private readonly reactive;
|
|
39
|
+
private readonly cleanup;
|
|
40
|
+
private readonly options;
|
|
41
|
+
/** Layer execution order (cheapest first) */
|
|
42
|
+
private readonly layerOrder;
|
|
43
|
+
private readonly layers;
|
|
44
|
+
constructor(options?: CompactionOrchestratorOptions);
|
|
45
|
+
/**
|
|
46
|
+
* Run the full compaction pipeline.
|
|
47
|
+
* Each layer is checked and applied in priority order.
|
|
48
|
+
* If any layer reduces messages, subsequent layers see the reduced set.
|
|
49
|
+
*/
|
|
50
|
+
compact(messages: MessageObject[]): Promise<CompactionPipelineResult>;
|
|
51
|
+
/**
|
|
52
|
+
* Attempt reactive recovery from an API error.
|
|
53
|
+
* Only called when the proactive pipeline didn't prevent overflow.
|
|
54
|
+
*/
|
|
55
|
+
recoverFromError(messages: MessageObject[], error: unknown): Promise<CompactionPipelineResult>;
|
|
56
|
+
/**
|
|
57
|
+
* Get the current auto-compact tracking state.
|
|
58
|
+
*/
|
|
59
|
+
getAutoCompactTracking(): import("./autoCompact").AutoCompactTracking | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Get the number of consecutive auto-compact failures.
|
|
62
|
+
*/
|
|
63
|
+
getConsecutiveFailures(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Reset the reactive compact's per-turn guard.
|
|
66
|
+
* Call at the start of each new turn.
|
|
67
|
+
*/
|
|
68
|
+
resetForTurn(): void;
|
|
69
|
+
/**
|
|
70
|
+
* Reset all compaction state.
|
|
71
|
+
*/
|
|
72
|
+
resetAll(): void;
|
|
73
|
+
getSnipLayer(): SnipCompact;
|
|
74
|
+
getMicroLayer(): MicroCompact;
|
|
75
|
+
getCollapseLayer(): ContextCollapse;
|
|
76
|
+
getAutoLayer(): AutoCompact;
|
|
77
|
+
getReactiveLayer(): ReactiveCompact;
|
|
78
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Compaction Orchestrator
|
|
4
|
+
*
|
|
5
|
+
* Coordinates the 5-layer defense-in-depth compaction system.
|
|
6
|
+
* Layers execute cheapest-first: snip -> micro -> collapse -> auto -> reactive
|
|
7
|
+
*
|
|
8
|
+
* The orchestrator:
|
|
9
|
+
* 1. Runs each layer's shouldApply() check
|
|
10
|
+
* 2. Applies layers in priority order
|
|
11
|
+
* 3. Passes each layer's savings to subsequent layers via CompactionContext
|
|
12
|
+
* 4. Runs post-compact cleanup after any layer is applied
|
|
13
|
+
* 5. Provides reactive recovery for API 413 errors
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.CompactionOrchestrator = void 0;
|
|
17
|
+
const snipCompact_1 = require("./snipCompact");
|
|
18
|
+
const microCompact_1 = require("./microCompact");
|
|
19
|
+
const contextCollapse_1 = require("./contextCollapse");
|
|
20
|
+
const autoCompact_1 = require("./autoCompact");
|
|
21
|
+
const reactiveCompact_1 = require("./reactiveCompact");
|
|
22
|
+
const postCompactCleanup_1 = require("./postCompactCleanup");
|
|
23
|
+
const DEFAULT_MODEL_TOKEN_LIMIT = 128000;
|
|
24
|
+
class CompactionOrchestrator {
|
|
25
|
+
constructor(options) {
|
|
26
|
+
var _a, _b, _c, _d;
|
|
27
|
+
/** Layer execution order (cheapest first) */
|
|
28
|
+
this.layerOrder = [
|
|
29
|
+
'snip',
|
|
30
|
+
'micro',
|
|
31
|
+
'collapse',
|
|
32
|
+
'auto',
|
|
33
|
+
];
|
|
34
|
+
this.options = {
|
|
35
|
+
modelTokenLimit: (_a = options === null || options === void 0 ? void 0 : options.modelTokenLimit) !== null && _a !== void 0 ? _a : DEFAULT_MODEL_TOKEN_LIMIT,
|
|
36
|
+
autoCompactEnabled: (_b = options === null || options === void 0 ? void 0 : options.autoCompactEnabled) !== null && _b !== void 0 ? _b : true,
|
|
37
|
+
contextCollapseEnabled: (_c = options === null || options === void 0 ? void 0 : options.contextCollapseEnabled) !== null && _c !== void 0 ? _c : false,
|
|
38
|
+
enableLogging: (_d = options === null || options === void 0 ? void 0 : options.enableLogging) !== null && _d !== void 0 ? _d : false,
|
|
39
|
+
};
|
|
40
|
+
this.snip = new snipCompact_1.SnipCompact({ enableLogging: this.options.enableLogging });
|
|
41
|
+
this.micro = new microCompact_1.MicroCompact({ enableLogging: this.options.enableLogging });
|
|
42
|
+
this.collapse = new contextCollapse_1.ContextCollapse({
|
|
43
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
44
|
+
enableLogging: this.options.enableLogging,
|
|
45
|
+
});
|
|
46
|
+
this.auto = new autoCompact_1.AutoCompact({
|
|
47
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
48
|
+
enableLogging: this.options.enableLogging,
|
|
49
|
+
});
|
|
50
|
+
this.reactive = new reactiveCompact_1.ReactiveCompact({
|
|
51
|
+
modelTokenLimit: this.options.modelTokenLimit,
|
|
52
|
+
enableLogging: this.options.enableLogging,
|
|
53
|
+
});
|
|
54
|
+
this.cleanup = new postCompactCleanup_1.PostCompactCleanup({
|
|
55
|
+
enableLogging: this.options.enableLogging,
|
|
56
|
+
});
|
|
57
|
+
const layerEntries = [
|
|
58
|
+
['snip', this.snip],
|
|
59
|
+
['micro', this.micro],
|
|
60
|
+
['collapse', this.collapse],
|
|
61
|
+
['auto', this.auto],
|
|
62
|
+
['reactive', this.reactive],
|
|
63
|
+
];
|
|
64
|
+
this.layers = new Map(layerEntries);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run the full compaction pipeline.
|
|
68
|
+
* Each layer is checked and applied in priority order.
|
|
69
|
+
* If any layer reduces messages, subsequent layers see the reduced set.
|
|
70
|
+
*/
|
|
71
|
+
async compact(messages) {
|
|
72
|
+
var _a;
|
|
73
|
+
let ctx = {
|
|
74
|
+
messages,
|
|
75
|
+
snipTokensFreed: 0,
|
|
76
|
+
contextCollapseEnabled: this.options.contextCollapseEnabled,
|
|
77
|
+
compactionHistory: [],
|
|
78
|
+
autoCompactTracking: {
|
|
79
|
+
compacted: false,
|
|
80
|
+
turnId: `turn-${Date.now()}`,
|
|
81
|
+
turnCounter: 0,
|
|
82
|
+
consecutiveFailures: 0,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const layersApplied = [];
|
|
86
|
+
const boundaries = [];
|
|
87
|
+
let totalTokensFreed = 0;
|
|
88
|
+
for (const layerName of this.layerOrder) {
|
|
89
|
+
// Skip auto-compact if disabled or if context collapse is handling it
|
|
90
|
+
if (layerName === 'auto' && !this.options.autoCompactEnabled)
|
|
91
|
+
continue;
|
|
92
|
+
if (layerName === 'auto' && this.options.contextCollapseEnabled)
|
|
93
|
+
continue;
|
|
94
|
+
if (layerName === 'collapse' && !this.options.contextCollapseEnabled)
|
|
95
|
+
continue;
|
|
96
|
+
const layer = this.layers.get(layerName);
|
|
97
|
+
if (!layer)
|
|
98
|
+
continue;
|
|
99
|
+
if (layer.shouldApply(ctx)) {
|
|
100
|
+
try {
|
|
101
|
+
const prevLength = ctx.messages.length;
|
|
102
|
+
ctx = await layer.apply(ctx);
|
|
103
|
+
// Track what was applied
|
|
104
|
+
if (ctx.messages.length !== prevLength || ctx.compactionHistory) {
|
|
105
|
+
layersApplied.push(layerName);
|
|
106
|
+
// Get the latest boundary
|
|
107
|
+
const latestBoundary = (_a = ctx.compactionHistory) === null || _a === void 0 ? void 0 : _a.at(-1);
|
|
108
|
+
if (latestBoundary) {
|
|
109
|
+
boundaries.push(latestBoundary);
|
|
110
|
+
totalTokensFreed += latestBoundary.tokensFreed;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (this.options.enableLogging) {
|
|
116
|
+
console.error(`[CompactionOrchestrator] Layer ${layerName} failed:`, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Run post-compact cleanup
|
|
122
|
+
if (layersApplied.length > 0) {
|
|
123
|
+
this.cleanup.runCleanup(this.layers, layersApplied);
|
|
124
|
+
}
|
|
125
|
+
const wasCompacted = layersApplied.length > 0;
|
|
126
|
+
if (wasCompacted && this.options.enableLogging) {
|
|
127
|
+
console.log(`[CompactionOrchestrator] Compacted: layers=[${layersApplied.join(',')}], tokensFreed=${totalTokensFreed}`);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
messages: ctx.messages,
|
|
131
|
+
totalTokensFreed,
|
|
132
|
+
layersApplied,
|
|
133
|
+
boundaries,
|
|
134
|
+
wasCompacted,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Attempt reactive recovery from an API error.
|
|
139
|
+
* Only called when the proactive pipeline didn't prevent overflow.
|
|
140
|
+
*/
|
|
141
|
+
async recoverFromError(messages, error) {
|
|
142
|
+
const ctx = {
|
|
143
|
+
messages,
|
|
144
|
+
contextCollapseEnabled: this.options.contextCollapseEnabled,
|
|
145
|
+
compactionHistory: [],
|
|
146
|
+
};
|
|
147
|
+
// First try context collapse recovery (cheap: drain staged collapses)
|
|
148
|
+
if (this.options.contextCollapseEnabled) {
|
|
149
|
+
const collapseResult = this.collapse.recoverFromOverflow(messages);
|
|
150
|
+
if (collapseResult.committed > 0) {
|
|
151
|
+
return {
|
|
152
|
+
messages: collapseResult.messages,
|
|
153
|
+
totalTokensFreed: 0, // Already accounted for in collapse
|
|
154
|
+
layersApplied: ['collapse'],
|
|
155
|
+
boundaries: [{
|
|
156
|
+
layer: 'collapse',
|
|
157
|
+
tokensFreed: 0,
|
|
158
|
+
messagesRemoved: 0,
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
committed: collapseResult.committed,
|
|
161
|
+
}],
|
|
162
|
+
wasCompacted: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Fall through to reactive compact
|
|
167
|
+
const result = await this.reactive.tryRecoverFromError(ctx, error);
|
|
168
|
+
if (result.recovered) {
|
|
169
|
+
this.reactive.resetForTurn();
|
|
170
|
+
return {
|
|
171
|
+
messages: result.messages,
|
|
172
|
+
totalTokensFreed: result.tokensBefore - result.tokensAfter,
|
|
173
|
+
layersApplied: ['reactive'],
|
|
174
|
+
boundaries: [{
|
|
175
|
+
layer: 'reactive',
|
|
176
|
+
tokensFreed: result.tokensBefore - result.tokensAfter,
|
|
177
|
+
messagesRemoved: 0,
|
|
178
|
+
timestamp: new Date().toISOString(),
|
|
179
|
+
}],
|
|
180
|
+
wasCompacted: true,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
messages,
|
|
185
|
+
totalTokensFreed: 0,
|
|
186
|
+
layersApplied: [],
|
|
187
|
+
boundaries: [],
|
|
188
|
+
wasCompacted: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get the current auto-compact tracking state.
|
|
193
|
+
*/
|
|
194
|
+
getAutoCompactTracking() {
|
|
195
|
+
return this.auto.getTracking();
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Get the number of consecutive auto-compact failures.
|
|
199
|
+
*/
|
|
200
|
+
getConsecutiveFailures() {
|
|
201
|
+
return this.auto.getConsecutiveFailures();
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Reset the reactive compact's per-turn guard.
|
|
205
|
+
* Call at the start of each new turn.
|
|
206
|
+
*/
|
|
207
|
+
resetForTurn() {
|
|
208
|
+
this.reactive.resetForTurn();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Reset all compaction state.
|
|
212
|
+
*/
|
|
213
|
+
resetAll() {
|
|
214
|
+
for (const [, layer] of this.layers) {
|
|
215
|
+
try {
|
|
216
|
+
layer.reset();
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// ─── Convenience Accessors ──────────────────────────────────────
|
|
224
|
+
getSnipLayer() { return this.snip; }
|
|
225
|
+
getMicroLayer() { return this.micro; }
|
|
226
|
+
getCollapseLayer() { return this.collapse; }
|
|
227
|
+
getAutoLayer() { return this.auto; }
|
|
228
|
+
getReactiveLayer() { return this.reactive; }
|
|
229
|
+
}
|
|
230
|
+
exports.CompactionOrchestrator = CompactionOrchestrator;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 3: Context Collapse
|
|
3
|
+
*
|
|
4
|
+
* Incrementally archives messages into a collapse store with granular summaries.
|
|
5
|
+
* Unlike full auto-compact which produces one monolithic summary, context collapse
|
|
6
|
+
* preserves granular per-range summaries so more context is retained.
|
|
7
|
+
*
|
|
8
|
+
* Key properties:
|
|
9
|
+
* - Commits at ~90% of context window
|
|
10
|
+
* - Blocks at ~95% (hard limit)
|
|
11
|
+
* - Archives messages incrementally (not all at once)
|
|
12
|
+
* - Read-time projection over full history
|
|
13
|
+
* - Recovery: drains staged collapses on 413 errors (cheap)
|
|
14
|
+
* - When enabled, suppresses auto-compact to avoid racing
|
|
15
|
+
*/
|
|
16
|
+
import type { MessageObject } from '@codebolt/types/sdk';
|
|
17
|
+
import { type CompactionContext, type CompactionLayer, type CompactionLayerKind } from './types';
|
|
18
|
+
export interface ContextCollapseOptions {
|
|
19
|
+
/** Commit threshold as fraction of context window (default: 0.9) */
|
|
20
|
+
commitThreshold?: number;
|
|
21
|
+
/** Blocking threshold as fraction of context window (default: 0.95) */
|
|
22
|
+
blockingThreshold?: number;
|
|
23
|
+
/** Model token limit (default: 128000) */
|
|
24
|
+
modelTokenLimit?: number;
|
|
25
|
+
/** LLM role for granular summarization (default: 'summarizer') */
|
|
26
|
+
llmRole?: string;
|
|
27
|
+
/** Max summaries to keep in the collapse store (default: 20) */
|
|
28
|
+
maxSummaries?: number;
|
|
29
|
+
/** Enable logging (default: false) */
|
|
30
|
+
enableLogging?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare class ContextCollapse implements CompactionLayer {
|
|
33
|
+
readonly name: CompactionLayerKind;
|
|
34
|
+
private readonly options;
|
|
35
|
+
/** The collapse store: ordered list of collapsed ranges */
|
|
36
|
+
private store;
|
|
37
|
+
/** Staged collapses waiting for API confirmation */
|
|
38
|
+
private staged;
|
|
39
|
+
constructor(options?: ContextCollapseOptions);
|
|
40
|
+
shouldApply(ctx: CompactionContext): boolean;
|
|
41
|
+
apply(ctx: CompactionContext): Promise<CompactionContext>;
|
|
42
|
+
/**
|
|
43
|
+
* Drain staged collapses on API 413 errors (recovery path).
|
|
44
|
+
* Confirms all staged entries into the permanent store.
|
|
45
|
+
*/
|
|
46
|
+
drainStaged(): number;
|
|
47
|
+
/**
|
|
48
|
+
* Recovery from overflow: drain staged collapses and rebuild.
|
|
49
|
+
*/
|
|
50
|
+
recoverFromOverflow(messages: MessageObject[]): {
|
|
51
|
+
messages: MessageObject[];
|
|
52
|
+
committed: number;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Check if at blocking threshold.
|
|
56
|
+
*/
|
|
57
|
+
isAtBlockingLimit(messages: MessageObject[]): boolean;
|
|
58
|
+
reset(): void;
|
|
59
|
+
private findSplitPoint;
|
|
60
|
+
private buildSummaryMessages;
|
|
61
|
+
private generateGranularSummary;
|
|
62
|
+
private createStructuralSummary;
|
|
63
|
+
}
|