billion-context-pi 0.1.45 → 0.1.46

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.js CHANGED
@@ -4,204 +4,555 @@ var __export = (target, all) => {
4
4
  __defProp(target, name, { get: all[name], enumerable: true });
5
5
  };
6
6
 
7
- // node_modules/acp-kernel/dist/index.js
7
+ // node_modules/acp-kernel/dist/chunk-MWXUJVMN.js
8
8
  import { createRequire } from "module";
9
- var REF_WIDTH = 5;
10
- var MIN_INDEX = 1;
11
- var MAX_INDEX = 99999;
12
- var REF_PATTERN = /^m0*(\d{1,5})$/;
13
- var BLOCKED_REF = "BLOCKED";
14
- function indexToRef(index) {
15
- if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {
16
- throw new RangeError(
17
- `ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`
18
- );
19
- }
20
- return `m${String(index).padStart(REF_WIDTH, "0")}`;
21
- }
22
- function refToIndex(ref) {
23
- const match = REF_PATTERN.exec(ref.trim().toLowerCase());
24
- if (!match) return null;
25
- const index = Number(match[1]);
26
- if (index < MIN_INDEX || index > MAX_INDEX) return null;
27
- return index;
28
- }
29
- function refForRaw(map, rawId) {
30
- return map.byRaw[rawId] ?? null;
31
- }
32
- function assignRefs(messages, options) {
33
- const map = {
34
- byRaw: { ...options.existing.byRaw },
35
- byRef: { ...options.existing.byRef }
36
- };
37
- let cursor = Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX ? options.nextIndex : MIN_INDEX;
38
- let newlyAssigned = 0;
39
- for (const message of messages) {
40
- if (!message.id || options.shouldSkip?.(message)) continue;
41
- if (map.byRaw[message.id]) continue;
42
- if (options.isProtected?.(message)) {
43
- map.byRaw[message.id] = BLOCKED_REF;
44
- continue;
45
- }
46
- const ref = allocateFreeRef(map, cursor);
47
- cursor = ref.index + 1;
48
- map.byRaw[message.id] = ref.text;
49
- map.byRef[ref.text] = message.id;
50
- newlyAssigned++;
51
- }
52
- return { map, nextIndex: cursor, newlyAssigned };
9
+ var require2 = createRequire(import.meta.url);
10
+ function defaultCountTokens(text) {
11
+ if (!text) return 0;
12
+ const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
13
+ const cjkCount = cjk?.length ?? 0;
14
+ return cjkCount + Math.ceil((text.length - cjkCount) / 4);
53
15
  }
54
- function allocateFreeRef(map, start) {
55
- let candidate = Math.max(start, MIN_INDEX);
56
- while (candidate <= MAX_INDEX) {
57
- const text = indexToRef(candidate);
58
- if (!map.byRef[text]) {
59
- return { text, index: candidate };
16
+ var COMPRESS_PHILOSOPHY = `Compression Philosophy:
17
+ - All compression serves the primary task, but be frugal.
18
+ - Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
19
+ - Compress by need, not by percentage.
20
+ - Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
21
+ var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
22
+
23
+ When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
24
+
25
+ KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
26
+ - Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
27
+ - Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
28
+ - Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
29
+ - Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
30
+ - Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
31
+ - Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
32
+ - Exact values: versions, config keys, thresholds, magic numbers.
33
+ - User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
34
+ - The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
35
+ - Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
36
+ - Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
37
+ - Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
38
+
39
+ DROP \u2014 extract the signal, discard the vessel:
40
+ - Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
41
+ - Duplicate file reads once the needed content is recorded.
42
+ - Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
43
+ - Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
44
+ - Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
45
+ - Repeated status checks (\`git status\`, \`ls\`) once state is known.
46
+
47
+ For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
48
+
49
+ PRIORITY \u2014 when the summary must be compact, preserve in this order:
50
+ 1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
51
+ 2. Decisions and rationale.
52
+ 3. Exact technical artifacts: paths, signatures, errors, values.
53
+ 4. Conclusions and key findings.
54
+ 5. Lessons learned: what failed and why.
55
+
56
+ Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
57
+ var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
58
+
59
+ You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
60
+
61
+ KEEP \u2014 these are the only things that survive distillation:
62
+ - Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
63
+ - Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
64
+ - Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
65
+ - Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
66
+ - Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
67
+ - Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
68
+ - Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
69
+ - Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
70
+
71
+ DROP \u2014 these were useful during the work but are no longer needed:
72
+ - Exact line numbers, diffs, verbose function signatures, full code listings.
73
+ - Build/deploy process details, test execution steps.
74
+ - Review process details (who reviewed, what rounds, test counts).
75
+ - Verbose logs, command output, intermediate debugging steps.
76
+
77
+ FORMAT:
78
+ - Start each distilled block with a source header line:
79
+ \`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
80
+ Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
81
+ - 3-5 bullet points per source block, each a self-contained fact.
82
+ - Dense, scannable \u2014 no narrative prose.
83
+ - Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
84
+ - Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
85
+
86
+ SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
87
+ var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
88
+
89
+ You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
90
+
91
+ PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
92
+ 1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
93
+ 2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
94
+ 3. Key decisions with architectural impact ("chose X over Y because Z").
95
+ 4. Critical constraints ("must support Node 22").
96
+ Drop everything else. Tier 3 is a lookup index, not a knowledge base.
97
+
98
+ FORMAT:
99
+ - Start with a source header line:
100
+ \`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
101
+ - Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
102
+ - No explanations, no rationale, no process \u2014 just the fact.
103
+ - Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
104
+ - Merge related facts from different source blocks if they concern the same topic.
105
+
106
+ EXAMPLES:
107
+ - "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
108
+ - "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
109
+ - "Bug 1214 fixed \u2014 compress consumed all user messages"
110
+ - "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
111
+ - "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
112
+
113
+ DROP:
114
+ - Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
115
+ - Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
116
+ - Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
117
+ - Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
118
+
119
+ SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
120
+ var defaultPrompts = Object.freeze({
121
+ compressPhilosophy: COMPRESS_PHILOSOPHY,
122
+ howToCompressRules: HOW_TO_COMPRESS_RULES,
123
+ tier2DistillRules: TIER2_DISTILL_RULES,
124
+ tier3CondenseRules: TIER3_CONDENSE_RULES
125
+ });
126
+ function resolvePrompts(overrides, options = {}) {
127
+ const clean = {};
128
+ if (overrides) {
129
+ for (const [key, value] of Object.entries(overrides)) {
130
+ if (typeof value === "string") {
131
+ clean[key] = value;
132
+ }
60
133
  }
61
- candidate++;
62
134
  }
63
- throw new Error(
64
- `ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`
65
- );
66
- }
67
- function highestUsedIndex(map) {
68
- let highest = 0;
69
- for (const ref of Object.values(map.byRaw)) {
70
- const index = ref === BLOCKED_REF ? null : refToIndex(ref);
71
- if (index !== null && index > highest) highest = index;
135
+ const keys = Object.keys(clean);
136
+ if (keys.length > 0 && !options.acknowledgeRisk) {
137
+ throw new Error(
138
+ `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. Overridden keys: ${keys.join(", ")}. These rules are quality-critical (tuned over months of production use); changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`
139
+ );
72
140
  }
73
- return highest;
74
- }
75
- function createInitialState() {
76
- return {
77
- blocks: [],
78
- messageRefs: { byRaw: {}, byRef: {} },
79
- tokenSnapshot: {},
80
- nudge: {
81
- lastPerMessageNudgeTokens: 0,
82
- lastNudgeShownTokens: 0,
83
- baselineTokens: 0,
84
- anchors: {},
85
- lastShownByTier: {}
86
- },
87
- stats: { tokensCompressed: 0, compressionCount: 0 },
88
- nextBlockId: 1,
89
- nextRunId: 1
90
- };
141
+ return { ...defaultPrompts, ...clean };
91
142
  }
92
- function allocateBlockId(state) {
93
- const id = state.nextBlockId;
94
- state.nextBlockId = Math.max(1, id) + 1;
95
- return `b${id}`;
143
+ function efficiencyNote(prompts) {
144
+ return `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
145
+
146
+ ${prompts.compressPhilosophy}`;
96
147
  }
97
- function allocateRunId(state) {
98
- const id = state.nextRunId;
99
- state.nextRunId = Math.max(1, id) + 1;
100
- return `r${id}`;
148
+ function emergencyHeader(prompts) {
149
+ return `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
150
+
151
+ ${prompts.compressPhilosophy}`;
101
152
  }
102
- function blockById(state, blockId) {
103
- return state.blocks.find((block) => block.blockId === blockId);
153
+ function formatK(n) {
154
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
155
+ return `${n}`;
104
156
  }
105
- function activeBlocks(state) {
106
- return state.blocks.filter((block) => block.active);
157
+ function formatBreakdown(bd) {
158
+ if (!bd) return "";
159
+ const parts = [];
160
+ if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);
161
+ if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);
162
+ if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);
163
+ if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);
164
+ if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);
165
+ const growth = bd.growth > 0 ? `
166
+ +${formatK(bd.growth)} since last nudge` : "";
167
+ return `Context breakdown: ${parts.join(" | ")}${growth}`;
107
168
  }
108
- function coveredMessageIds(state) {
109
- const covered = /* @__PURE__ */ new Set();
110
- for (const block of state.blocks) {
111
- if (!block.active) continue;
112
- for (const id of block.effectiveMessageIds) covered.add(id);
169
+ function formatTierTargetBlocks(blocks) {
170
+ if (blocks.length === 0) {
171
+ return "Target blocks: (none \u2014 no tier blocks found)";
113
172
  }
114
- return covered;
115
- }
116
- function advanceSurvival(state, promotionThreshold) {
117
- for (const block of state.blocks) {
118
- if (!block.active) continue;
119
- block.survivedCount += 1;
120
- if (block.survivedCount >= promotionThreshold) {
121
- block.generation = "old";
122
- }
123
- }
124
- }
125
- var SUMMARY_HEADER = "[Compressed conversation section]";
126
- function prune(messages, state, options = {}) {
127
- const covered = coveredMessageIds(state);
128
- if (covered.size === 0) return [...messages];
129
- const inject = options.injectSummaries ?? true;
130
- const firstUserIndex = messages.findIndex(
131
- (message) => message.role === "user"
132
- );
133
- const indexById = /* @__PURE__ */ new Map();
134
- messages.forEach((message, index) => indexById.set(message.id, index));
135
- const anchors = inject ? collectSummaryAnchors(state, indexById) : [];
136
- return stripOrphanedReasoning(
137
- stripOrphanedToolResults(
138
- stripOrphanedToolCalls(
139
- rebuildMessages(messages, covered, firstUserIndex, anchors)
140
- )
141
- )
142
- );
173
+ const lines = blocks.map((b) => {
174
+ const summaryTokens = Math.ceil((b.summary ?? "").length / 4);
175
+ const topic = b.topic ? ` "${b.topic}"` : "";
176
+ return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}\u2192${formatK(summaryTokens)}${topic}`;
177
+ });
178
+ return `Target ${blocks[0].tier === 1 ? "tier-1" : "tier-2"} blocks to distill (${blocks.length}):
179
+ ${lines.join("\n")}`;
143
180
  }
144
- function collectSummaryAnchors(state, indexById) {
145
- const anchors = [];
146
- for (const block of activeBlocks(state)) {
147
- let earliest = null;
148
- for (const id of block.effectiveMessageIds) {
149
- const index = indexById.get(id);
150
- if (index !== void 0 && (earliest === null || index < earliest)) {
151
- earliest = index;
181
+ function formatRanges(compressible, protectedRanges) {
182
+ if (compressible.length === 0 && protectedRanges.length === 0) {
183
+ return "[No specific ranges detected \u2014 compress any consumed content.]";
184
+ }
185
+ const refNum2 = (ref) => {
186
+ const m = ref.match(/\d+/);
187
+ return m ? parseInt(m[0], 10) : 0;
188
+ };
189
+ const entries = [];
190
+ for (const r of compressible) {
191
+ entries.push({
192
+ startRef: r.startRef,
193
+ endRef: r.endRef,
194
+ startNum: refNum2(r.startRef),
195
+ endNum: refNum2(r.endRef),
196
+ count: r.count,
197
+ tokens: r.tokens,
198
+ toolPct: r.toolPct,
199
+ textPct: r.textPct,
200
+ compressibleTokens: r.tokens,
201
+ compressibleCount: r.count,
202
+ protectedTokens: 0,
203
+ protectedCount: 0,
204
+ protectedTools: [],
205
+ dangerous: r.dangerous ?? false
206
+ });
207
+ }
208
+ for (const r of protectedRanges) {
209
+ entries.push({
210
+ startRef: r.startRef,
211
+ endRef: r.endRef,
212
+ startNum: refNum2(r.startRef),
213
+ endNum: refNum2(r.endRef),
214
+ count: r.count,
215
+ tokens: r.tokens,
216
+ toolPct: 0,
217
+ textPct: 0,
218
+ compressibleTokens: 0,
219
+ compressibleCount: 0,
220
+ protectedTokens: r.tokens,
221
+ protectedCount: r.count,
222
+ protectedTools: [...r.tools],
223
+ dangerous: false
224
+ });
225
+ }
226
+ entries.sort((a, b) => a.startNum - b.startNum);
227
+ const merged = [];
228
+ for (const e of entries) {
229
+ const last = merged[merged.length - 1];
230
+ if (last && e.startNum <= last.endNum + 1) {
231
+ last.endRef = e.endRef;
232
+ last.endNum = Math.max(last.endNum, e.endNum);
233
+ last.count += e.count;
234
+ last.tokens += e.tokens;
235
+ last.compressibleTokens += e.compressibleTokens;
236
+ last.compressibleCount += e.compressibleCount;
237
+ last.protectedTokens += e.protectedTokens;
238
+ last.protectedCount += e.protectedCount;
239
+ if (e.dangerous) last.dangerous = true;
240
+ for (const t of e.protectedTools) {
241
+ if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
152
242
  }
243
+ } else {
244
+ merged.push({ ...e });
153
245
  }
154
- anchors.push({
155
- blockId: block.blockId,
156
- summary: block.summary,
157
- topic: block.topic,
158
- insertAt: earliest ?? 0
159
- });
160
246
  }
161
- anchors.sort((left, right) => left.insertAt - right.insertAt);
162
- return anchors;
163
- }
164
- function rebuildMessages(messages, covered, firstUserIndex, anchors) {
165
- const result = [];
166
- const pending = [...anchors];
167
- for (let index = 0; index < messages.length; index++) {
168
- while (pending.length > 0 && pending[0].insertAt === index) {
169
- result.push(renderSummary(pending.shift()));
247
+ const lines = merged.map((e) => {
248
+ const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
249
+ if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
250
+ return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
170
251
  }
171
- if (index === firstUserIndex && firstUserIndex >= 0) {
172
- result.push(messages[index]);
173
- continue;
252
+ if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
253
+ return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
174
254
  }
175
- if (covered.has(messages[index].id)) continue;
176
- result.push(messages[index]);
255
+ return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
256
+ });
257
+ return `Compressible ranges (${merged.length}, oldest first):
258
+ ${lines.join("\n")}`;
259
+ }
260
+ function renderNudgeText(decision, prompts = defaultPrompts) {
261
+ const breakdownStr = formatBreakdown(decision.contextBreakdown);
262
+ const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
263
+ const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
264
+ if (decision.tier !== null && decision.tier >= 2) {
265
+ const isT2 = decision.tier === 2;
266
+ const targets = decision.tierTargetBlocks ?? [];
267
+ const blockList = formatTierTargetBlocks(targets);
268
+ const startId = targets[0]?.blockId ?? "b1";
269
+ const endId = targets[targets.length - 1]?.blockId ?? "b5";
270
+ const voice = isEmergency ? "emergency" : "gentle";
271
+ const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
272
+ return {
273
+ voice,
274
+ text: [
275
+ efficiencyNote(prompts),
276
+ "",
277
+ breakdownStr,
278
+ "",
279
+ triggerLine,
280
+ isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
281
+ blockList,
282
+ `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
283
+ "",
284
+ prompts.howToCompressRules,
285
+ "",
286
+ isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
287
+ ].join("\n")
288
+ };
177
289
  }
178
- while (pending.length > 0) {
179
- result.push(renderSummary(pending.shift()));
290
+ if (isEmergency) {
291
+ return {
292
+ voice: "emergency",
293
+ text: [
294
+ emergencyHeader(prompts),
295
+ "",
296
+ breakdownStr,
297
+ "",
298
+ prompts.howToCompressRules,
299
+ "",
300
+ `{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
301
+ "Only use IDs from visible messages above. Compress older work first.",
302
+ "",
303
+ rangesStr
304
+ ].join("\n")
305
+ };
180
306
  }
181
- return result;
307
+ return {
308
+ voice: "gentle",
309
+ text: [
310
+ efficiencyNote(prompts),
311
+ "",
312
+ breakdownStr,
313
+ "",
314
+ prompts.howToCompressRules,
315
+ "",
316
+ rangesStr,
317
+ "",
318
+ `\u{1F4A1} Compress all ranges in one call (pass multiple content entries: \`content: [{...}, {...}]\`).`
319
+ ].join("\n")
320
+ };
182
321
  }
183
- function renderSummary(anchor) {
184
- const body = anchor.summary.trim();
185
- const topicLine = anchor.topic ? `${SUMMARY_HEADER} \u2014 ${anchor.topic}` : SUMMARY_HEADER;
186
- const text = body.length === 0 ? topicLine : `${topicLine}
187
- ${body}`;
322
+ var VIABLE_RANGE_MIN_TOKENS = 200;
323
+ function viableRanges(ranges) {
324
+ return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
325
+ }
326
+
327
+ // node_modules/acp-kernel/dist/chunk-DPH62BGM.js
328
+ function createInitialState() {
188
329
  return {
189
- id: `acp_summary_${anchor.blockId}`,
190
- role: "system",
191
- contentType: "text",
192
- text
330
+ blocks: [],
331
+ messageRefs: { byRaw: {}, byRef: {} },
332
+ tokenSnapshot: {},
333
+ nudge: {
334
+ lastPerMessageNudgeTokens: 0,
335
+ lastNudgeShownTokens: 0,
336
+ baselineTokens: 0,
337
+ anchors: {},
338
+ lastShownByTier: {}
339
+ },
340
+ stats: { tokensCompressed: 0, compressionCount: 0 },
341
+ nextBlockId: 1,
342
+ nextRunId: 1
193
343
  };
194
344
  }
195
- function stripOrphanedToolResults(messages) {
196
- const knownCallIds = /* @__PURE__ */ new Set();
197
- for (const m of messages) {
198
- if (m.contentType === "tool-call" && m.toolCallId) {
199
- knownCallIds.add(m.toolCallId);
345
+ function allocateBlockId(state) {
346
+ const id = state.nextBlockId;
347
+ state.nextBlockId = Math.max(1, id) + 1;
348
+ return `b${id}`;
349
+ }
350
+ function allocateRunId(state) {
351
+ const id = state.nextRunId;
352
+ state.nextRunId = Math.max(1, id) + 1;
353
+ return `r${id}`;
354
+ }
355
+ function blockById(state, blockId) {
356
+ return state.blocks.find((block) => block.blockId === blockId);
357
+ }
358
+ function activeBlocks(state) {
359
+ return state.blocks.filter((block) => block.active);
360
+ }
361
+ function coveredMessageIds(state) {
362
+ const covered = /* @__PURE__ */ new Set();
363
+ for (const block of state.blocks) {
364
+ if (!block.active) continue;
365
+ for (const id of block.effectiveMessageIds) covered.add(id);
366
+ }
367
+ return covered;
368
+ }
369
+ function advanceSurvival(state, promotionThreshold) {
370
+ for (const block of state.blocks) {
371
+ if (!block.active) continue;
372
+ block.survivedCount += 1;
373
+ if (block.survivedCount >= promotionThreshold) {
374
+ block.generation = "old";
200
375
  }
201
376
  }
202
- return messages.filter(
203
- (m) => m.contentType !== "tool-result" || !m.toolCallId || knownCallIds.has(m.toolCallId)
204
- );
377
+ }
378
+
379
+ // node_modules/acp-kernel/dist/index.js
380
+ var REF_WIDTH = 5;
381
+ var MIN_INDEX = 1;
382
+ var MAX_INDEX = 99999;
383
+ var REF_PATTERN = /^m0*(\d{1,5})$/;
384
+ var BLOCKED_REF = "BLOCKED";
385
+ function indexToRef(index) {
386
+ if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {
387
+ throw new RangeError(
388
+ `ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`
389
+ );
390
+ }
391
+ return `m${String(index).padStart(REF_WIDTH, "0")}`;
392
+ }
393
+ function refToIndex(ref) {
394
+ const match = REF_PATTERN.exec(ref.trim().toLowerCase());
395
+ if (!match) return null;
396
+ const index = Number(match[1]);
397
+ if (index < MIN_INDEX || index > MAX_INDEX) return null;
398
+ return index;
399
+ }
400
+ function refForRaw(map, rawId) {
401
+ return map.byRaw[rawId] ?? null;
402
+ }
403
+ function assignRefs(messages, options) {
404
+ const map = {
405
+ byRaw: { ...options.existing.byRaw },
406
+ byRef: { ...options.existing.byRef }
407
+ };
408
+ let cursor = Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX ? options.nextIndex : MIN_INDEX;
409
+ let newlyAssigned = 0;
410
+ for (const message of messages) {
411
+ if (!message.id || options.shouldSkip?.(message)) continue;
412
+ if (map.byRaw[message.id]) continue;
413
+ if (options.isProtected?.(message)) {
414
+ map.byRaw[message.id] = BLOCKED_REF;
415
+ continue;
416
+ }
417
+ const ref = allocateFreeRef(map, cursor);
418
+ cursor = ref.index + 1;
419
+ map.byRaw[message.id] = ref.text;
420
+ map.byRef[ref.text] = message.id;
421
+ newlyAssigned++;
422
+ }
423
+ return { map, nextIndex: cursor, newlyAssigned };
424
+ }
425
+ function allocateFreeRef(map, start) {
426
+ let candidate = Math.max(start, MIN_INDEX);
427
+ while (candidate <= MAX_INDEX) {
428
+ const text = indexToRef(candidate);
429
+ if (!map.byRef[text]) {
430
+ return { text, index: candidate };
431
+ }
432
+ candidate++;
433
+ }
434
+ throw new Error(
435
+ `ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`
436
+ );
437
+ }
438
+ function highestUsedIndex(map) {
439
+ let highest = 0;
440
+ for (const ref of Object.values(map.byRaw)) {
441
+ const index = ref === BLOCKED_REF ? null : refToIndex(ref);
442
+ if (index !== null && index > highest) highest = index;
443
+ }
444
+ return highest;
445
+ }
446
+ var SUMMARY_HEADER = "[Compressed conversation section]";
447
+ var SUMMARY_ID_PREFIX = "acp_summary_";
448
+ function summaryMessageId(blockId) {
449
+ return `${SUMMARY_ID_PREFIX}${blockId}`;
450
+ }
451
+ function isSummaryMessageId(id) {
452
+ return id.startsWith(SUMMARY_ID_PREFIX);
453
+ }
454
+ function isRenderedSummaryMessage(message) {
455
+ return isSummaryMessageId(message.id) && message.role === "system" && message.contentType === "text";
456
+ }
457
+ function prune(messages, state, options = {}) {
458
+ const covered = coveredMessageIds(state);
459
+ if (covered.size === 0) return [...messages];
460
+ const inject = options.injectSummaries ?? true;
461
+ const firstUserIndex = messages.findIndex(
462
+ (message) => message.role === "user"
463
+ );
464
+ const indexById = /* @__PURE__ */ new Map();
465
+ const summaryIndexById = /* @__PURE__ */ new Map();
466
+ messages.forEach((message, index) => {
467
+ indexById.set(message.id, index);
468
+ if (isRenderedSummaryMessage(message))
469
+ summaryIndexById.set(message.id, index);
470
+ });
471
+ const anchors = inject ? collectSummaryAnchors(state, indexById, summaryIndexById) : [];
472
+ return stripOrphanedReasoning(
473
+ stripOrphanedToolResults(
474
+ stripOrphanedToolCalls(
475
+ rebuildMessages(messages, covered, firstUserIndex, anchors)
476
+ )
477
+ )
478
+ );
479
+ }
480
+ function collectSummaryAnchors(state, indexById, summaryIndexById) {
481
+ const anchors = [];
482
+ for (const block of activeBlocks(state)) {
483
+ const existingIndex = summaryIndexById.get(summaryMessageId(block.blockId));
484
+ if (existingIndex !== void 0) {
485
+ anchors.push({
486
+ blockId: block.blockId,
487
+ summary: block.summary,
488
+ topic: block.topic,
489
+ insertAt: existingIndex
490
+ });
491
+ continue;
492
+ }
493
+ let earliest = null;
494
+ for (const id of block.effectiveMessageIds) {
495
+ const index = indexById.get(id);
496
+ if (index !== void 0 && (earliest === null || index < earliest)) {
497
+ earliest = index;
498
+ }
499
+ }
500
+ anchors.push({
501
+ blockId: block.blockId,
502
+ summary: block.summary,
503
+ topic: block.topic,
504
+ insertAt: earliest ?? 0
505
+ });
506
+ }
507
+ anchors.sort((left, right) => left.insertAt - right.insertAt);
508
+ return anchors;
509
+ }
510
+ function rebuildMessages(messages, covered, firstUserIndex, anchors) {
511
+ const result = [];
512
+ const pending = [...anchors];
513
+ const anchoredSummaryIds = new Set(
514
+ anchors.map((anchor) => summaryMessageId(anchor.blockId))
515
+ );
516
+ for (let index = 0; index < messages.length; index++) {
517
+ while (pending.length > 0 && pending[0].insertAt === index) {
518
+ result.push(renderSummary(pending.shift()));
519
+ }
520
+ if (index === firstUserIndex && firstUserIndex >= 0) {
521
+ result.push(messages[index]);
522
+ continue;
523
+ }
524
+ if (covered.has(messages[index].id)) continue;
525
+ if (isRenderedSummaryMessage(messages[index]) && anchoredSummaryIds.has(messages[index].id))
526
+ continue;
527
+ result.push(messages[index]);
528
+ }
529
+ while (pending.length > 0) {
530
+ result.push(renderSummary(pending.shift()));
531
+ }
532
+ return result;
533
+ }
534
+ function renderSummary(anchor) {
535
+ const body = anchor.summary.trim();
536
+ const topicLine = anchor.topic ? `${SUMMARY_HEADER} \u2014 ${anchor.topic}` : SUMMARY_HEADER;
537
+ const text = body.length === 0 ? topicLine : `${topicLine}
538
+ ${body}`;
539
+ return {
540
+ id: summaryMessageId(anchor.blockId),
541
+ role: "system",
542
+ contentType: "text",
543
+ text
544
+ };
545
+ }
546
+ function stripOrphanedToolResults(messages) {
547
+ const knownCallIds = /* @__PURE__ */ new Set();
548
+ for (const m of messages) {
549
+ if (m.contentType === "tool-call" && m.toolCallId) {
550
+ knownCallIds.add(m.toolCallId);
551
+ }
552
+ }
553
+ return messages.filter(
554
+ (m) => m.contentType !== "tool-result" || !m.toolCallId || knownCallIds.has(m.toolCallId)
555
+ );
205
556
  }
206
557
  function stripOrphanedToolCalls(messages) {
207
558
  const knownResultIds = /* @__PURE__ */ new Set();
@@ -275,9 +626,7 @@ function syncBlocks(messages, state) {
275
626
  continue;
276
627
  }
277
628
  block.active = true;
278
- const stillPresent = block.effectiveMessageIds.some(
279
- (id) => presentIds.has(id)
280
- );
629
+ const stillPresent = block.effectiveMessageIds.some((id) => presentIds.has(id)) || presentIds.has(summaryMessageId(block.blockId));
281
630
  if (!stillPresent) {
282
631
  block.active = false;
283
632
  deactivated.push(block.blockId);
@@ -285,13 +634,6 @@ function syncBlocks(messages, state) {
285
634
  }
286
635
  return { state: result, deactivated };
287
636
  }
288
- var require2 = createRequire(import.meta.url);
289
- function defaultCountTokens(text) {
290
- if (!text) return 0;
291
- const cjk = text.match(/[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]/g);
292
- const cjkCount = cjk?.length ?? 0;
293
- return cjkCount + Math.ceil((text.length - cjkCount) / 4);
294
- }
295
637
  function defaultConfig(modelContextLimit, overrides = {}) {
296
638
  const base = {
297
639
  tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },
@@ -397,14 +739,24 @@ function resolveBoundaries(input) {
397
739
  `Invalid boundary ref(s): startId="${input.startRef}", endId="${input.endRef}". Use mNNNNN or bN.`
398
740
  );
399
741
  }
400
- const indexByRawId = /* @__PURE__ */ new Map();
742
+ const indexByMessageId = /* @__PURE__ */ new Map();
401
743
  input.messages.forEach(
402
- (message, index) => indexByRawId.set(message.id, index)
744
+ (message, index) => indexByMessageId.set(message.id, index)
403
745
  );
404
746
  let snappedBoundaries = [];
405
- const startAnchor = resolveAnchorIndex(start, input.state, indexByRawId, "start");
747
+ const startAnchor = resolveAnchorIndex(
748
+ start,
749
+ input.state,
750
+ indexByMessageId,
751
+ "start"
752
+ );
406
753
  if (startAnchor.snapped) snappedBoundaries.push(startAnchor.snapped);
407
- const endAnchor = resolveAnchorIndex(end, input.state, indexByRawId, "end");
754
+ const endAnchor = resolveAnchorIndex(
755
+ end,
756
+ input.state,
757
+ indexByMessageId,
758
+ "end"
759
+ );
408
760
  if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
409
761
  let startIndex = startAnchor.index;
410
762
  let endIndex = endAnchor.index;
@@ -414,14 +766,14 @@ function resolveBoundaries(input) {
414
766
  const messageIds = [];
415
767
  for (let index = startIndex; index <= endIndex; index++) {
416
768
  const message = input.messages[index];
417
- if (message) messageIds.push(message.id);
769
+ if (message && !isRenderedSummaryMessage(message))
770
+ messageIds.push(message.id);
418
771
  }
419
772
  const boundaryKind = start.kind === "block" || end.kind === "block" ? "block" : "message";
420
773
  const nestedBlockIds = [];
421
774
  const nestedSeen = /* @__PURE__ */ new Set();
422
775
  for (const block of activeBlocks(input.state)) {
423
- const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
424
- if (anchor !== null && anchor >= startIndex && anchor <= endIndex) {
776
+ if (blockVisibleInRange(block, indexByMessageId, startIndex, endIndex)) {
425
777
  if (!nestedSeen.has(block.blockId)) {
426
778
  nestedSeen.add(block.blockId);
427
779
  nestedBlockIds.push(block.blockId);
@@ -439,7 +791,7 @@ function resolveBoundaries(input) {
439
791
  snappedBoundaries
440
792
  };
441
793
  }
442
- function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
794
+ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
443
795
  const label = endpoint === "start" ? "startId" : "endId";
444
796
  if (boundary.kind === "message") {
445
797
  const rawId = state.messageRefs.byRef[boundary.raw] ?? state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];
@@ -450,15 +802,15 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
450
802
  `${label}="${boundary.raw}" does not exist in this session (typo or wrong session) \u2014 run acp_status for current refs.`
451
803
  );
452
804
  }
453
- const index = indexByRawId.get(rawId);
805
+ const index = indexByMessageId.get(rawId);
454
806
  if (index !== void 0) {
455
807
  return { index, snapped: null };
456
808
  }
457
- const owner2 = activeOwnerAnchor(state, [rawId], indexByRawId);
809
+ const owner2 = activeOwnerAnchor(state, [rawId], indexByMessageId);
458
810
  if (owner2 !== null) {
459
811
  return {
460
812
  index: owner2,
461
- snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to that block's summary instead.`
813
+ snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
462
814
  };
463
815
  }
464
816
  throw new BoundaryNotFoundError(
@@ -476,12 +828,16 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
476
828
  );
477
829
  }
478
830
  if (block.active) {
479
- const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
831
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
480
832
  if (anchor !== null) {
481
833
  return { index: anchor, snapped: null };
482
834
  }
483
835
  }
484
- const owner = activeOwnerAnchor(state, block.effectiveMessageIds, indexByRawId);
836
+ const owner = activeOwnerAnchor(
837
+ state,
838
+ block.effectiveMessageIds,
839
+ indexByMessageId
840
+ );
485
841
  if (owner !== null) {
486
842
  return {
487
843
  index: owner,
@@ -498,31 +854,64 @@ function resolveAnchorIndex(boundary, state, indexByRawId, endpoint) {
498
854
  throw new BoundaryNotFoundError(
499
855
  "consumed",
500
856
  endpoint,
501
- `${label}="b${boundary.numericId}" not found in visible context (block messages consumed by a higher-tier block).`
857
+ `${label}="b${boundary.numericId}" is an active block but none of its content (raw messages or rendered summary) is visible in the current context \u2014 run acp_status to verify.`
502
858
  );
503
859
  }
504
- function activeOwnerAnchor(state, ownedIds, indexByRawId) {
860
+ function activeOwnerAnchor(state, ownedIds, indexByMessageId) {
505
861
  if (ownedIds.length === 0) return null;
506
862
  const owned = new Set(ownedIds);
507
863
  let best = null;
508
864
  for (const block of state.blocks) {
509
865
  if (!block.active) continue;
510
- const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);
866
+ const inherited = inheritedContentIds(state, block);
867
+ let ownsInherited = false;
868
+ for (const id of owned) {
869
+ if (inherited.has(id)) {
870
+ ownsInherited = true;
871
+ break;
872
+ }
873
+ }
874
+ if (!ownsInherited) continue;
875
+ const anchor = visibleBlockAnchor(block, indexByMessageId);
511
876
  if (anchor === null) continue;
512
- const ownsContent = block.effectiveMessageIds.some((id) => owned.has(id));
513
- if (ownsContent && (best === null || anchor < best)) {
877
+ if (best === null || anchor < best) {
514
878
  best = anchor;
515
879
  }
516
880
  }
517
881
  return best;
518
882
  }
883
+ function inheritedContentIds(state, block) {
884
+ const ids = /* @__PURE__ */ new Set();
885
+ for (const childId of block.directBlockIds) {
886
+ const child = blockById(state, childId);
887
+ if (!child) continue;
888
+ for (const id of child.effectiveMessageIds) ids.add(id);
889
+ }
890
+ return ids;
891
+ }
519
892
  function formatPaddedRef(index) {
520
893
  return `m${String(index).padStart(5, "0")}`;
521
894
  }
522
- function earliestIndexOfIds(ids, indexByRawId) {
895
+ function visibleBlockAnchor(block, indexByMessageId) {
896
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
897
+ if (summaryIndex !== void 0) return summaryIndex;
898
+ return earliestIndexOfIds(block.effectiveMessageIds, indexByMessageId);
899
+ }
900
+ function blockVisibleInRange(block, indexByMessageId, startIndex, endIndex) {
901
+ const summaryIndex = indexByMessageId.get(summaryMessageId(block.blockId));
902
+ if (summaryIndex !== void 0 && summaryIndex >= startIndex && summaryIndex <= endIndex) {
903
+ return true;
904
+ }
905
+ const rawIndex = earliestIndexOfIds(
906
+ block.effectiveMessageIds,
907
+ indexByMessageId
908
+ );
909
+ return rawIndex !== null && rawIndex >= startIndex && rawIndex <= endIndex;
910
+ }
911
+ function earliestIndexOfIds(ids, indexByMessageId) {
523
912
  let earliest = null;
524
913
  for (const id of ids) {
525
- const index = indexByRawId.get(id);
914
+ const index = indexByMessageId.get(id);
526
915
  if (index !== void 0 && (earliest === null || index < earliest)) {
527
916
  earliest = index;
528
917
  }
@@ -1148,7 +1537,12 @@ function createCore(ports = {}) {
1148
1537
  let tokensCompressed = 0;
1149
1538
  const errors = [];
1150
1539
  const warnings = [];
1151
- const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(input.messages, input.state, input.config, countTokens);
1540
+ const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(
1541
+ input.messages,
1542
+ input.state,
1543
+ input.config,
1544
+ countTokens
1545
+ );
1152
1546
  const preExistingCoverage = collectCoverage(state);
1153
1547
  const classifications = /* @__PURE__ */ new Map();
1154
1548
  const classificationErrors = [];
@@ -1179,7 +1573,10 @@ function createCore(ports = {}) {
1179
1573
  error: error instanceof Error ? error : new Error(String(error))
1180
1574
  });
1181
1575
  classificationErrors.push(
1182
- rangeError(spec, error instanceof Error ? error.message : String(error))
1576
+ rangeError(
1577
+ spec,
1578
+ error instanceof Error ? error.message : String(error)
1579
+ )
1183
1580
  );
1184
1581
  }
1185
1582
  }
@@ -1190,32 +1587,27 @@ function createCore(ports = {}) {
1190
1587
  if (resolution.status === "ok") resolvableCount++;
1191
1588
  else if (resolution.status === "unknown") unknownCount++;
1192
1589
  }
1193
- const rangeIndexSets = [];
1590
+ const rangeSpans = [];
1194
1591
  for (const [spec, resolution] of classifications) {
1195
1592
  if (resolution.status !== "ok") continue;
1196
- const indices = resolution.resolved.messageIds.map(
1197
- (id) => input.messages.findIndex((m) => m.id === id)
1198
- ).filter((i) => i >= 0);
1199
- rangeIndexSets.push({ spec, indices });
1200
- }
1201
- const sortedRanges = [...rangeIndexSets].sort((a, b) => {
1202
- const aMin = a.indices.length > 0 ? Math.min(...a.indices) : Infinity;
1203
- const bMin = b.indices.length > 0 ? Math.min(...b.indices) : Infinity;
1204
- return aMin - bMin;
1205
- });
1593
+ rangeSpans.push({
1594
+ spec,
1595
+ start: resolution.resolved.startIndex,
1596
+ end: resolution.resolved.endIndex
1597
+ });
1598
+ }
1599
+ const sortedRanges = [...rangeSpans].sort((a, b) => a.start - b.start);
1206
1600
  const skipSpecs = /* @__PURE__ */ new Set();
1207
1601
  let acceptedMaxIndex = -1;
1208
1602
  for (const entry of sortedRanges) {
1209
- const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;
1210
- const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;
1211
- if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {
1603
+ if (entry.start <= acceptedMaxIndex) {
1212
1604
  skipSpecs.add(entry.spec);
1213
1605
  warnings.push(
1214
1606
  `Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) \u2014 overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`
1215
1607
  );
1216
1608
  continue;
1217
1609
  }
1218
- if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;
1610
+ if (entry.end > acceptedMaxIndex) acceptedMaxIndex = entry.end;
1219
1611
  }
1220
1612
  if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {
1221
1613
  let totalRangeChars = 0;
@@ -1278,7 +1670,12 @@ function createCore(ports = {}) {
1278
1670
  tokensCompressed += outcome.tokens;
1279
1671
  warnings.push(...outcome.warnings);
1280
1672
  } catch (error) {
1281
- errors.push(rangeError(spec, error instanceof Error ? error.message : String(error)));
1673
+ errors.push(
1674
+ rangeError(
1675
+ spec,
1676
+ error instanceof Error ? error.message : String(error)
1677
+ )
1678
+ );
1282
1679
  }
1283
1680
  }
1284
1681
  state.stats.compressionCount += blocksCreated;
@@ -1288,12 +1685,17 @@ function createCore(ports = {}) {
1288
1685
  state.nudge.lastNudgeShownTokens = 0;
1289
1686
  state.nudge.lastShownByTier = {};
1290
1687
  }
1291
- return { state, result: { blocksCreated, tokensCompressed, errors, warnings } };
1688
+ return {
1689
+ state,
1690
+ result: { blocksCreated, tokensCompressed, errors, warnings }
1691
+ };
1292
1692
  }
1293
1693
  function processTurn(input) {
1294
1694
  const configErrors = validateConfig(input.config);
1295
1695
  if (configErrors.length > 0) {
1296
- console.warn(`[acp-kernel] Config validation warnings: ${configErrors.join("; ")}. Thresholds may not fire correctly.`);
1696
+ console.warn(
1697
+ `[acp-kernel] Config validation warnings: ${configErrors.join("; ")}. Thresholds may not fire correctly.`
1698
+ );
1297
1699
  }
1298
1700
  const ctx = {
1299
1701
  config: input.config,
@@ -1353,7 +1755,14 @@ function createCore(ports = {}) {
1353
1755
  if (strategy === "none") return base;
1354
1756
  return [...base, createRenderRefsNode(strategy)];
1355
1757
  }
1356
- return { processTurn, applyCompression, defaultNodes, decompress, search, status };
1758
+ return {
1759
+ processTurn,
1760
+ applyCompression,
1761
+ defaultNodes,
1762
+ decompress,
1763
+ search,
1764
+ status
1765
+ };
1357
1766
  }
1358
1767
  var assignRefsNode = {
1359
1768
  name: "assign-refs",
@@ -1453,7 +1862,10 @@ var nudgeNode = {
1453
1862
  if (nudge.shouldInject) {
1454
1863
  stamped.lastNudgeShownTokens = ctx.tokenCount;
1455
1864
  if (nudge.tier !== null) {
1456
- stamped.lastShownByTier = { ...stamped.lastShownByTier, [nudge.tier]: ctx.tokenCount };
1865
+ stamped.lastShownByTier = {
1866
+ ...stamped.lastShownByTier,
1867
+ [nudge.tier]: ctx.tokenCount
1868
+ };
1457
1869
  }
1458
1870
  }
1459
1871
  return {
@@ -1493,17 +1905,16 @@ function applySingleRange(input) {
1493
1905
  const rangeMessageIds = applyPairBoundaryAdjustments(
1494
1906
  resolved,
1495
1907
  input.messages
1496
- );
1908
+ ).filter((id) => !isSummaryMessageId(id));
1497
1909
  if (rangeMessageIds.length > resolved.messageIds.length) {
1498
- const indexByRawId = /* @__PURE__ */ new Map();
1499
- input.messages.forEach((m, i) => indexByRawId.set(m.id, i));
1500
- const adjustedStart = indexByRawId.get(rangeMessageIds[0]) ?? resolved.startIndex;
1501
- const adjustedEnd = indexByRawId.get(rangeMessageIds[rangeMessageIds.length - 1]) ?? resolved.endIndex;
1910
+ const indexByMessageId = /* @__PURE__ */ new Map();
1911
+ input.messages.forEach((m, i) => indexByMessageId.set(m.id, i));
1912
+ const adjustedStart = rangeMessageIds.length > 0 ? indexByMessageId.get(rangeMessageIds[0]) ?? resolved.startIndex : resolved.startIndex;
1913
+ const adjustedEnd = rangeMessageIds.length > 0 ? indexByMessageId.get(rangeMessageIds[rangeMessageIds.length - 1]) ?? resolved.endIndex : resolved.endIndex;
1502
1914
  const nestedSeen = new Set(resolved.nestedBlockIds);
1503
1915
  for (const block2 of activeBlocks(input.state)) {
1504
1916
  if (nestedSeen.has(block2.blockId)) continue;
1505
- const anchor = earliestIndexOfIds(block2.effectiveMessageIds, indexByRawId);
1506
- if (anchor !== null && anchor >= adjustedStart && anchor <= adjustedEnd) {
1917
+ if (blockVisibleInRange(block2, indexByMessageId, adjustedStart, adjustedEnd)) {
1507
1918
  nestedSeen.add(block2.blockId);
1508
1919
  resolved.nestedBlockIds.push(block2.blockId);
1509
1920
  }
@@ -1566,6 +1977,15 @@ function applySingleRange(input) {
1566
1977
  )} from compression range (recent/last-user zone).`
1567
1978
  );
1568
1979
  }
1980
+ if (!isBlockBoundary && filteredIds.length === 0 && consumedBlockIds.length > 0) {
1981
+ const first = consumedBlockIds[0];
1982
+ const last = consumedBlockIds[consumedBlockIds.length - 1];
1983
+ throw new Error(
1984
+ `Range ${input.spec.startRef}..${input.spec.endRef} contains no new compressible messages \u2014 every message in it is already covered by active block(s) ${consumedBlockIds.join(
1985
+ ", "
1986
+ )}. Nothing was compressed. To rewrite or merge those blocks, reference them by block ID (${first}..${last}); otherwise run acp_status and compress a range it reports as compressible.`
1987
+ );
1988
+ }
1569
1989
  validateCompressionRange(input, filteredIds, consumedBlockIds.length);
1570
1990
  let compressedTokens = 0;
1571
1991
  for (const id of filteredIds) {
@@ -1718,12 +2138,21 @@ function pendingByTier(state, recommendation, countTokens, minCompressRange) {
1718
2138
  const out = {};
1719
2139
  const merged = recommendation?.recommendedRanges ?? [];
1720
2140
  const effective = minCompressRange > 0 ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange) : merged;
1721
- out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };
2141
+ out[1] = {
2142
+ pending: effective.reduce((s, r) => s + r.tokens, 0),
2143
+ targetBlocks: []
2144
+ };
1722
2145
  const active = activeBlocks(state);
1723
2146
  const t1 = active.filter((b) => b.tier === 1);
1724
2147
  const t2 = active.filter((b) => b.tier === 2);
1725
- out[2] = { pending: t1.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t1 };
1726
- out[3] = { pending: t2.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t2 };
2148
+ out[2] = {
2149
+ pending: t1.reduce((s, b) => s + countTokens(b.summary), 0),
2150
+ targetBlocks: t1
2151
+ };
2152
+ out[3] = {
2153
+ pending: t2.reduce((s, b) => s + countTokens(b.summary), 0),
2154
+ targetBlocks: t2
2155
+ };
1727
2156
  return out;
1728
2157
  }
1729
2158
  function decideNudge(input) {
@@ -1811,18 +2240,35 @@ function decideNudge(input) {
1811
2240
  const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);
1812
2241
  const ready = eligible.filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens).map((t) => `T${t} ${tiers[t].pending}`);
1813
2242
  const readyHint = ready.length > 0 ? `, ready: ${ready.join(", ")}` : "";
1814
- const blocked = eligible.filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor).map((t) => `T${t} (cadence)`);
2243
+ const blocked = eligible.filter(
2244
+ (t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor
2245
+ ).map((t) => `T${t} (cadence)`);
1815
2246
  const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(", ")}` : "";
1816
- const maxPending = Math.max(0, ...Object.values(tiers).map((t) => t.pending));
2247
+ const maxPending = Math.max(
2248
+ 0,
2249
+ ...Object.values(tiers).map((t) => t.pending)
2250
+ );
1817
2251
  const pendingShort = maxPending < nudgeGrowthTokens;
1818
2252
  const growthShort = growthSinceReference < growthFloor;
1819
2253
  const parts = [];
1820
- if (pendingShort) parts.push(`max compressible ${maxPending} < threshold ${nudgeGrowthTokens}`);
1821
- if (growthShort) parts.push(`growth ${growthSinceReference} < floor ${growthFloor}`);
1822
- if (parts.length === 0) parts.push(`max compressible ${maxPending}, growth ${growthSinceReference}`);
2254
+ if (pendingShort)
2255
+ parts.push(
2256
+ `max compressible ${maxPending} < threshold ${nudgeGrowthTokens}`
2257
+ );
2258
+ if (growthShort)
2259
+ parts.push(`growth ${growthSinceReference} < floor ${growthFloor}`);
2260
+ if (parts.length === 0)
2261
+ parts.push(
2262
+ `max compressible ${maxPending}, growth ${growthSinceReference}`
2263
+ );
1823
2264
  reason = `${parts.join("; ")}${readyHint}${blockedHint}`;
1824
2265
  }
1825
- const ctxBreakdown = computeContextBreakdown(input.messages, tokenCount, growthSinceReference, countTokens);
2266
+ const ctxBreakdown = computeContextBreakdown(
2267
+ input.messages,
2268
+ tokenCount,
2269
+ growthSinceReference,
2270
+ countTokens
2271
+ );
1826
2272
  return {
1827
2273
  shouldInject,
1828
2274
  reason,
@@ -1865,355 +2311,49 @@ function computeContextBreakdown(messages, total, growth, countTokens) {
1865
2311
  text += tokens;
1866
2312
  }
1867
2313
  }
1868
- return { system, tool, summaries, code, text, total, growth };
1869
- }
1870
- function cloneState(state) {
1871
- return {
1872
- blocks: state.blocks.map((block) => ({
1873
- ...block,
1874
- directMessageIds: [...block.directMessageIds],
1875
- effectiveMessageIds: [...block.effectiveMessageIds],
1876
- directBlockIds: [...block.directBlockIds]
1877
- })),
1878
- messageRefs: {
1879
- byRaw: { ...state.messageRefs.byRaw },
1880
- byRef: { ...state.messageRefs.byRef }
1881
- },
1882
- tokenSnapshot: { ...state.tokenSnapshot ?? {} },
1883
- nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
1884
- stats: { ...state.stats },
1885
- nextBlockId: state.nextBlockId,
1886
- nextRunId: state.nextRunId
1887
- };
1888
- }
1889
- function scoreRelevance(block, terms) {
1890
- const topic = (block.topic ?? "").toLowerCase();
1891
- const summary = block.summary.toLowerCase();
1892
- let score = 0;
1893
- for (const term of terms) {
1894
- const topicHits = countOccurrences(topic, term);
1895
- if (topicHits > 0) score += Math.min(topicHits * 0.15, 0.45);
1896
- const summaryHits = countOccurrences(summary, term);
1897
- if (summaryHits > 0) score += Math.min(summaryHits * 0.04, 0.2);
1898
- }
1899
- return Math.min(score, 1);
1900
- }
1901
- function countOccurrences(haystack, needle) {
1902
- if (!haystack || !needle) return 0;
1903
- let count = 0;
1904
- let position = 0;
1905
- while ((position = haystack.indexOf(needle, position)) !== -1) {
1906
- count++;
1907
- position += needle.length;
1908
- }
1909
- return count;
1910
- }
1911
- var COMPRESS_PHILOSOPHY = `Compression Philosophy:
1912
- - All compression serves the primary task, but be frugal.
1913
- - Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.
1914
- - Compress by need, not by percentage.
1915
- - Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;
1916
- var HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS
1917
-
1918
- When you call \`compress\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.
1919
-
1920
- KEEP VERBATIM \u2014 never paraphrase or abbreviate these:
1921
- - Full file paths with line numbers, directory prefix on every mention (\`lib/hooks.ts:347\`, \`src/index.ts:12-18\`, \`gatenet_v3/model.py:45\`). Never abbreviate to a bare filename (\`hooks.ts\`, \`model.py\`) \u2014 they are ambiguous and cannot be grepped or decompressed-to later.
1922
- - Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic \u2014 the line that IS the finding, not just the function name (e.g. \`kv_keys += define_gate * a_key[i](emb)\` is more useful than "see model_kvnet.py").
1923
- - Error messages and stack traces (exact text \u2014 you need the literal string to grep for it later).
1924
- - Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not "X is worse" alone (write "1.76\xD7 PPL gap because KV store is static", not "KVNet underperforms").
1925
- - Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing; without it the decision looks arbitrary).
1926
- - Constraints discovered ("must support Node 22", "no new dependencies", "AGENTS.md forbids \`as any\`").
1927
- - Exact values: versions, config keys, thresholds, magic numbers.
1928
- - User intent \u2014 quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., "User said: ..."), not as current directives. Losing these changes the task itself.
1929
- - The user's overall goal and any changes to it \u2014 the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., "initially: fix bug X \u2192 pivoted to: refactor module Y after discovering root cause"). Losing the goal or its evolution makes all subsequent work appear unmotivated.
1930
- - Purpose behind each significant action \u2014 preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.
1931
- - Open questions and unresolved TODOs \u2014 losing these changes what work appears to remain.
1932
- - Message refs of key anchors (\`m00420\`, \`m00510\u2013m00520\`) \u2014 they let you or a later reader jump back via decompress to the exact original.
1933
-
1934
- DROP \u2014 extract the signal, discard the vessel:
1935
- - Verbose logs (build/test/\`npm\` output) once you have captured the error line or the result.
1936
- - Duplicate file reads once the needed content is recorded.
1937
- - Consumed exploration \u2014 search hits, agent return values, successful tool outputs \u2014 once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).
1938
- - Dead-end exploration \u2014 but PRESERVE the lesson in one line: "tried X, failed because Y".
1939
- - Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).
1940
- - Repeated status checks (\`git status\`, \`ls\`) once state is known.
1941
-
1942
- For each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers \u2014 not where it lives. Bad: "probe script at /path/probe_kvnet.py". Good: "probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention." This lets a later decompress target the right block by relevance, not by guessing locations.
1943
-
1944
- PRIORITY \u2014 when the summary must be compact, preserve in this order:
1945
- 1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).
1946
- 2. Decisions and rationale.
1947
- 3. Exact technical artifacts: paths, signatures, errors, values.
1948
- 4. Conclusions and key findings.
1949
- 5. Lessons learned: what failed and why.
1950
-
1951
- Write dense, scannable bullets \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;
1952
- var TIER2_DISTILL_RULES = `TIER 2 COMPRESSION \u2014 DISTILLATION
1953
-
1954
- You are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.
1955
-
1956
- KEEP \u2014 these are the only things that survive distillation:
1957
- - Decisions and their rationale ("chose X over Y because Z" \u2014 the "because" is load-bearing).
1958
- - Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.
1959
- - Key lessons: what failed and why ("tried X, failed because Y"). These prevent repeating mistakes.
1960
- - Critical constraints discovered ("must support Node 22", "AGENTS.md forbids as any").
1961
- - Design decisions with architectural impact ("chose compress-as-anchor over synthetic messages because prefix cache").
1962
- - Whether content is OBSOLETE or SUPERSEDED \u2014 mark with one line: "[SUPERSEDED by PR #NNN]" or "[OBSOLETE: deleted in vX.Y.Z]". Do NOT keep the obsolete content's details \u2014 just the marker and reason.
1963
- - Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., "fixed filterCompressedRanges in prune.ts", "added SessionStateRegistry in state.ts". Not exact line numbers or full signatures \u2014 just enough to LOCATE the code without searching.
1964
- - Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line ("explored X, not viable because Y"). Do not keep the exploration process.
1965
-
1966
- DROP \u2014 these were useful during the work but are no longer needed:
1967
- - Exact line numbers, diffs, verbose function signatures, full code listings.
1968
- - Build/deploy process details, test execution steps.
1969
- - Review process details (who reviewed, what rounds, test counts).
1970
- - Verbose logs, command output, intermediate debugging steps.
1971
-
1972
- FORMAT:
1973
- - Start each distilled block with a source header line:
1974
- \`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
1975
- Example: \`Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]\`
1976
- - 3-5 bullet points per source block, each a self-contained fact.
1977
- - Dense, scannable \u2014 no narrative prose.
1978
- - Start with the outcome, not the process: "v1.13.0 shipped (7 PRs bundled)" not "implemented 7 PRs then reviewed then merged".
1979
- - Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks \u2014 keep it once under the most relevant source header.
1980
-
1981
- SIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by "[no actionable content]."`;
1982
- var TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION \u2014 ULTRA-CONDENSATION
1983
-
1984
- You are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.
1985
-
1986
- PRIORITY \u2014 when a source block has more facts than the size target allows, keep in this order:
1987
- 1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.
1988
- 2. Open work (PRs/issues still pending) \u2014 these may need follow-up.
1989
- 3. Key decisions with architectural impact ("chose X over Y because Z").
1990
- 4. Critical constraints ("must support Node 22").
1991
- Drop everything else. Tier 3 is a lookup index, not a knowledge base.
1992
-
1993
- FORMAT:
1994
- - Start with a source header line:
1995
- \`Source: bN+bM+... (XK\u2192YK tok, Zx). [original topic]\`
1996
- - Output 1-3 facts per source block. Each fact is a single line: subject + outcome.
1997
- - No explanations, no rationale, no process \u2014 just the fact.
1998
- - Format: "[PR/Issue/Version] \u2014 [outcome in \u22648 words]"
1999
- - Merge related facts from different source blocks if they concern the same topic.
2000
-
2001
- EXAMPLES:
2002
- - "v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)"
2003
- - "PR #196 merged \u2014 preserve-first-user (supersedes #169)"
2004
- - "Bug 1214 fixed \u2014 compress consumed all user messages"
2005
- - "Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection"
2006
- - "Constraint: AGENTS.md forbids as any \u2014 never suppress types"
2007
-
2008
- DROP:
2009
- - Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.
2010
- - Lessons learned ("tried X, failed because Y") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.
2011
- - Design rationale details \u2014 keep the decision, drop the "because" unless it's a critical constraint.
2012
- - Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 drop entirely, note "[N blocks obsolete]" in the summary.
2013
-
2014
- SIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output \u2248 N \xD7 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;
2015
- var defaultPrompts = Object.freeze({
2016
- compressPhilosophy: COMPRESS_PHILOSOPHY,
2017
- howToCompressRules: HOW_TO_COMPRESS_RULES,
2018
- tier2DistillRules: TIER2_DISTILL_RULES,
2019
- tier3CondenseRules: TIER3_CONDENSE_RULES
2020
- });
2021
- function resolvePrompts(overrides, options = {}) {
2022
- const clean = {};
2023
- if (overrides) {
2024
- for (const [key, value] of Object.entries(overrides)) {
2025
- if (typeof value === "string") {
2026
- clean[key] = value;
2027
- }
2028
- }
2029
- }
2030
- const keys = Object.keys(clean);
2031
- if (keys.length > 0 && !options.acknowledgeRisk) {
2032
- throw new Error(
2033
- `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. Overridden keys: ${keys.join(", ")}. These rules are quality-critical (tuned over months of production use); changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`
2034
- );
2035
- }
2036
- return { ...defaultPrompts, ...clean };
2037
- }
2038
- function efficiencyNote(prompts) {
2039
- return `This is an efficiency nudge to compress early and keep context lean \u2014 not an overflow warning. A separate, stronger alert will appear if the context is actually full.
2040
-
2041
- ${prompts.compressPhilosophy}`;
2042
- }
2043
- function emergencyHeader(prompts) {
2044
- return `\u26A0\uFE0F Context limit reached \u2014 compress now. Prioritize consumed tool outputs.
2045
-
2046
- ${prompts.compressPhilosophy}`;
2047
- }
2048
- function formatK(n) {
2049
- if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
2050
- return `${n}`;
2051
- }
2052
- function formatBreakdown(bd) {
2053
- if (!bd) return "";
2054
- const parts = [];
2055
- if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);
2056
- if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);
2057
- if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);
2058
- if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);
2059
- if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);
2060
- const growth = bd.growth > 0 ? `
2061
- +${formatK(bd.growth)} since last nudge` : "";
2062
- return `Context breakdown: ${parts.join(" | ")}${growth}`;
2063
- }
2064
- function formatTierTargetBlocks(blocks) {
2065
- if (blocks.length === 0) {
2066
- return "Target blocks: (none \u2014 no tier blocks found)";
2067
- }
2068
- const lines = blocks.map((b) => {
2069
- const summaryTokens = Math.ceil((b.summary ?? "").length / 4);
2070
- const topic = b.topic ? ` "${b.topic}"` : "";
2071
- return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}\u2192${formatK(summaryTokens)}${topic}`;
2072
- });
2073
- return `Target ${blocks[0].tier === 1 ? "tier-1" : "tier-2"} blocks to distill (${blocks.length}):
2074
- ${lines.join("\n")}`;
2075
- }
2076
- function formatRanges(compressible, protectedRanges) {
2077
- if (compressible.length === 0 && protectedRanges.length === 0) {
2078
- return "[No specific ranges detected \u2014 compress any consumed content.]";
2079
- }
2080
- const refNum2 = (ref) => {
2081
- const m = ref.match(/\d+/);
2082
- return m ? parseInt(m[0], 10) : 0;
2083
- };
2084
- const entries = [];
2085
- for (const r of compressible) {
2086
- entries.push({
2087
- startRef: r.startRef,
2088
- endRef: r.endRef,
2089
- startNum: refNum2(r.startRef),
2090
- endNum: refNum2(r.endRef),
2091
- count: r.count,
2092
- tokens: r.tokens,
2093
- toolPct: r.toolPct,
2094
- textPct: r.textPct,
2095
- compressibleTokens: r.tokens,
2096
- compressibleCount: r.count,
2097
- protectedTokens: 0,
2098
- protectedCount: 0,
2099
- protectedTools: [],
2100
- dangerous: r.dangerous ?? false
2101
- });
2102
- }
2103
- for (const r of protectedRanges) {
2104
- entries.push({
2105
- startRef: r.startRef,
2106
- endRef: r.endRef,
2107
- startNum: refNum2(r.startRef),
2108
- endNum: refNum2(r.endRef),
2109
- count: r.count,
2110
- tokens: r.tokens,
2111
- toolPct: 0,
2112
- textPct: 0,
2113
- compressibleTokens: 0,
2114
- compressibleCount: 0,
2115
- protectedTokens: r.tokens,
2116
- protectedCount: r.count,
2117
- protectedTools: [...r.tools],
2118
- dangerous: false
2119
- });
2120
- }
2121
- entries.sort((a, b) => a.startNum - b.startNum);
2122
- const merged = [];
2123
- for (const e of entries) {
2124
- const last = merged[merged.length - 1];
2125
- if (last && e.startNum <= last.endNum + 1) {
2126
- last.endRef = e.endRef;
2127
- last.endNum = Math.max(last.endNum, e.endNum);
2128
- last.count += e.count;
2129
- last.tokens += e.tokens;
2130
- last.compressibleTokens += e.compressibleTokens;
2131
- last.compressibleCount += e.compressibleCount;
2132
- last.protectedTokens += e.protectedTokens;
2133
- last.protectedCount += e.protectedCount;
2134
- if (e.dangerous) last.dangerous = true;
2135
- for (const t of e.protectedTools) {
2136
- if (!last.protectedTools.includes(t)) last.protectedTools.push(t);
2137
- }
2138
- } else {
2139
- merged.push({ ...e });
2140
- }
2141
- }
2142
- const lines = merged.map((e) => {
2143
- const suffix = e.dangerous && e.compressibleTokens > 0 ? " \u26A0\uFE0F NOT recommended unless you are certain." : "";
2144
- if (e.protectedTokens > 0 && e.compressibleTokens === 0) {
2145
- return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(", ")} \u2014 not compressible]${suffix}`;
2146
- }
2147
- if (e.protectedTokens > 0 && e.compressibleTokens > 0) {
2148
- return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(", ")}]${suffix}`;
2149
- }
2150
- return ` ${e.startRef}\u2013${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;
2151
- });
2152
- return `Compressible ranges (${merged.length}, oldest first):
2153
- ${lines.join("\n")}`;
2154
- }
2155
- function renderNudgeText(decision, prompts = defaultPrompts) {
2156
- const breakdownStr = formatBreakdown(decision.contextBreakdown);
2157
- const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);
2158
- const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;
2159
- if (decision.tier !== null && decision.tier >= 2) {
2160
- const isT2 = decision.tier === 2;
2161
- const targets = decision.tierTargetBlocks ?? [];
2162
- const blockList = formatTierTargetBlocks(targets);
2163
- const startId = targets[0]?.blockId ?? "b1";
2164
- const endId = targets[targets.length - 1]?.blockId ?? "b5";
2165
- const voice = isEmergency ? "emergency" : "gentle";
2166
- const triggerLine = isEmergency ? `[EMERGENCY \u2014 TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"}] Context limit reached \u2014 distill NOW into a denser summary to reclaim tokens.` : `[TIER ${decision.tier} ${isT2 ? "DISTILLATION" : "CONDENSATION"} TRIGGER]`;
2167
- return {
2168
- voice,
2169
- text: [
2170
- efficiencyNote(prompts),
2171
- "",
2172
- breakdownStr,
2173
- "",
2174
- triggerLine,
2175
- isT2 ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.` : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well \u2014 apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,
2176
- blockList,
2177
- `Example: compress({ content: [{ startId: "${startId}", endId: "${endId}", summary: "..." }] })`,
2178
- "",
2179
- prompts.howToCompressRules,
2180
- "",
2181
- isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules
2182
- ].join("\n")
2183
- };
2184
- }
2185
- if (isEmergency) {
2186
- return {
2187
- voice: "emergency",
2188
- text: [
2189
- emergencyHeader(prompts),
2190
- "",
2191
- breakdownStr,
2192
- "",
2193
- prompts.howToCompressRules,
2194
- "",
2195
- `{ "topic": "...", "content": [{ "startId": "<ID>", "endId": "<ID>", "summary": "..." }] }`,
2196
- "Only use IDs from visible messages above. Compress older work first.",
2197
- "",
2198
- rangesStr
2199
- ].join("\n")
2200
- };
2201
- }
2314
+ return { system, tool, summaries, code, text, total, growth };
2315
+ }
2316
+ function cloneState(state) {
2202
2317
  return {
2203
- voice: "gentle",
2204
- text: [
2205
- efficiencyNote(prompts),
2206
- "",
2207
- breakdownStr,
2208
- "",
2209
- prompts.howToCompressRules,
2210
- "",
2211
- rangesStr,
2212
- "",
2213
- `\u{1F4A1} Compress all ranges in one call (pass multiple content entries: \`content: [{...}, {...}]\`).`
2214
- ].join("\n")
2318
+ blocks: state.blocks.map((block) => ({
2319
+ ...block,
2320
+ directMessageIds: [...block.directMessageIds],
2321
+ effectiveMessageIds: [...block.effectiveMessageIds],
2322
+ directBlockIds: [...block.directBlockIds]
2323
+ })),
2324
+ messageRefs: {
2325
+ byRaw: { ...state.messageRefs.byRaw },
2326
+ byRef: { ...state.messageRefs.byRef }
2327
+ },
2328
+ tokenSnapshot: { ...state.tokenSnapshot ?? {} },
2329
+ nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },
2330
+ stats: { ...state.stats },
2331
+ nextBlockId: state.nextBlockId,
2332
+ nextRunId: state.nextRunId
2215
2333
  };
2216
2334
  }
2335
+ function scoreRelevance(block, terms) {
2336
+ const topic = (block.topic ?? "").toLowerCase();
2337
+ const summary = block.summary.toLowerCase();
2338
+ let score = 0;
2339
+ for (const term of terms) {
2340
+ const topicHits = countOccurrences(topic, term);
2341
+ if (topicHits > 0) score += Math.min(topicHits * 0.15, 0.45);
2342
+ const summaryHits = countOccurrences(summary, term);
2343
+ if (summaryHits > 0) score += Math.min(summaryHits * 0.04, 0.2);
2344
+ }
2345
+ return Math.min(score, 1);
2346
+ }
2347
+ function countOccurrences(haystack, needle) {
2348
+ if (!haystack || !needle) return 0;
2349
+ let count = 0;
2350
+ let position = 0;
2351
+ while ((position = haystack.indexOf(needle, position)) !== -1) {
2352
+ count++;
2353
+ position += needle.length;
2354
+ }
2355
+ return count;
2356
+ }
2217
2357
  function parseBlockIdArg(arg) {
2218
2358
  const normalized = arg.trim().toLowerCase();
2219
2359
  const refMatch = /^b0*(\d+)$/.exec(normalized);
@@ -8989,111 +9129,6 @@ function formatSize(tokens) {
8989
9129
  return `${(tokens / 1e6).toFixed(1)}M`;
8990
9130
  }
8991
9131
 
8992
- // node_modules/billion-context-kit/dist/index.js
8993
- var VIABLE_RANGE_MIN_TOKENS = 200;
8994
- function viableRanges(ranges) {
8995
- return ranges.filter((r) => r.tokens >= VIABLE_RANGE_MIN_TOKENS);
8996
- }
8997
- function topicFallback(summary) {
8998
- const first = summary.split(/[.\n]/)[0] ?? "";
8999
- const t = first.trim().replace(/^["'`]+/, "").trim();
9000
- return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}\u2026`;
9001
- }
9002
- function formatCompactTokens(count) {
9003
- if (count < 1e3) return count.toString();
9004
- if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
9005
- if (count < 1e6) return `${Math.round(count / 1e3)}k`;
9006
- if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
9007
- return `${Math.round(count / 1e6)}M`;
9008
- }
9009
- function bar(value, total, width = 20) {
9010
- if (total === 0) return "";
9011
- const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
9012
- return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
9013
- }
9014
- function buildStatusPanel(input) {
9015
- const { tokenCount, state, nudge, modelContextLimit } = input;
9016
- const fmt2 = input.fmtTokens ?? formatCompactTokens;
9017
- const bd = nudge?.contextBreakdown;
9018
- const limit = modelContextLimit;
9019
- const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
9020
- const systemPromptTokens = input.systemPromptTokens;
9021
- const sentTotal = classified + systemPromptTokens;
9022
- const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
9023
- const displayTotal = tokenCount;
9024
- const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
9025
- const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
9026
- const activeBlocksList = state.blocks.filter((b) => b.active);
9027
- const totalBlocksList = state.blocks;
9028
- const lines = [];
9029
- lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
9030
- lines.push("\u2502 ACP Context Analysis \u2502");
9031
- lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
9032
- if (input.version) lines.push(input.version);
9033
- lines.push("");
9034
- lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
9035
- if (nudge && bd) {
9036
- const growth = bd.growth;
9037
- if (growth > 0 && displayTotal > 0) {
9038
- lines.push(`Growth: +${fmt2(growth)} since last nudge`);
9039
- }
9040
- lines.push("");
9041
- lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
9042
- if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
9043
- lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
9044
- }
9045
- lines.push("");
9046
- lines.push("Token Breakdown (sent view):");
9047
- const categories = [
9048
- { label: "Tool", value: bd.tool },
9049
- { label: "SysPrompt", value: systemPromptTokens },
9050
- { label: "Text", value: bd.text },
9051
- { label: "Code", value: bd.code },
9052
- { label: "Summaries", value: bd.summaries }
9053
- ];
9054
- for (const cat of categories) {
9055
- if (cat.value <= 0) continue;
9056
- const pct2 = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
9057
- const b = bar(cat.value, sentTotal);
9058
- lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
9059
- }
9060
- }
9061
- lines.push("");
9062
- if (nudge) {
9063
- if (nudge.shouldInject) {
9064
- const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
9065
- lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
9066
- } else {
9067
- lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
9068
- }
9069
- }
9070
- const ranges = viableRanges(nudge?.compressibleRanges ?? []);
9071
- const protectedRanges = nudge?.protectedRanges ?? [];
9072
- if (ranges.length > 0 || protectedRanges.length > 0) {
9073
- lines.push("");
9074
- lines.push(formatRanges(ranges, protectedRanges));
9075
- }
9076
- if (activeBlocksList.length > 0) {
9077
- lines.push("");
9078
- lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
9079
- for (const b of activeBlocksList) {
9080
- const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
9081
- const summaryTok = defaultCountTokens(b.summary || "");
9082
- const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
9083
- lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
9084
- }
9085
- } else if (totalBlocksList.length > 0) {
9086
- lines.push("");
9087
- lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
9088
- } else {
9089
- lines.push("");
9090
- lines.push("Blocks: none (nothing compressed yet)");
9091
- }
9092
- lines.push("");
9093
- lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
9094
- return lines.join("\n");
9095
- }
9096
-
9097
9132
  // src/delegate-tool.ts
9098
9133
  import {
9099
9134
  spawn
@@ -9107,7 +9142,7 @@ import { dirname as dirname4, join as join6, resolve as resolvePath } from "path
9107
9142
  var FOOTER_STATUS_KEY = "billion-context-pi";
9108
9143
  var ui;
9109
9144
  var lastFooterText = "";
9110
- function formatCompactTokens2(count) {
9145
+ function formatCompactTokens(count) {
9111
9146
  if (count < 1e3) return count.toString();
9112
9147
  if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
9113
9148
  if (count < 1e6) return `${Math.round(count / 1e3)}k`;
@@ -9124,7 +9159,7 @@ function updateFooterStatus() {
9124
9159
  let text;
9125
9160
  if (usage && usage.totalTokens > 0) {
9126
9161
  const costStr = usage.cost.total > 0 ? ` ($${usage.cost.total.toFixed(4)})` : "";
9127
- text = `sub-agents \u2191${formatCompactTokens2(usage.input)} \u2193${formatCompactTokens2(usage.output)}${costStr}`;
9162
+ text = `sub-agents \u2191${formatCompactTokens(usage.input)} \u2193${formatCompactTokens(usage.output)}${costStr}`;
9128
9163
  }
9129
9164
  if ((text ?? "") === lastFooterText) return;
9130
9165
  lastFooterText = text ?? "";
@@ -10400,6 +10435,107 @@ async function handleStatus(args, runtime, ctx) {
10400
10435
  ${extra.join("\n")}` : base;
10401
10436
  }
10402
10437
 
10438
+ // node_modules/acp-kernel/dist/panel/index.js
10439
+ function topicFallback(summary) {
10440
+ const first = summary.split(/[.\n]/)[0] ?? "";
10441
+ const t = first.trim().replace(/^["'`]+/, "").trim();
10442
+ return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}\u2026`;
10443
+ }
10444
+ function formatCompactTokens2(count) {
10445
+ if (count < 1e3) return count.toString();
10446
+ if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
10447
+ if (count < 1e6) return `${Math.round(count / 1e3)}k`;
10448
+ if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
10449
+ return `${Math.round(count / 1e6)}M`;
10450
+ }
10451
+ function bar(value, total, width = 20) {
10452
+ if (total === 0) return "";
10453
+ const filled = Math.max(0, Math.min(width, Math.round(value / total * width)));
10454
+ return "\u2588".repeat(filled) + "\u2591".repeat(width - filled);
10455
+ }
10456
+ function buildStatusPanel(input) {
10457
+ const { tokenCount, state, nudge, modelContextLimit } = input;
10458
+ const fmt2 = input.fmtTokens ?? formatCompactTokens2;
10459
+ const bd = nudge?.contextBreakdown;
10460
+ const limit = modelContextLimit;
10461
+ const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;
10462
+ const systemPromptTokens = input.systemPromptTokens;
10463
+ const sentTotal = classified + systemPromptTokens;
10464
+ const sessionOnly = input.unprunedTokens !== void 0 ? Math.max(0, input.unprunedTokens - sentTotal) : 0;
10465
+ const displayTotal = tokenCount;
10466
+ const displayPct = limit > 0 ? Math.round(displayTotal / limit * 100) : 0;
10467
+ const sentPct = limit > 0 ? Math.round(sentTotal / limit * 100) : 0;
10468
+ const activeBlocksList = state.blocks.filter((b) => b.active);
10469
+ const totalBlocksList = state.blocks;
10470
+ const lines = [];
10471
+ lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
10472
+ lines.push("\u2502 ACP Context Analysis \u2502");
10473
+ lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
10474
+ if (input.version) lines.push(input.version);
10475
+ lines.push("");
10476
+ lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt2(displayTotal)} / ${fmt2(limit)}) \u2014 never shrinks; includes compressed originals`);
10477
+ if (nudge && bd) {
10478
+ const growth = bd.growth;
10479
+ if (growth > 0 && displayTotal > 0) {
10480
+ lines.push(`Growth: +${fmt2(growth)} since last nudge`);
10481
+ }
10482
+ lines.push("");
10483
+ lines.push(`Sent to LLM (after compression, est.): ${fmt2(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
10484
+ if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
10485
+ lines.push(`Session-only (compressed originals, est.): ${fmt2(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
10486
+ }
10487
+ lines.push("");
10488
+ lines.push("Token Breakdown (sent view):");
10489
+ const categories = [
10490
+ { label: "Tool", value: bd.tool },
10491
+ { label: "SysPrompt", value: systemPromptTokens },
10492
+ { label: "Text", value: bd.text },
10493
+ { label: "Code", value: bd.code },
10494
+ { label: "Summaries", value: bd.summaries }
10495
+ ];
10496
+ for (const cat of categories) {
10497
+ if (cat.value <= 0) continue;
10498
+ const pct2 = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
10499
+ const b = bar(cat.value, sentTotal);
10500
+ lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct2).padStart(3)}% ${fmt2(cat.value)}`);
10501
+ }
10502
+ }
10503
+ lines.push("");
10504
+ if (nudge) {
10505
+ if (nudge.shouldInject) {
10506
+ const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
10507
+ lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
10508
+ } else {
10509
+ lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
10510
+ }
10511
+ }
10512
+ const ranges = viableRanges(nudge?.compressibleRanges ?? []);
10513
+ const protectedRanges = nudge?.protectedRanges ?? [];
10514
+ if (ranges.length > 0 || protectedRanges.length > 0) {
10515
+ lines.push("");
10516
+ lines.push(formatRanges(ranges, protectedRanges));
10517
+ }
10518
+ if (activeBlocksList.length > 0) {
10519
+ lines.push("");
10520
+ lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
10521
+ for (const b of activeBlocksList) {
10522
+ const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
10523
+ const summaryTok = defaultCountTokens(b.summary || "");
10524
+ const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;
10525
+ lines.push(` [${b.blockId}] T${b.tier} ${fmt2(origTok)}\u2192${fmt2(summaryTok)}${topic}`);
10526
+ }
10527
+ } else if (totalBlocksList.length > 0) {
10528
+ lines.push("");
10529
+ lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt2(state.stats.tokensCompressed)} tokens compressed)`);
10530
+ } else {
10531
+ lines.push("");
10532
+ lines.push("Blocks: none (nothing compressed yet)");
10533
+ }
10534
+ lines.push("");
10535
+ lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
10536
+ return lines.join("\n");
10537
+ }
10538
+
10403
10539
  // src/setup-subagent-tools.ts
10404
10540
  import * as fs3 from "fs";
10405
10541
  import * as os from "os";
@@ -10690,7 +10826,7 @@ async function statusReport(runtime, ctx) {
10690
10826
  const modelId = ctx.model?.id ?? "default";
10691
10827
  const sentTokens = estimateTokens(coreMessages, coveredIds) + systemPromptTokens;
10692
10828
  const turn = runtime.core.processTurn({ messages: coreMessages, state, config, tokenCount: calibrateTokens(sentTokens, runtime.density.densityFor(modelId)) });
10693
- const versionStr = "0.1.45" ? `billion-context-pi@${"0.1.45"}` : void 0;
10829
+ const versionStr = "0.1.46" ? `billion-context-pi@${"0.1.46"}` : void 0;
10694
10830
  let text = buildStatusPanel({
10695
10831
  version: versionStr,
10696
10832
  tokenCount: sessionTokens,
@@ -11052,7 +11188,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11052
11188
  "--no-fund",
11053
11189
  "--no-save"
11054
11190
  ];
11055
- const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.45";
11191
+ const prevVersion = (await readPackageJson(join8(extDir, "package.json")))?.version ?? "0.1.46";
11056
11192
  const { code, stderr } = await runNpmImpl(installArgs(latest), { cwd: npmDir, timeout: 6e4 });
11057
11193
  if (code !== 0) {
11058
11194
  logWarn("update", {
@@ -11065,7 +11201,7 @@ async function autoInstallLatest(latest, extDirOverride) {
11065
11201
  }
11066
11202
  const verify = await verifyInstall(npmDir, latest);
11067
11203
  if (!verify.ok) {
11068
- const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.45";
11204
+ const rollbackTo = SEMVER_RE.test(prevVersion) ? prevVersion : "0.1.46";
11069
11205
  logWarn("update", { event: "auto-install-verify-failed", latest, reason: verify.reason, rollbackTo });
11070
11206
  const rb = await runNpmImpl(installArgs(rollbackTo), { cwd: npmDir, timeout: 6e4 });
11071
11207
  logInfo("update", { event: "rollback", from: latest, to: rollbackTo, ok: rb.code === 0 });
@@ -11126,7 +11262,7 @@ async function checkForUpdate(autoUpdate, notify) {
11126
11262
  const runtimeVersion = await getRuntimeVersion();
11127
11263
  const latest = await fetchLatestVersion();
11128
11264
  if (!latest) return;
11129
- const current = runtimeVersion ?? "0.1.45";
11265
+ const current = runtimeVersion ?? "0.1.46";
11130
11266
  const hasUpdate = isNewer(latest, current);
11131
11267
  debug.event("update-check", {
11132
11268
  current,
@@ -11201,7 +11337,7 @@ function wireSessionLifecycle(pi, runtime) {
11201
11337
  const sid = ctx.sessionManager.getSessionId();
11202
11338
  runtime.clearSessionTracking(sid);
11203
11339
  const modelInfo = ctx.model;
11204
- logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.45" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
11340
+ logInfo("session", { event: "start", sid, cwd: ctx.cwd, debug: runtime.adapter.debug ?? null, version: true ? "0.1.46" : null, model: modelInfo?.id ?? null, modelApi: modelInfo?.api ?? null, contextWindow: modelInfo?.contextWindow ?? null });
11205
11341
  try {
11206
11342
  await runtime.reloadConfig(ctx.cwd);
11207
11343
  setDelegateDisplayUsage(resolveDelegate(runtime.adapter).displayUsage);