@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.
Files changed (176) hide show
  1. package/dist/.last_build +0 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +11 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/agents/executor-prompt-builder.d.ts +19 -0
  6. package/dist/src/agents/executor-prompt-builder.js +44 -0
  7. package/dist/src/agents/executor-prompt-builder.js.map +1 -0
  8. package/dist/src/agents/executor-termination.d.ts +22 -0
  9. package/dist/src/agents/executor-termination.js +44 -0
  10. package/dist/src/agents/executor-termination.js.map +1 -0
  11. package/dist/src/agents/executor-validation.d.ts +17 -0
  12. package/dist/src/agents/executor-validation.js +35 -0
  13. package/dist/src/agents/executor-validation.js.map +1 -0
  14. package/dist/src/agents/executor.d.ts +136 -0
  15. package/dist/src/agents/executor.js +874 -0
  16. package/dist/src/agents/executor.js.map +1 -0
  17. package/dist/src/agents/invocation.d.ts +45 -0
  18. package/dist/src/agents/invocation.js +118 -0
  19. package/dist/src/agents/invocation.js.map +1 -0
  20. package/dist/src/agents/recovery.d.ts +75 -0
  21. package/dist/src/agents/recovery.js +107 -0
  22. package/dist/src/agents/recovery.js.map +1 -0
  23. package/dist/src/agents/types.d.ts +149 -0
  24. package/dist/src/agents/types.js +18 -0
  25. package/dist/src/agents/types.js.map +1 -0
  26. package/dist/src/agents/utils.d.ts +15 -0
  27. package/dist/src/agents/utils.js +28 -0
  28. package/dist/src/agents/utils.js.map +1 -0
  29. package/dist/src/compression/CompressionHandler.d.ts +178 -0
  30. package/dist/src/compression/CompressionHandler.js +630 -0
  31. package/dist/src/compression/CompressionHandler.js.map +1 -0
  32. package/dist/src/compression/HighDensityStrategy.d.ts +113 -0
  33. package/dist/src/compression/HighDensityStrategy.js +769 -0
  34. package/dist/src/compression/HighDensityStrategy.js.map +1 -0
  35. package/dist/src/compression/MiddleOutStrategy.d.ts +27 -0
  36. package/dist/src/compression/MiddleOutStrategy.js +347 -0
  37. package/dist/src/compression/MiddleOutStrategy.js.map +1 -0
  38. package/dist/src/compression/OneShotStrategy.d.ts +22 -0
  39. package/dist/src/compression/OneShotStrategy.js +235 -0
  40. package/dist/src/compression/OneShotStrategy.js.map +1 -0
  41. package/dist/src/compression/TopDownTruncationStrategy.d.ts +13 -0
  42. package/dist/src/compression/TopDownTruncationStrategy.js +74 -0
  43. package/dist/src/compression/TopDownTruncationStrategy.js.map +1 -0
  44. package/dist/src/compression/compressionBudgeting.d.ts +34 -0
  45. package/dist/src/compression/compressionBudgeting.js +139 -0
  46. package/dist/src/compression/compressionBudgeting.js.map +1 -0
  47. package/dist/src/compression/compressionStrategyFactory.d.ts +23 -0
  48. package/dist/src/compression/compressionStrategyFactory.js +40 -0
  49. package/dist/src/compression/compressionStrategyFactory.js.map +1 -0
  50. package/dist/src/compression/index.d.ts +16 -0
  51. package/dist/src/compression/index.js +17 -0
  52. package/dist/src/compression/index.js.map +1 -0
  53. package/dist/src/compression/reasoningUtils.d.ts +18 -0
  54. package/dist/src/compression/reasoningUtils.js +22 -0
  55. package/dist/src/compression/reasoningUtils.js.map +1 -0
  56. package/dist/src/compression/utils.d.ts +114 -0
  57. package/dist/src/compression/utils.js +316 -0
  58. package/dist/src/compression/utils.js.map +1 -0
  59. package/dist/src/core/AgentHookManager.d.ts +41 -0
  60. package/dist/src/core/AgentHookManager.js +65 -0
  61. package/dist/src/core/AgentHookManager.js.map +1 -0
  62. package/dist/src/core/ChatSessionFactory.d.ts +50 -0
  63. package/dist/src/core/ChatSessionFactory.js +216 -0
  64. package/dist/src/core/ChatSessionFactory.js.map +1 -0
  65. package/dist/src/core/ConversationManager.d.ts +113 -0
  66. package/dist/src/core/ConversationManager.js +311 -0
  67. package/dist/src/core/ConversationManager.js.map +1 -0
  68. package/dist/src/core/DirectMessageProcessor.d.ts +79 -0
  69. package/dist/src/core/DirectMessageProcessor.js +478 -0
  70. package/dist/src/core/DirectMessageProcessor.js.map +1 -0
  71. package/dist/src/core/IdeContextTracker.d.ts +56 -0
  72. package/dist/src/core/IdeContextTracker.js +207 -0
  73. package/dist/src/core/IdeContextTracker.js.map +1 -0
  74. package/dist/src/core/MessageConverter.d.ts +78 -0
  75. package/dist/src/core/MessageConverter.js +492 -0
  76. package/dist/src/core/MessageConverter.js.map +1 -0
  77. package/dist/src/core/MessageStreamOrchestrator.d.ts +85 -0
  78. package/dist/src/core/MessageStreamOrchestrator.js +448 -0
  79. package/dist/src/core/MessageStreamOrchestrator.js.map +1 -0
  80. package/dist/src/core/MessageStreamTerminalHandler.d.ts +16 -0
  81. package/dist/src/core/MessageStreamTerminalHandler.js +170 -0
  82. package/dist/src/core/MessageStreamTerminalHandler.js.map +1 -0
  83. package/dist/src/core/StreamProcessor.d.ts +105 -0
  84. package/dist/src/core/StreamProcessor.js +660 -0
  85. package/dist/src/core/StreamProcessor.js.map +1 -0
  86. package/dist/src/core/TodoContinuationService.d.ts +74 -0
  87. package/dist/src/core/TodoContinuationService.js +312 -0
  88. package/dist/src/core/TodoContinuationService.js.map +1 -0
  89. package/dist/src/core/TurnProcessor.d.ts +91 -0
  90. package/dist/src/core/TurnProcessor.js +540 -0
  91. package/dist/src/core/TurnProcessor.js.map +1 -0
  92. package/dist/src/core/baseLlmClient.d.ts +115 -0
  93. package/dist/src/core/baseLlmClient.js +231 -0
  94. package/dist/src/core/baseLlmClient.js.map +1 -0
  95. package/dist/src/core/bucketFailoverIntegration.d.ts +62 -0
  96. package/dist/src/core/bucketFailoverIntegration.js +186 -0
  97. package/dist/src/core/bucketFailoverIntegration.js.map +1 -0
  98. package/dist/src/core/chatSession.d.ts +121 -0
  99. package/dist/src/core/chatSession.js +720 -0
  100. package/dist/src/core/chatSession.js.map +1 -0
  101. package/dist/src/core/client.d.ts +132 -0
  102. package/dist/src/core/client.js +524 -0
  103. package/dist/src/core/client.js.map +1 -0
  104. package/dist/src/core/clientHelpers.d.ts +15 -0
  105. package/dist/src/core/clientHelpers.js +122 -0
  106. package/dist/src/core/clientHelpers.js.map +1 -0
  107. package/dist/src/core/clientLlmUtilities.d.ts +30 -0
  108. package/dist/src/core/clientLlmUtilities.js +125 -0
  109. package/dist/src/core/clientLlmUtilities.js.map +1 -0
  110. package/dist/src/core/clientToolGovernance.d.ts +37 -0
  111. package/dist/src/core/clientToolGovernance.js +104 -0
  112. package/dist/src/core/clientToolGovernance.js.map +1 -0
  113. package/dist/src/core/compression-config.d.ts +12 -0
  114. package/dist/src/core/compression-config.js +21 -0
  115. package/dist/src/core/compression-config.js.map +1 -0
  116. package/dist/src/core/coreToolScheduler.d.ts +105 -0
  117. package/dist/src/core/coreToolScheduler.js +453 -0
  118. package/dist/src/core/coreToolScheduler.js.map +1 -0
  119. package/dist/src/core/hookToolRestrictions.d.ts +18 -0
  120. package/dist/src/core/hookToolRestrictions.js +187 -0
  121. package/dist/src/core/hookToolRestrictions.js.map +1 -0
  122. package/dist/src/core/nonInteractiveToolExecutor.d.ts +17 -0
  123. package/dist/src/core/nonInteractiveToolExecutor.js +102 -0
  124. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
  125. package/dist/src/core/subagent.d.ts +105 -0
  126. package/dist/src/core/subagent.js +650 -0
  127. package/dist/src/core/subagent.js.map +1 -0
  128. package/dist/src/core/subagentExecution.d.ts +104 -0
  129. package/dist/src/core/subagentExecution.js +275 -0
  130. package/dist/src/core/subagentExecution.js.map +1 -0
  131. package/dist/src/core/subagentOrchestrator.d.ts +84 -0
  132. package/dist/src/core/subagentOrchestrator.js +503 -0
  133. package/dist/src/core/subagentOrchestrator.js.map +1 -0
  134. package/dist/src/core/subagentRuntimeSetup.d.ts +75 -0
  135. package/dist/src/core/subagentRuntimeSetup.js +387 -0
  136. package/dist/src/core/subagentRuntimeSetup.js.map +1 -0
  137. package/dist/src/core/subagentScheduler.d.ts +17 -0
  138. package/dist/src/core/subagentScheduler.js +7 -0
  139. package/dist/src/core/subagentScheduler.js.map +1 -0
  140. package/dist/src/core/subagentToolProcessing.d.ts +51 -0
  141. package/dist/src/core/subagentToolProcessing.js +359 -0
  142. package/dist/src/core/subagentToolProcessing.js.map +1 -0
  143. package/dist/src/core/toolGovernance.d.ts +18 -0
  144. package/dist/src/core/toolGovernance.js +50 -0
  145. package/dist/src/core/toolGovernance.js.map +1 -0
  146. package/dist/src/core/turn.d.ts +47 -0
  147. package/dist/src/core/turn.js +455 -0
  148. package/dist/src/core/turn.js.map +1 -0
  149. package/dist/src/core/turnLogging.d.ts +35 -0
  150. package/dist/src/core/turnLogging.js +80 -0
  151. package/dist/src/core/turnLogging.js.map +1 -0
  152. package/dist/src/index.d.ts +32 -0
  153. package/dist/src/index.js +44 -0
  154. package/dist/src/index.js.map +1 -0
  155. package/dist/src/scheduler/confirmation-coordinator.d.ts +139 -0
  156. package/dist/src/scheduler/confirmation-coordinator.js +443 -0
  157. package/dist/src/scheduler/confirmation-coordinator.js.map +1 -0
  158. package/dist/src/scheduler/result-aggregator.d.ts +127 -0
  159. package/dist/src/scheduler/result-aggregator.js +302 -0
  160. package/dist/src/scheduler/result-aggregator.js.map +1 -0
  161. package/dist/src/scheduler/status-transitions.d.ts +39 -0
  162. package/dist/src/scheduler/status-transitions.js +184 -0
  163. package/dist/src/scheduler/status-transitions.js.map +1 -0
  164. package/dist/src/scheduler/tool-dispatcher.d.ts +40 -0
  165. package/dist/src/scheduler/tool-dispatcher.js +120 -0
  166. package/dist/src/scheduler/tool-dispatcher.js.map +1 -0
  167. package/dist/src/scheduler/tool-executor.d.ts +44 -0
  168. package/dist/src/scheduler/tool-executor.js +117 -0
  169. package/dist/src/scheduler/tool-executor.js.map +1 -0
  170. package/dist/src/scheduler/utils.d.ts +11 -0
  171. package/dist/src/scheduler/utils.js +19 -0
  172. package/dist/src/scheduler/utils.js.map +1 -0
  173. package/dist/src/tools/task.d.ts +128 -0
  174. package/dist/src/tools/task.js +932 -0
  175. package/dist/src/tools/task.js.map +1 -0
  176. 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