@vybestack/llxprt-code-agents 0.10.0-nightly.260613.1adad3b34
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/.last_build +0 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/src/agents/executor-prompt-builder.d.ts +19 -0
- package/dist/src/agents/executor-prompt-builder.js +44 -0
- package/dist/src/agents/executor-prompt-builder.js.map +1 -0
- package/dist/src/agents/executor-termination.d.ts +22 -0
- package/dist/src/agents/executor-termination.js +44 -0
- package/dist/src/agents/executor-termination.js.map +1 -0
- package/dist/src/agents/executor-validation.d.ts +17 -0
- package/dist/src/agents/executor-validation.js +35 -0
- package/dist/src/agents/executor-validation.js.map +1 -0
- package/dist/src/agents/executor.d.ts +136 -0
- package/dist/src/agents/executor.js +874 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +45 -0
- package/dist/src/agents/invocation.js +118 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/recovery.d.ts +75 -0
- package/dist/src/agents/recovery.js +107 -0
- package/dist/src/agents/recovery.js.map +1 -0
- package/dist/src/agents/types.d.ts +149 -0
- package/dist/src/agents/types.js +18 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +28 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/compression/CompressionHandler.d.ts +178 -0
- package/dist/src/compression/CompressionHandler.js +630 -0
- package/dist/src/compression/CompressionHandler.js.map +1 -0
- package/dist/src/compression/HighDensityStrategy.d.ts +113 -0
- package/dist/src/compression/HighDensityStrategy.js +769 -0
- package/dist/src/compression/HighDensityStrategy.js.map +1 -0
- package/dist/src/compression/MiddleOutStrategy.d.ts +27 -0
- package/dist/src/compression/MiddleOutStrategy.js +347 -0
- package/dist/src/compression/MiddleOutStrategy.js.map +1 -0
- package/dist/src/compression/OneShotStrategy.d.ts +22 -0
- package/dist/src/compression/OneShotStrategy.js +235 -0
- package/dist/src/compression/OneShotStrategy.js.map +1 -0
- package/dist/src/compression/TopDownTruncationStrategy.d.ts +13 -0
- package/dist/src/compression/TopDownTruncationStrategy.js +74 -0
- package/dist/src/compression/TopDownTruncationStrategy.js.map +1 -0
- package/dist/src/compression/compressionBudgeting.d.ts +34 -0
- package/dist/src/compression/compressionBudgeting.js +139 -0
- package/dist/src/compression/compressionBudgeting.js.map +1 -0
- package/dist/src/compression/compressionStrategyFactory.d.ts +23 -0
- package/dist/src/compression/compressionStrategyFactory.js +40 -0
- package/dist/src/compression/compressionStrategyFactory.js.map +1 -0
- package/dist/src/compression/index.d.ts +16 -0
- package/dist/src/compression/index.js +17 -0
- package/dist/src/compression/index.js.map +1 -0
- package/dist/src/compression/reasoningUtils.d.ts +18 -0
- package/dist/src/compression/reasoningUtils.js +22 -0
- package/dist/src/compression/reasoningUtils.js.map +1 -0
- package/dist/src/compression/utils.d.ts +114 -0
- package/dist/src/compression/utils.js +316 -0
- package/dist/src/compression/utils.js.map +1 -0
- package/dist/src/core/AgentHookManager.d.ts +41 -0
- package/dist/src/core/AgentHookManager.js +65 -0
- package/dist/src/core/AgentHookManager.js.map +1 -0
- package/dist/src/core/ChatSessionFactory.d.ts +50 -0
- package/dist/src/core/ChatSessionFactory.js +216 -0
- package/dist/src/core/ChatSessionFactory.js.map +1 -0
- package/dist/src/core/ConversationManager.d.ts +113 -0
- package/dist/src/core/ConversationManager.js +311 -0
- package/dist/src/core/ConversationManager.js.map +1 -0
- package/dist/src/core/DirectMessageProcessor.d.ts +79 -0
- package/dist/src/core/DirectMessageProcessor.js +478 -0
- package/dist/src/core/DirectMessageProcessor.js.map +1 -0
- package/dist/src/core/IdeContextTracker.d.ts +56 -0
- package/dist/src/core/IdeContextTracker.js +207 -0
- package/dist/src/core/IdeContextTracker.js.map +1 -0
- package/dist/src/core/MessageConverter.d.ts +78 -0
- package/dist/src/core/MessageConverter.js +492 -0
- package/dist/src/core/MessageConverter.js.map +1 -0
- package/dist/src/core/MessageStreamOrchestrator.d.ts +85 -0
- package/dist/src/core/MessageStreamOrchestrator.js +448 -0
- package/dist/src/core/MessageStreamOrchestrator.js.map +1 -0
- package/dist/src/core/MessageStreamTerminalHandler.d.ts +16 -0
- package/dist/src/core/MessageStreamTerminalHandler.js +170 -0
- package/dist/src/core/MessageStreamTerminalHandler.js.map +1 -0
- package/dist/src/core/StreamProcessor.d.ts +105 -0
- package/dist/src/core/StreamProcessor.js +660 -0
- package/dist/src/core/StreamProcessor.js.map +1 -0
- package/dist/src/core/TodoContinuationService.d.ts +74 -0
- package/dist/src/core/TodoContinuationService.js +312 -0
- package/dist/src/core/TodoContinuationService.js.map +1 -0
- package/dist/src/core/TurnProcessor.d.ts +91 -0
- package/dist/src/core/TurnProcessor.js +540 -0
- package/dist/src/core/TurnProcessor.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +115 -0
- package/dist/src/core/baseLlmClient.js +231 -0
- package/dist/src/core/baseLlmClient.js.map +1 -0
- package/dist/src/core/bucketFailoverIntegration.d.ts +62 -0
- package/dist/src/core/bucketFailoverIntegration.js +186 -0
- package/dist/src/core/bucketFailoverIntegration.js.map +1 -0
- package/dist/src/core/chatSession.d.ts +121 -0
- package/dist/src/core/chatSession.js +720 -0
- package/dist/src/core/chatSession.js.map +1 -0
- package/dist/src/core/client.d.ts +132 -0
- package/dist/src/core/client.js +524 -0
- package/dist/src/core/client.js.map +1 -0
- package/dist/src/core/clientHelpers.d.ts +15 -0
- package/dist/src/core/clientHelpers.js +122 -0
- package/dist/src/core/clientHelpers.js.map +1 -0
- package/dist/src/core/clientLlmUtilities.d.ts +30 -0
- package/dist/src/core/clientLlmUtilities.js +125 -0
- package/dist/src/core/clientLlmUtilities.js.map +1 -0
- package/dist/src/core/clientToolGovernance.d.ts +37 -0
- package/dist/src/core/clientToolGovernance.js +104 -0
- package/dist/src/core/clientToolGovernance.js.map +1 -0
- package/dist/src/core/compression-config.d.ts +12 -0
- package/dist/src/core/compression-config.js +21 -0
- package/dist/src/core/compression-config.js.map +1 -0
- package/dist/src/core/coreToolScheduler.d.ts +105 -0
- package/dist/src/core/coreToolScheduler.js +453 -0
- package/dist/src/core/coreToolScheduler.js.map +1 -0
- package/dist/src/core/hookToolRestrictions.d.ts +18 -0
- package/dist/src/core/hookToolRestrictions.js +187 -0
- package/dist/src/core/hookToolRestrictions.js.map +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +17 -0
- package/dist/src/core/nonInteractiveToolExecutor.js +102 -0
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
- package/dist/src/core/subagent.d.ts +105 -0
- package/dist/src/core/subagent.js +650 -0
- package/dist/src/core/subagent.js.map +1 -0
- package/dist/src/core/subagentExecution.d.ts +104 -0
- package/dist/src/core/subagentExecution.js +275 -0
- package/dist/src/core/subagentExecution.js.map +1 -0
- package/dist/src/core/subagentOrchestrator.d.ts +84 -0
- package/dist/src/core/subagentOrchestrator.js +503 -0
- package/dist/src/core/subagentOrchestrator.js.map +1 -0
- package/dist/src/core/subagentRuntimeSetup.d.ts +75 -0
- package/dist/src/core/subagentRuntimeSetup.js +387 -0
- package/dist/src/core/subagentRuntimeSetup.js.map +1 -0
- package/dist/src/core/subagentScheduler.d.ts +17 -0
- package/dist/src/core/subagentScheduler.js +7 -0
- package/dist/src/core/subagentScheduler.js.map +1 -0
- package/dist/src/core/subagentToolProcessing.d.ts +51 -0
- package/dist/src/core/subagentToolProcessing.js +359 -0
- package/dist/src/core/subagentToolProcessing.js.map +1 -0
- package/dist/src/core/toolGovernance.d.ts +18 -0
- package/dist/src/core/toolGovernance.js +50 -0
- package/dist/src/core/toolGovernance.js.map +1 -0
- package/dist/src/core/turn.d.ts +47 -0
- package/dist/src/core/turn.js +455 -0
- package/dist/src/core/turn.js.map +1 -0
- package/dist/src/core/turnLogging.d.ts +35 -0
- package/dist/src/core/turnLogging.js +80 -0
- package/dist/src/core/turnLogging.js.map +1 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +44 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/scheduler/confirmation-coordinator.d.ts +139 -0
- package/dist/src/scheduler/confirmation-coordinator.js +443 -0
- package/dist/src/scheduler/confirmation-coordinator.js.map +1 -0
- package/dist/src/scheduler/result-aggregator.d.ts +127 -0
- package/dist/src/scheduler/result-aggregator.js +302 -0
- package/dist/src/scheduler/result-aggregator.js.map +1 -0
- package/dist/src/scheduler/status-transitions.d.ts +39 -0
- package/dist/src/scheduler/status-transitions.js +184 -0
- package/dist/src/scheduler/status-transitions.js.map +1 -0
- package/dist/src/scheduler/tool-dispatcher.d.ts +40 -0
- package/dist/src/scheduler/tool-dispatcher.js +120 -0
- package/dist/src/scheduler/tool-dispatcher.js.map +1 -0
- package/dist/src/scheduler/tool-executor.d.ts +44 -0
- package/dist/src/scheduler/tool-executor.js +117 -0
- package/dist/src/scheduler/tool-executor.js.map +1 -0
- package/dist/src/scheduler/utils.d.ts +11 -0
- package/dist/src/scheduler/utils.js +19 -0
- package/dist/src/scheduler/utils.js.map +1 -0
- package/dist/src/tools/task.d.ts +128 -0
- package/dist/src/tools/task.js +932 -0
- package/dist/src/tools/task.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2025 Vybestack LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
/* eslint-disable complexity, sonarjs/cognitive-complexity -- Phase 5: legacy core boundary retained while larger decomposition continues. */
|
|
7
|
+
/**
|
|
8
|
+
* @plan PLAN-20260211-HIGHDENSITY.P09
|
|
9
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
10
|
+
* @requirement REQ-HD-004.3, REQ-HD-005.1, REQ-HD-006.1, REQ-HD-007.1
|
|
11
|
+
* @pseudocode high-density-optimize.md
|
|
12
|
+
*
|
|
13
|
+
* High-density compression strategy: performs multi-phase context-window
|
|
14
|
+
* optimization (read/write pair pruning, file deduplication, recency pruning)
|
|
15
|
+
* followed by LLM-based compression of the remaining history.
|
|
16
|
+
*
|
|
17
|
+
* The optimize() pass runs in "continuous" mode — before every threshold check —
|
|
18
|
+
* to reclaim tokens without LLM calls. The compress() pass uses an LLM when
|
|
19
|
+
* the token budget is still exceeded after optimization.
|
|
20
|
+
*/
|
|
21
|
+
import * as path from 'node:path';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Constants (@pseudocode high-density-optimize.md lines 10-15)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
export const READ_TOOLS = [
|
|
26
|
+
'read_file',
|
|
27
|
+
'read_line_range',
|
|
28
|
+
'read_many_files',
|
|
29
|
+
'ast_read_file',
|
|
30
|
+
];
|
|
31
|
+
export const WRITE_TOOLS = [
|
|
32
|
+
'write_file',
|
|
33
|
+
'ast_edit',
|
|
34
|
+
'replace',
|
|
35
|
+
'insert_at_line',
|
|
36
|
+
'delete_line_range',
|
|
37
|
+
];
|
|
38
|
+
/** Shared suffix hint used in both pruned pointers and tool summaries */
|
|
39
|
+
export const RERUN_HINT_SUFFIX = '— re-run to view';
|
|
40
|
+
export const PRUNED_POINTER = `[Result pruned ${RERUN_HINT_SUFFIX}]`;
|
|
41
|
+
export const FILE_INCLUSION_OPEN_REGEX = /^--- (.+) ---$/m;
|
|
42
|
+
export const FILE_INCLUSION_CLOSE = '--- End of content ---';
|
|
43
|
+
const GLOB_CHARS = ['*', '?'];
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helper functions (@pseudocode high-density-optimize.md lines 260-273, 470-471)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
49
|
+
* @requirement REQ-HD-005.4, REQ-HD-013.5
|
|
50
|
+
* @pseudocode high-density-optimize.md lines 260-267
|
|
51
|
+
*/
|
|
52
|
+
function extractFilePath(params) {
|
|
53
|
+
if (typeof params !== 'object' || params === null) {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
const p = params;
|
|
57
|
+
const candidate = p.file_path ?? p.absolute_path ?? p.path;
|
|
58
|
+
if (typeof candidate === 'string' && candidate.length > 0) {
|
|
59
|
+
return candidate;
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
65
|
+
* @requirement REQ-HD-005.5, REQ-HD-005.11
|
|
66
|
+
* @pseudocode high-density-optimize.md lines 270-273
|
|
67
|
+
*/
|
|
68
|
+
function resolvePath(filePath, workspaceRoot) {
|
|
69
|
+
if (path.isAbsolute(filePath)) {
|
|
70
|
+
return path.resolve(filePath);
|
|
71
|
+
}
|
|
72
|
+
return path.resolve(workspaceRoot, filePath);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
76
|
+
* @pseudocode high-density-optimize.md lines 365-393
|
|
77
|
+
*/
|
|
78
|
+
function findAllInclusions(text) {
|
|
79
|
+
const results = [];
|
|
80
|
+
const openPattern = /^--- (.+) ---$/gm;
|
|
81
|
+
let match;
|
|
82
|
+
while ((match = openPattern.exec(text)) !== null) {
|
|
83
|
+
const filePath = match[1].trim();
|
|
84
|
+
const startOffset = match.index;
|
|
85
|
+
const closeIndex = text.indexOf(FILE_INCLUSION_CLOSE, startOffset + match[0].length);
|
|
86
|
+
if (closeIndex === -1) {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
let endOffset = closeIndex + FILE_INCLUSION_CLOSE.length;
|
|
90
|
+
if (text[endOffset] === '\n') {
|
|
91
|
+
endOffset = endOffset + 1;
|
|
92
|
+
}
|
|
93
|
+
results.push({ filePath, startOffset, endOffset });
|
|
94
|
+
openPattern.lastIndex = endOffset;
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
100
|
+
* @pseudocode high-density-optimize.md lines 470-471
|
|
101
|
+
*/
|
|
102
|
+
function isEmptyTextBlock(block) {
|
|
103
|
+
return block.type === 'text' && (!block.text || block.text.trim() === '');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
107
|
+
* Build latest write map — latest write index per file path.
|
|
108
|
+
* @pseudocode high-density-optimize.md lines 60-90
|
|
109
|
+
*/
|
|
110
|
+
function buildLatestWriteMap(history, config) {
|
|
111
|
+
const latestWrite = new Map();
|
|
112
|
+
for (let index = history.length - 1; index >= 0; index--) {
|
|
113
|
+
const entry = history[index];
|
|
114
|
+
if (entry.speaker !== 'ai') {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
118
|
+
for (const block of entry.blocks) {
|
|
119
|
+
if (block.type !== 'tool_call') {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (!WRITE_TOOLS.includes(block.name)) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const filePath = extractFilePath(block.parameters);
|
|
126
|
+
if (filePath === undefined) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const resolved = resolvePath(filePath, config.workspaceRoot);
|
|
130
|
+
if (!latestWrite.has(resolved)) {
|
|
131
|
+
latestWrite.set(resolved, index);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return latestWrite;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
139
|
+
* Identify stale read tool calls by comparing against the latest write map.
|
|
140
|
+
* @pseudocode high-density-optimize.md lines 100-160
|
|
141
|
+
*/
|
|
142
|
+
function identifyStaleReadCalls(history, config, latestWrite, strategy) {
|
|
143
|
+
const staleCallIds = new Set();
|
|
144
|
+
const aiEntryStaleBlocks = new Map();
|
|
145
|
+
const aiEntryTotalToolCalls = new Map();
|
|
146
|
+
for (let index = 0; index < history.length; index++) {
|
|
147
|
+
const entry = history[index];
|
|
148
|
+
if (entry.speaker !== 'ai') {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const toolCallBlocks = entry.blocks.filter((b) => b.type === 'tool_call');
|
|
152
|
+
aiEntryTotalToolCalls.set(index, toolCallBlocks.length);
|
|
153
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
154
|
+
for (const block of toolCallBlocks) {
|
|
155
|
+
if (!READ_TOOLS.includes(block.name)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (block.name === 'read_many_files') {
|
|
159
|
+
const canPrune = strategy.canPruneReadManyFiles(block.parameters, config.workspaceRoot, latestWrite, index);
|
|
160
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
161
|
+
if (!canPrune) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const filePath = extractFilePath(block.parameters);
|
|
167
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
168
|
+
if (filePath === undefined) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const resolved = resolvePath(filePath, config.workspaceRoot);
|
|
172
|
+
const writeIndex = latestWrite.get(resolved);
|
|
173
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
174
|
+
if (writeIndex === undefined || writeIndex <= index) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// This read is stale
|
|
179
|
+
staleCallIds.add(block.id);
|
|
180
|
+
if (!aiEntryStaleBlocks.has(index)) {
|
|
181
|
+
aiEntryStaleBlocks.set(index, new Set());
|
|
182
|
+
}
|
|
183
|
+
aiEntryStaleBlocks.get(index).add(block.id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { staleCallIds, aiEntryStaleBlocks, aiEntryTotalToolCalls };
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
190
|
+
* Process AI entries with stale tool calls — remove or replace.
|
|
191
|
+
* @pseudocode high-density-optimize.md lines 162-180
|
|
192
|
+
*/
|
|
193
|
+
function applyStaleAiEntries(history, staleCallIds, aiEntryStaleBlocks, aiEntryTotalToolCalls, removals, replacements) {
|
|
194
|
+
for (const [aiIndex, staleCalls] of aiEntryStaleBlocks) {
|
|
195
|
+
const totalCalls = aiEntryTotalToolCalls.get(aiIndex) ?? 0;
|
|
196
|
+
const filteredBlocks = history[aiIndex].blocks.filter((b) => b.type !== 'tool_call' || !staleCalls.has(b.id));
|
|
197
|
+
if (staleCalls.size === totalCalls &&
|
|
198
|
+
(filteredBlocks.length === 0 ||
|
|
199
|
+
filteredBlocks.every((b) => isEmptyTextBlock(b)))) {
|
|
200
|
+
removals.add(aiIndex);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
replacements.set(aiIndex, {
|
|
204
|
+
...history[aiIndex],
|
|
205
|
+
blocks: filteredBlocks,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
212
|
+
* Process tool entries — remove tool_response blocks for stale callIds.
|
|
213
|
+
* Returns the number of pruned responses.
|
|
214
|
+
* @pseudocode high-density-optimize.md lines 185-209
|
|
215
|
+
*/
|
|
216
|
+
function applyStaleToolEntries(history, staleCallIds, removals, replacements) {
|
|
217
|
+
let prunedCount = 0;
|
|
218
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
219
|
+
for (let index = 0; index < history.length; index++) {
|
|
220
|
+
const entry = history[index];
|
|
221
|
+
if (entry.speaker !== 'tool') {
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (removals.has(index)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const responseBlocks = entry.blocks.filter((b) => b.type === 'tool_response');
|
|
228
|
+
const staleResponses = responseBlocks.filter((b) => staleCallIds.has(b.callId));
|
|
229
|
+
if (staleResponses.length === 0) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (staleResponses.length === responseBlocks.length &&
|
|
233
|
+
entry.blocks.every((b) => b.type === 'tool_response')) {
|
|
234
|
+
removals.add(index);
|
|
235
|
+
prunedCount = prunedCount + staleResponses.length;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const filteredBlocks = entry.blocks.filter((b) => b.type !== 'tool_response' || !staleCallIds.has(b.callId));
|
|
239
|
+
if (filteredBlocks.length === 0) {
|
|
240
|
+
removals.add(index);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
replacements.set(index, {
|
|
244
|
+
...entry,
|
|
245
|
+
blocks: filteredBlocks,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
prunedCount = prunedCount + staleResponses.length;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return prunedCount;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
255
|
+
* @requirement REQ-HD-006.1
|
|
256
|
+
* Scan human messages for file inclusions, grouped by resolved file path.
|
|
257
|
+
* @pseudocode high-density-optimize.md lines 280-310
|
|
258
|
+
*/
|
|
259
|
+
function scanFileInclusions(history, config, existingRemovals) {
|
|
260
|
+
const inclusions = new Map();
|
|
261
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
262
|
+
for (let index = 0; index < history.length; index++) {
|
|
263
|
+
const entry = history[index];
|
|
264
|
+
if (entry.speaker !== 'human') {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (existingRemovals.has(index)) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
for (let blockIndex = 0; blockIndex < entry.blocks.length; blockIndex++) {
|
|
271
|
+
const block = entry.blocks[blockIndex];
|
|
272
|
+
if (block.type !== 'text') {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const text = block.text;
|
|
276
|
+
const matches = findAllInclusions(text);
|
|
277
|
+
for (const match of matches) {
|
|
278
|
+
const resolvedFilePath = resolvePath(match.filePath, config.workspaceRoot);
|
|
279
|
+
// eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
280
|
+
if (!inclusions.has(resolvedFilePath)) {
|
|
281
|
+
inclusions.set(resolvedFilePath, []);
|
|
282
|
+
}
|
|
283
|
+
inclusions.get(resolvedFilePath).push({
|
|
284
|
+
messageIndex: index,
|
|
285
|
+
blockIndex,
|
|
286
|
+
startOffset: match.startOffset,
|
|
287
|
+
endOffset: match.endOffset,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return inclusions;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
296
|
+
* @requirement REQ-HD-006.2
|
|
297
|
+
* Collect all stale inclusions (all-but-latest per file), group by block key.
|
|
298
|
+
* @pseudocode high-density-optimize.md lines 312-330
|
|
299
|
+
*/
|
|
300
|
+
function collectStaleInclusions(inclusions) {
|
|
301
|
+
const staleByBlock = new Map();
|
|
302
|
+
for (const [, entries] of inclusions) {
|
|
303
|
+
if (entries.length <= 1) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
entries.sort((a, b) => {
|
|
307
|
+
const messageDiff = b.messageIndex - a.messageIndex;
|
|
308
|
+
return messageDiff !== 0 ? messageDiff : b.startOffset - a.startOffset;
|
|
309
|
+
});
|
|
310
|
+
// entries[0] is the latest — preserve. Strip entries[1..n]
|
|
311
|
+
for (let i = 1; i < entries.length; i++) {
|
|
312
|
+
const stale = entries[i];
|
|
313
|
+
const key = `${stale.messageIndex}:${stale.blockIndex}`;
|
|
314
|
+
if (!staleByBlock.has(key)) {
|
|
315
|
+
staleByBlock.set(key, []);
|
|
316
|
+
}
|
|
317
|
+
staleByBlock.get(key).push(stale);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return staleByBlock;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
324
|
+
* @requirement REQ-HD-006.3, REQ-HD-006.4, REQ-HD-006.5
|
|
325
|
+
* Apply all stale removals per block in descending offset order.
|
|
326
|
+
* Returns updated prunedCount.
|
|
327
|
+
* @pseudocode high-density-optimize.md lines 332-359
|
|
328
|
+
*/
|
|
329
|
+
function applyStaleInclusionRemovals(staleByBlock, history, replacements, prunedCount) {
|
|
330
|
+
let count = prunedCount;
|
|
331
|
+
for (const [, stales] of staleByBlock) {
|
|
332
|
+
stales.sort((a, b) => b.startOffset - a.startOffset);
|
|
333
|
+
const { messageIndex, blockIndex } = stales[0];
|
|
334
|
+
const originalEntry = replacements.get(messageIndex) ?? history[messageIndex];
|
|
335
|
+
const originalBlock = originalEntry.blocks[blockIndex];
|
|
336
|
+
if (originalBlock.type !== 'text') {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
let newText = originalBlock.text;
|
|
340
|
+
for (const stale of stales) {
|
|
341
|
+
newText =
|
|
342
|
+
newText.substring(0, stale.startOffset) +
|
|
343
|
+
newText.substring(stale.endOffset);
|
|
344
|
+
count = count + 1;
|
|
345
|
+
}
|
|
346
|
+
newText = newText.replace(/\n{3,}/g, '\n\n');
|
|
347
|
+
const newBlocks = [...originalEntry.blocks];
|
|
348
|
+
newBlocks[blockIndex] = { type: 'text', text: newText };
|
|
349
|
+
replacements.set(messageIndex, {
|
|
350
|
+
...originalEntry,
|
|
351
|
+
blocks: newBlocks,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return count;
|
|
355
|
+
}
|
|
356
|
+
// ---------------------------------------------------------------------------
|
|
357
|
+
// HighDensityStrategy
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
/**
|
|
360
|
+
* @plan PLAN-20260211-HIGHDENSITY.P09
|
|
361
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
362
|
+
* @requirement REQ-HD-004.3
|
|
363
|
+
* @pseudocode high-density-optimize.md lines 20-53
|
|
364
|
+
*/
|
|
365
|
+
export class HighDensityStrategy {
|
|
366
|
+
name = 'high-density';
|
|
367
|
+
requiresLLM = false;
|
|
368
|
+
trigger = {
|
|
369
|
+
mode: 'continuous',
|
|
370
|
+
defaultThreshold: 0.6,
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
374
|
+
* @requirement REQ-HD-005.1, REQ-HD-006.1, REQ-HD-007.1, REQ-HD-013.7
|
|
375
|
+
* @pseudocode high-density-optimize.md lines 20-53
|
|
376
|
+
*/
|
|
377
|
+
optimize(history, config) {
|
|
378
|
+
const removals = new Set();
|
|
379
|
+
const replacements = new Map();
|
|
380
|
+
let readWritePairsPruned = 0;
|
|
381
|
+
let fileDeduplicationsPruned = 0;
|
|
382
|
+
let recencyPruned = 0;
|
|
383
|
+
// Phase 1: READ→WRITE pair pruning
|
|
384
|
+
if (config.readWritePruning) {
|
|
385
|
+
const rwResult = this.pruneReadWritePairs(history, config);
|
|
386
|
+
for (const idx of rwResult.removals) {
|
|
387
|
+
removals.add(idx);
|
|
388
|
+
}
|
|
389
|
+
for (const [idx, entry] of rwResult.replacements) {
|
|
390
|
+
if (!removals.has(idx)) {
|
|
391
|
+
replacements.set(idx, entry);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
readWritePairsPruned = rwResult.prunedCount;
|
|
395
|
+
}
|
|
396
|
+
// Phase 2: Duplicate @ file inclusion dedup
|
|
397
|
+
if (config.fileDedupe) {
|
|
398
|
+
const ddResult = this.deduplicateFileInclusions(history, config, removals);
|
|
399
|
+
for (const [idx, entry] of ddResult.replacements) {
|
|
400
|
+
if (!removals.has(idx)) {
|
|
401
|
+
replacements.set(idx, entry);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
fileDeduplicationsPruned = ddResult.prunedCount;
|
|
405
|
+
}
|
|
406
|
+
// Phase 3: Tool result recency pruning
|
|
407
|
+
// Apply prior replacements so recency pruning sees already-pruned entries
|
|
408
|
+
if (config.recencyPruning) {
|
|
409
|
+
const historyForRecency = history.map((entry, idx) => replacements.get(idx) ?? entry);
|
|
410
|
+
const rpResult = this.pruneByRecency(historyForRecency, config, removals);
|
|
411
|
+
for (const [idx, entry] of rpResult.replacements) {
|
|
412
|
+
if (!removals.has(idx)) {
|
|
413
|
+
replacements.set(idx, entry);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
recencyPruned = rpResult.prunedCount;
|
|
417
|
+
}
|
|
418
|
+
return {
|
|
419
|
+
removals: Array.from(removals),
|
|
420
|
+
replacements: replacements,
|
|
421
|
+
metadata: {
|
|
422
|
+
readWritePairsPruned,
|
|
423
|
+
fileDeduplicationsPruned,
|
|
424
|
+
recencyPruned,
|
|
425
|
+
},
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
430
|
+
* @requirement REQ-HD-008.1, REQ-HD-008.2, REQ-HD-008.3, REQ-HD-008.4, REQ-HD-008.5, REQ-HD-008.6
|
|
431
|
+
* @pseudocode high-density-compress.md lines 10-91
|
|
432
|
+
*
|
|
433
|
+
* Deterministic (no-LLM) compression:
|
|
434
|
+
* 1. Calculate tail size from preserveThreshold
|
|
435
|
+
* 2. Calculate target token budget
|
|
436
|
+
* 3. Summarize tool responses in the head
|
|
437
|
+
* 4. Truncate oldest entries if still over budget
|
|
438
|
+
* 5. Return CompressionResult with metadata
|
|
439
|
+
*/
|
|
440
|
+
async compress(context) {
|
|
441
|
+
const history = context.history;
|
|
442
|
+
const originalCount = history.length;
|
|
443
|
+
// Edge case: empty history
|
|
444
|
+
if (originalCount === 0) {
|
|
445
|
+
return {
|
|
446
|
+
newHistory: [],
|
|
447
|
+
metadata: this.buildMetadata(0, 0),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
// STEP 1: Calculate tail size from preserveThreshold (@pseudocode lines 21-27)
|
|
451
|
+
const preserveThreshold = context.runtimeContext.ephemerals.preserveThreshold();
|
|
452
|
+
const tailSize = Math.max(1, Math.floor(originalCount * preserveThreshold));
|
|
453
|
+
let tailStartIndex = originalCount - tailSize;
|
|
454
|
+
// Adjust for tool_call boundary — expand tail backward to avoid splitting pairs
|
|
455
|
+
tailStartIndex = this.adjustTailBoundary(history, tailStartIndex);
|
|
456
|
+
// If tail covers everything, return history unchanged (@pseudocode lines 29-34)
|
|
457
|
+
if (tailStartIndex <= 0) {
|
|
458
|
+
return {
|
|
459
|
+
newHistory: [...history],
|
|
460
|
+
metadata: this.buildMetadata(originalCount, originalCount),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
// STEP 2: Calculate target tokens (@pseudocode lines 36-39)
|
|
464
|
+
const compressionThreshold = context.runtimeContext.ephemerals.compressionThreshold();
|
|
465
|
+
const contextLimit = context.runtimeContext.ephemerals.contextLimit();
|
|
466
|
+
const compressHeadroom = context.runtimeContext.ephemerals.densityCompressHeadroom();
|
|
467
|
+
const targetTokens = Math.floor(compressionThreshold * contextLimit * compressHeadroom);
|
|
468
|
+
context.logger.debug(`[HighDensity compress] originalCount=${originalCount} tailStart=${tailStartIndex} target=${targetTokens}`);
|
|
469
|
+
// STEP 3: Build new history — process head + preserve tail (@pseudocode lines 48-74)
|
|
470
|
+
const newHistory = [];
|
|
471
|
+
// 3a: Process entries before tail
|
|
472
|
+
for (let i = 0; i < tailStartIndex; i++) {
|
|
473
|
+
const entry = history[i];
|
|
474
|
+
if (entry.speaker === 'human' || entry.speaker === 'ai') {
|
|
475
|
+
// Preserve human and AI entries intact (REQ-HD-008.4)
|
|
476
|
+
newHistory.push(entry);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
// Summarize tool response blocks (REQ-HD-008.3)
|
|
480
|
+
const summarized = this.summarizeToolResponseBlocks(entry, history);
|
|
481
|
+
newHistory.push(summarized);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
// 3b: Push tail entries intact (REQ-HD-008.2)
|
|
485
|
+
for (let i = tailStartIndex; i < originalCount; i++) {
|
|
486
|
+
newHistory.push(history[i]);
|
|
487
|
+
}
|
|
488
|
+
// STEP 4: Truncate if still over target (@pseudocode lines 76-85)
|
|
489
|
+
const finalHistory = await this.truncateToTarget(newHistory, newHistory.length - (originalCount - tailStartIndex), targetTokens, context);
|
|
490
|
+
// STEP 5: Return result (@pseudocode lines 87-91)
|
|
491
|
+
return {
|
|
492
|
+
newHistory: finalHistory,
|
|
493
|
+
metadata: this.buildMetadata(originalCount, finalHistory.length),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
// -------------------------------------------------------------------------
|
|
497
|
+
// Private helpers — compress phase
|
|
498
|
+
// -------------------------------------------------------------------------
|
|
499
|
+
/**
|
|
500
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
501
|
+
* @requirement REQ-HD-008.2
|
|
502
|
+
* @pseudocode high-density-compress.md line 27
|
|
503
|
+
*
|
|
504
|
+
* Adjust the tail boundary backward to avoid splitting tool_call/response
|
|
505
|
+
* pairs. If the boundary lands on a tool_response, move it back to include
|
|
506
|
+
* the preceding AI tool_call entry as well.
|
|
507
|
+
*/
|
|
508
|
+
adjustTailBoundary(history, index) {
|
|
509
|
+
if (index <= 0 || index >= history.length) {
|
|
510
|
+
return index;
|
|
511
|
+
}
|
|
512
|
+
// If the entry at index is a tool_response, move backward to include
|
|
513
|
+
// any preceding tool entries and their AI tool_call
|
|
514
|
+
while (index > 0 && history[index].speaker === 'tool') {
|
|
515
|
+
index--;
|
|
516
|
+
}
|
|
517
|
+
// If we landed on an AI entry with tool_calls, include it only if
|
|
518
|
+
// its tool responses are in the tail (to avoid orphaning the call)
|
|
519
|
+
if (index > 0 && history[index].speaker === 'ai') {
|
|
520
|
+
const toolCallIds = history[index].blocks
|
|
521
|
+
.filter((b) => b.type === 'tool_call')
|
|
522
|
+
.map((b) => b.id);
|
|
523
|
+
if (toolCallIds.length > 0) {
|
|
524
|
+
const tailHasResponses = toolCallIds.some((id) => history
|
|
525
|
+
.slice(index + 1)
|
|
526
|
+
.some((e) => e.speaker === 'tool' &&
|
|
527
|
+
e.blocks.some((b) => b.type === 'tool_response' && b.callId === id)));
|
|
528
|
+
if (!tailHasResponses) {
|
|
529
|
+
index++;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return index;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
537
|
+
* @requirement REQ-HD-008.3
|
|
538
|
+
* @pseudocode high-density-compress.md lines 100-112
|
|
539
|
+
*
|
|
540
|
+
* Replace tool_response block results with compact one-line summaries.
|
|
541
|
+
* Non-tool_response blocks are passed through unchanged.
|
|
542
|
+
*/
|
|
543
|
+
summarizeToolResponseBlocks(entry, fullHistory) {
|
|
544
|
+
const newBlocks = entry.blocks.map((block) => {
|
|
545
|
+
if (block.type !== 'tool_response') {
|
|
546
|
+
return block;
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
...block,
|
|
550
|
+
result: this.buildToolSummaryText(block, fullHistory),
|
|
551
|
+
};
|
|
552
|
+
});
|
|
553
|
+
return { ...entry, blocks: newBlocks };
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
557
|
+
* @requirement REQ-HD-008.3
|
|
558
|
+
* @pseudocode high-density-compress.md lines 120-149
|
|
559
|
+
*
|
|
560
|
+
* Build a compact summary string for a tool response block.
|
|
561
|
+
* Format: `[toolName keyParam: status]`
|
|
562
|
+
*/
|
|
563
|
+
buildToolSummaryText(block, fullHistory) {
|
|
564
|
+
const toolName = block.toolName;
|
|
565
|
+
// Determine status: error if error field present or result contains error indicators
|
|
566
|
+
const hasError = block.error !== undefined && block.error !== '';
|
|
567
|
+
const resultStr = typeof block.result === 'string'
|
|
568
|
+
? block.result
|
|
569
|
+
: JSON.stringify(block.result ?? '');
|
|
570
|
+
const looksLikeError = !hasError &&
|
|
571
|
+
(resultStr.toLowerCase().includes('error:') ||
|
|
572
|
+
resultStr.toLowerCase().includes('error occurred') ||
|
|
573
|
+
resultStr.toLowerCase().includes('command failed'));
|
|
574
|
+
const outcome = hasError || looksLikeError ? 'error' : 'success';
|
|
575
|
+
// Extract key param — look for matching tool_call by callId
|
|
576
|
+
let keyParam;
|
|
577
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
578
|
+
for (const entry of fullHistory) {
|
|
579
|
+
if (entry.speaker !== 'ai')
|
|
580
|
+
continue;
|
|
581
|
+
for (const b of entry.blocks) {
|
|
582
|
+
if (b.type === 'tool_call' && b.id === block.callId) {
|
|
583
|
+
const params = b.parameters;
|
|
584
|
+
keyParam = extractFilePath(params);
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (keyParam !== undefined)
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
if (keyParam) {
|
|
592
|
+
return `[${toolName} ${keyParam}: ${outcome} ${RERUN_HINT_SUFFIX}]`;
|
|
593
|
+
}
|
|
594
|
+
return `[${toolName}: ${outcome} ${RERUN_HINT_SUFFIX}]`;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
598
|
+
* @requirement REQ-HD-008.6
|
|
599
|
+
* @pseudocode high-density-compress.md lines 155-175
|
|
600
|
+
*
|
|
601
|
+
* Remove oldest head entries until the estimated token count is
|
|
602
|
+
* at or below the target. Never removes tail entries.
|
|
603
|
+
*/
|
|
604
|
+
async truncateToTarget(history, headEnd, targetTokens, context) {
|
|
605
|
+
let removeCount = 0;
|
|
606
|
+
let estimatedTokens = await context.estimateTokens(history);
|
|
607
|
+
while (estimatedTokens > targetTokens && removeCount < headEnd) {
|
|
608
|
+
removeCount++;
|
|
609
|
+
estimatedTokens = await context.estimateTokens(history.slice(removeCount));
|
|
610
|
+
}
|
|
611
|
+
return removeCount > 0 ? history.slice(removeCount) : history;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* @plan PLAN-20260211-HIGHDENSITY.P14
|
|
615
|
+
* @requirement REQ-HD-008.5
|
|
616
|
+
* @pseudocode high-density-compress.md lines 180-193
|
|
617
|
+
*/
|
|
618
|
+
buildMetadata(originalMessageCount, compressedMessageCount) {
|
|
619
|
+
return {
|
|
620
|
+
originalMessageCount,
|
|
621
|
+
compressedMessageCount,
|
|
622
|
+
strategyUsed: 'high-density',
|
|
623
|
+
llmCallMade: false,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
// -------------------------------------------------------------------------
|
|
627
|
+
// Private helpers — optimize phase
|
|
628
|
+
// -------------------------------------------------------------------------
|
|
629
|
+
/**
|
|
630
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
631
|
+
* @requirement REQ-HD-005.1, REQ-HD-005.2, REQ-HD-005.3, REQ-HD-005.6,
|
|
632
|
+
* REQ-HD-005.7, REQ-HD-005.8, REQ-HD-005.9
|
|
633
|
+
* @pseudocode high-density-optimize.md lines 60-209
|
|
634
|
+
*/
|
|
635
|
+
pruneReadWritePairs(history, config) {
|
|
636
|
+
const removals = new Set();
|
|
637
|
+
const replacements = new Map();
|
|
638
|
+
let prunedCount = 0;
|
|
639
|
+
const latestWrite = buildLatestWriteMap(history, config);
|
|
640
|
+
const { staleCallIds, aiEntryStaleBlocks, aiEntryTotalToolCalls } = identifyStaleReadCalls(history, config, latestWrite, this);
|
|
641
|
+
applyStaleAiEntries(history, staleCallIds, aiEntryStaleBlocks, aiEntryTotalToolCalls, removals, replacements);
|
|
642
|
+
prunedCount = applyStaleToolEntries(history, staleCallIds, removals, replacements);
|
|
643
|
+
return { removals, replacements, prunedCount };
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
647
|
+
* @requirement REQ-HD-005.9
|
|
648
|
+
* @pseudocode high-density-optimize.md lines 215-255
|
|
649
|
+
*/
|
|
650
|
+
canPruneReadManyFiles(params, workspaceRoot, latestWrite, readIndex) {
|
|
651
|
+
if (typeof params !== 'object' || params === null) {
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
const p = params;
|
|
655
|
+
const paths = p.paths;
|
|
656
|
+
if (!Array.isArray(paths)) {
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
let hasGlob = false;
|
|
660
|
+
let allConcreteHaveWrite = true;
|
|
661
|
+
let hasAnyConcrete = false;
|
|
662
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
663
|
+
for (const filePath of paths) {
|
|
664
|
+
if (typeof filePath !== 'string') {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (GLOB_CHARS.some((c) => filePath.includes(c))) {
|
|
668
|
+
hasGlob = true;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
hasAnyConcrete = true;
|
|
672
|
+
const resolved = resolvePath(filePath, workspaceRoot);
|
|
673
|
+
const writeIndex = latestWrite.get(resolved);
|
|
674
|
+
if (writeIndex === undefined || writeIndex <= readIndex) {
|
|
675
|
+
allConcreteHaveWrite = false;
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (hasGlob) {
|
|
680
|
+
return false;
|
|
681
|
+
}
|
|
682
|
+
if (!hasAnyConcrete) {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
return allConcreteHaveWrite;
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
689
|
+
* @requirement REQ-HD-006.1, REQ-HD-006.2, REQ-HD-006.3, REQ-HD-006.4, REQ-HD-006.5
|
|
690
|
+
* @pseudocode high-density-optimize.md lines 280-359
|
|
691
|
+
*/
|
|
692
|
+
deduplicateFileInclusions(history, config, existingRemovals) {
|
|
693
|
+
const replacements = new Map();
|
|
694
|
+
let prunedCount = 0;
|
|
695
|
+
const inclusions = scanFileInclusions(history, config, existingRemovals);
|
|
696
|
+
const staleByBlock = collectStaleInclusions(inclusions);
|
|
697
|
+
prunedCount = applyStaleInclusionRemovals(staleByBlock, history, replacements, prunedCount);
|
|
698
|
+
return { replacements, prunedCount };
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* @plan PLAN-20260211-HIGHDENSITY.P11
|
|
702
|
+
* @requirement REQ-HD-007.1, REQ-HD-007.2, REQ-HD-007.3, REQ-HD-007.4,
|
|
703
|
+
* REQ-HD-007.6, REQ-HD-013.6
|
|
704
|
+
* @pseudocode high-density-optimize.md lines 400-464
|
|
705
|
+
*/
|
|
706
|
+
pruneByRecency(history, config, existingRemovals) {
|
|
707
|
+
const replacements = new Map();
|
|
708
|
+
let prunedCount = 0;
|
|
709
|
+
const retention = Math.max(1, config.recencyRetention);
|
|
710
|
+
// STEP 1: Count tool responses per tool name, walking in reverse
|
|
711
|
+
const toolCounts = new Map();
|
|
712
|
+
const entriesToPrune = [];
|
|
713
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
714
|
+
for (let index = history.length - 1; index >= 0; index--) {
|
|
715
|
+
const entry = history[index];
|
|
716
|
+
if (entry.speaker !== 'tool') {
|
|
717
|
+
continue;
|
|
718
|
+
}
|
|
719
|
+
if (existingRemovals.has(index)) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
// eslint-disable-next-line sonarjs/too-many-break-or-continue-in-loop -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
|
|
723
|
+
for (let blockIndex = entry.blocks.length - 1; blockIndex >= 0; blockIndex--) {
|
|
724
|
+
const block = entry.blocks[blockIndex];
|
|
725
|
+
if (block.type !== 'tool_response') {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
// Skip already-pruned results to ensure idempotency
|
|
729
|
+
if (block.result === PRUNED_POINTER) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
const toolName = block.toolName;
|
|
733
|
+
let currentCount = toolCounts.get(toolName) ?? 0;
|
|
734
|
+
currentCount = currentCount + 1;
|
|
735
|
+
toolCounts.set(toolName, currentCount);
|
|
736
|
+
if (currentCount > retention) {
|
|
737
|
+
entriesToPrune.push({ index, blockIndex });
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// STEP 2: Build replacements — group by entry index
|
|
742
|
+
const grouped = new Map();
|
|
743
|
+
for (const { index, blockIndex } of entriesToPrune) {
|
|
744
|
+
if (!grouped.has(index)) {
|
|
745
|
+
grouped.set(index, new Set());
|
|
746
|
+
}
|
|
747
|
+
grouped.get(index).add(blockIndex);
|
|
748
|
+
}
|
|
749
|
+
for (const [entryIndex, blockIndices] of grouped) {
|
|
750
|
+
const entry = replacements.get(entryIndex) ?? history[entryIndex];
|
|
751
|
+
const newBlocks = entry.blocks.map((block, bi) => {
|
|
752
|
+
if (blockIndices.has(bi) && block.type === 'tool_response') {
|
|
753
|
+
return {
|
|
754
|
+
...block,
|
|
755
|
+
result: PRUNED_POINTER,
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return block;
|
|
759
|
+
});
|
|
760
|
+
replacements.set(entryIndex, {
|
|
761
|
+
...entry,
|
|
762
|
+
blocks: newBlocks,
|
|
763
|
+
});
|
|
764
|
+
prunedCount = prunedCount + blockIndices.size;
|
|
765
|
+
}
|
|
766
|
+
return { replacements, prunedCount };
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
//# sourceMappingURL=HighDensityStrategy.js.map
|