acp-kernel 0.0.89-pr.419.270 → 0.0.89

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
@@ -111,45 +111,33 @@ function summaryMessageId(blockId) {
111
111
  function isSummaryMessageId(id) {
112
112
  return id.startsWith(SUMMARY_ID_PREFIX);
113
113
  }
114
- function baseIdOf(id) {
115
- const hash = id.indexOf("#");
116
- return hash > 0 ? id.substring(0, hash) : id;
117
- }
118
- function isCovered(id, coveredBases) {
119
- return coveredBases.has(baseIdOf(id));
120
- }
121
114
  function isRenderedSummaryMessage(message) {
122
115
  return isSummaryMessageId(message.id) && message.role === "system" && message.contentType === "text";
123
116
  }
124
117
  function prune(messages, state, options = {}) {
125
118
  const covered = coveredMessageIds(state);
126
119
  if (covered.size === 0) return [...messages];
127
- const coveredBases = /* @__PURE__ */ new Set();
128
- for (const id of covered) coveredBases.add(baseIdOf(id));
129
120
  const inject = options.injectSummaries ?? true;
130
121
  const firstUserIndex = messages.findIndex(
131
122
  (message) => message.role === "user"
132
123
  );
133
- const baseIndexById = /* @__PURE__ */ new Map();
124
+ const indexById = /* @__PURE__ */ new Map();
134
125
  const summaryIndexById = /* @__PURE__ */ new Map();
135
126
  messages.forEach((message, index) => {
136
- const base = baseIdOf(message.id);
137
- const existing = baseIndexById.get(base);
138
- if (existing === void 0 || index < existing)
139
- baseIndexById.set(base, index);
127
+ indexById.set(message.id, index);
140
128
  if (isRenderedSummaryMessage(message))
141
129
  summaryIndexById.set(message.id, index);
142
130
  });
143
- const anchors = inject ? collectSummaryAnchors(state, baseIndexById, summaryIndexById) : [];
131
+ const anchors = inject ? collectSummaryAnchors(state, indexById, summaryIndexById) : [];
144
132
  return stripOrphanedReasoning(
145
133
  stripOrphanedToolResults(
146
134
  stripOrphanedToolCalls(
147
- rebuildMessages(messages, coveredBases, firstUserIndex, anchors)
135
+ rebuildMessages(messages, covered, firstUserIndex, anchors)
148
136
  )
149
137
  )
150
138
  );
151
139
  }
152
- function collectSummaryAnchors(state, baseIndexById, summaryIndexById) {
140
+ function collectSummaryAnchors(state, indexById, summaryIndexById) {
153
141
  const anchors = [];
154
142
  for (const block of activeBlocks(state)) {
155
143
  const existingIndex = summaryIndexById.get(summaryMessageId(block.blockId));
@@ -164,7 +152,7 @@ function collectSummaryAnchors(state, baseIndexById, summaryIndexById) {
164
152
  }
165
153
  let earliest = null;
166
154
  for (const id of block.effectiveMessageIds) {
167
- const index = baseIndexById.get(baseIdOf(id));
155
+ const index = indexById.get(id);
168
156
  if (index !== void 0 && (earliest === null || index < earliest)) {
169
157
  earliest = index;
170
158
  }
@@ -209,7 +197,7 @@ function pairSafeAnchorIndex(messages, index) {
209
197
  }
210
198
  return safe;
211
199
  }
212
- function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
200
+ function rebuildMessages(messages, covered, firstUserIndex, anchors) {
213
201
  const safeAnchors = anchors.map((anchor) => ({
214
202
  ...anchor,
215
203
  insertAt: pairSafeAnchorIndex(messages, anchor.insertAt)
@@ -227,7 +215,7 @@ function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
227
215
  result.push(messages[index]);
228
216
  continue;
229
217
  }
230
- if (isCovered(messages[index].id, coveredBases)) continue;
218
+ if (covered.has(messages[index].id)) continue;
231
219
  if (isRenderedSummaryMessage(messages[index]) && anchoredSummaryIds.has(messages[index].id))
232
220
  continue;
233
221
  result.push(messages[index]);
@@ -293,8 +281,6 @@ function stripOrphanedReasoning(messages) {
293
281
  // src/sync.ts
294
282
  function syncBlocks(messages, state) {
295
283
  const presentIds = new Set(messages.map((message) => message.id));
296
- const presentBases = /* @__PURE__ */ new Set();
297
- for (const message of messages) presentBases.add(baseIdOf(message.id));
298
284
  const deactivated = [];
299
285
  const result = {
300
286
  blocks: state.blocks.map((block) => ({
@@ -344,7 +330,7 @@ function syncBlocks(messages, state) {
344
330
  continue;
345
331
  }
346
332
  block.active = true;
347
- const stillPresent = block.effectiveMessageIds.some((id) => presentBases.has(baseIdOf(id))) || presentIds.has(summaryMessageId(block.blockId));
333
+ const stillPresent = block.effectiveMessageIds.some((id) => presentIds.has(id)) || presentIds.has(summaryMessageId(block.blockId));
348
334
  if (!stillPresent) {
349
335
  block.active = false;
350
336
  deactivated.push(block.blockId);
@@ -1262,12 +1248,7 @@ function hasMediaPayload(msg) {
1262
1248
  if (m.rawOpenaiContent != null) return true;
1263
1249
  if (Array.isArray(m.rawOpenaiContentParts) && m.rawOpenaiContentParts.length > 0)
1264
1250
  return true;
1265
- const ab = m.rawAnthropicBlock;
1266
- if (isObjWith(ab, "type", "image")) return true;
1267
- if (isObjWith(ab, "type", "tool_result")) {
1268
- const content = ab.content;
1269
- if (Array.isArray(content)) return content.some((p) => !isObjWith(p, "type", "text"));
1270
- }
1251
+ if (isObjWith(m.rawAnthropicBlock, "type", "image")) return true;
1271
1252
  const item = m.rawResponsesItem;
1272
1253
  if (isObjWith(item, "type", "input_image")) return true;
1273
1254
  if (item && typeof item === "object") {
@@ -1631,10 +1612,8 @@ function resolveBoundaries(input) {
1631
1612
  if (endAnchor.snapped) snappedBoundaries.push(endAnchor.snapped);
1632
1613
  let startIndex = startAnchor.index;
1633
1614
  let endIndex = endAnchor.index;
1634
- let reversedNote;
1635
1615
  if (startIndex > endIndex) {
1636
1616
  [startIndex, endIndex] = [endIndex, startIndex];
1637
- reversedNote = `note: refs were given reversed (${start.raw}\u2192${end.raw}), normalized to ${end.raw}..${start.raw}`;
1638
1617
  }
1639
1618
  const messageIds = [];
1640
1619
  for (let index = startIndex; index <= endIndex; index++) {
@@ -1661,8 +1640,7 @@ function resolveBoundaries(input) {
1661
1640
  nestedBlockIds,
1662
1641
  boundaryKind,
1663
1642
  protectedGaps,
1664
- snappedBoundaries,
1665
- reversedNote
1643
+ snappedBoundaries
1666
1644
  };
1667
1645
  }
1668
1646
  function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
@@ -1687,27 +1665,6 @@ function resolveAnchorIndex(boundary, state, indexByMessageId, endpoint) {
1687
1665
  snapped: `${label}="${boundary.raw}" refers to a message already compressed into an active block \u2014 anchored to the active block covering it instead.`
1688
1666
  };
1689
1667
  }
1690
- const paddedRef = formatPaddedRef(boundary.numericId);
1691
- if (state.hiddenOrphanRefs?.includes(paddedRef)) {
1692
- const neighbor = snapToNeighborVisible(
1693
- state,
1694
- indexByMessageId,
1695
- boundary.numericId,
1696
- endpoint
1697
- );
1698
- if (neighbor !== null) {
1699
- const dir = endpoint === "start" ? "the next visible message after it" : "the previous visible message before it";
1700
- return {
1701
- index: neighbor,
1702
- snapped: `${label}="${boundary.raw}" is a hidden orphan compress call (no matching block) \u2014 snapped to ${dir} instead.`
1703
- };
1704
- }
1705
- throw new BoundaryNotFoundError(
1706
- "consumed",
1707
- endpoint,
1708
- `${label}="${boundary.raw}" is a hidden orphan compress call with no adjacent visible message to anchor to \u2014 run acp_status for current refs.`
1709
- );
1710
- }
1711
1668
  throw new BoundaryNotFoundError(
1712
1669
  "consumed",
1713
1670
  endpoint,
@@ -1784,26 +1741,6 @@ function inheritedContentIds(state, block) {
1784
1741
  }
1785
1742
  return ids;
1786
1743
  }
1787
- function snapToNeighborVisible(state, indexByMessageId, refNumber, endpoint) {
1788
- let bestIndex = null;
1789
- let bestRef = null;
1790
- for (const [rawId, index] of indexByMessageId) {
1791
- const refText = state.messageRefs.byRaw[rawId];
1792
- if (!refText || refText === BLOCKED_REF) continue;
1793
- const ref = refToIndex(refText);
1794
- if (ref === null) continue;
1795
- if (endpoint === "start") {
1796
- if (ref > refNumber && (bestRef === null || ref < bestRef)) {
1797
- bestRef = ref;
1798
- bestIndex = index;
1799
- }
1800
- } else if (ref < refNumber && (bestRef === null || ref > bestRef)) {
1801
- bestRef = ref;
1802
- bestIndex = index;
1803
- }
1804
- }
1805
- return bestIndex;
1806
- }
1807
1744
  function formatPaddedRef(index) {
1808
1745
  return `m${String(index).padStart(5, "0")}`;
1809
1746
  }
@@ -1834,214 +1771,6 @@ function earliestIndexOfIds(ids, indexByMessageId) {
1834
1771
  return earliest;
1835
1772
  }
1836
1773
 
1837
- // src/block-map.ts
1838
- function refNum(ref) {
1839
- const m = ref.match(/\d+/);
1840
- return m ? parseInt(m[0], 10) : 0;
1841
- }
1842
- var M_REF = /^m\d+$/;
1843
- function resolveBlockSpan(block, byRaw) {
1844
- if (block.startRef && block.endRef && M_REF.test(block.startRef) && M_REF.test(block.endRef)) {
1845
- return { startRef: block.startRef, endRef: block.endRef };
1846
- }
1847
- const refs = block.effectiveMessageIds.map((id) => byRaw[id]).filter((r) => typeof r === "string" && r !== "BLOCKED");
1848
- if (refs.length === 0) return null;
1849
- const sorted = [...refs].sort((a, b) => refNum(a) - refNum(b));
1850
- return { startRef: sorted[0], endRef: sorted[sorted.length - 1] };
1851
- }
1852
- function activeBlockSpans(state) {
1853
- const spans = [];
1854
- for (const block of state.blocks) {
1855
- if (!block.active) continue;
1856
- const span = resolveBlockSpan(block, state.messageRefs.byRaw);
1857
- if (!span) continue;
1858
- spans.push({ blockId: block.blockId, tier: block.tier, ...span });
1859
- }
1860
- return spans;
1861
- }
1862
- function formatCreatedBlocks(state, newBlocks) {
1863
- const parts = [];
1864
- for (const block of newBlocks) {
1865
- const span = resolveBlockSpan(block, state.messageRefs.byRaw);
1866
- parts.push(
1867
- span ? `${block.blockId}=${span.startRef}\u2013${span.endRef}` : block.blockId
1868
- );
1869
- }
1870
- return parts.length > 0 ? `blocks: ${parts.join(", ")}` : "";
1871
- }
1872
-
1873
- // src/decompress.ts
1874
- function parseBlockIdArg(arg) {
1875
- const normalized = arg.trim().toLowerCase();
1876
- const refMatch = /^b0*(\d+)$/.exec(normalized);
1877
- if (refMatch && refMatch[1] !== void 0) return `b${refMatch[1]}`;
1878
- const numMatch = /^(\d+)$/.exec(normalized);
1879
- if (numMatch && numMatch[1] !== void 0) return `b${numMatch[1]}`;
1880
- return null;
1881
- }
1882
- function findBlocksOverlappingMessages(state, messageIds) {
1883
- if (messageIds.size === 0) return [];
1884
- const matched = [];
1885
- for (const block of state.blocks) {
1886
- if (!block.active) continue;
1887
- if (block.effectiveMessageIds.some((id) => messageIds.has(id))) {
1888
- matched.push(block);
1889
- }
1890
- }
1891
- return matched.sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));
1892
- }
1893
- function findActiveAncestor(state, blockId) {
1894
- const start = state.blocks.find((b) => b.blockId === blockId);
1895
- if (!start) return null;
1896
- const queue = [...start.directBlockIds];
1897
- const visited = /* @__PURE__ */ new Set();
1898
- while (queue.length > 0) {
1899
- const currentId = queue.shift();
1900
- if (visited.has(currentId)) continue;
1901
- visited.add(currentId);
1902
- const current = state.blocks.find((b) => b.blockId === currentId);
1903
- if (!current) continue;
1904
- if (current.active) return current.blockId;
1905
- for (const ancestorId of current.directBlockIds) {
1906
- if (!visited.has(ancestorId)) queue.push(ancestorId);
1907
- }
1908
- }
1909
- return null;
1910
- }
1911
- function deactivateBlock(state, blockIds, options = {}) {
1912
- const targets = new Set(blockIds);
1913
- const updated = state.blocks.map((block) => {
1914
- if (!targets.has(block.blockId) || !block.active) return block;
1915
- return {
1916
- ...block,
1917
- active: false,
1918
- durationMs: block.durationMs,
1919
- createdAt: block.createdAt
1920
- };
1921
- });
1922
- let final = updated;
1923
- if (options.deep) {
1924
- const visited = /* @__PURE__ */ new Set();
1925
- const queue = [];
1926
- for (const id of blockIds) {
1927
- const block = updated.find((b) => b.blockId === id);
1928
- if (block) queue.push(...block.directBlockIds);
1929
- }
1930
- while (queue.length > 0) {
1931
- const id = queue.shift();
1932
- if (visited.has(id)) continue;
1933
- visited.add(id);
1934
- final = final.map((block) => {
1935
- if (block.blockId !== id) return block;
1936
- queue.push(...block.directBlockIds);
1937
- return block.active ? { ...block, active: false } : block;
1938
- });
1939
- }
1940
- }
1941
- return { ...state, blocks: final };
1942
- }
1943
- function buildRestoredContentPreview(messages, beforeActiveMessageIds, state) {
1944
- const restored = [];
1945
- for (const message of messages) {
1946
- if (!beforeActiveMessageIds.has(message.id)) continue;
1947
- const stillCovered = state.blocks.some(
1948
- (b) => b.active && b.effectiveMessageIds.includes(message.id)
1949
- );
1950
- if (!stillCovered) restored.push(message);
1951
- }
1952
- if (restored.length === 0) return { preview: "", restoredCount: 0 };
1953
- const lines = [];
1954
- let totalLength = 0;
1955
- const MAX_PREVIEW = 2e3;
1956
- const MAX_PER_MESSAGE = 200;
1957
- for (const message of restored) {
1958
- if (totalLength >= MAX_PREVIEW) break;
1959
- const text = message.text ?? "";
1960
- const truncated = text.length > MAX_PER_MESSAGE ? clampPrefix(text, MAX_PER_MESSAGE) + "..." : text;
1961
- const label = message.toolName && message.contentType !== "text" ? `${message.toolName}: ${truncated}` : `[${message.role}] ${truncated}`;
1962
- lines.push(label);
1963
- totalLength += label.length + 1;
1964
- }
1965
- return { preview: lines.join("\n"), restoredCount: restored.length };
1966
- }
1967
- function collectBlockContent(state, block, messages, options = {}) {
1968
- const full = options.full ?? false;
1969
- const targetIds = new Set(block.effectiveMessageIds);
1970
- if (full) {
1971
- const msgs = messages.filter((m) => targetIds.has(m.id));
1972
- if (msgs.length === 0) return { text: "", count: 0 };
1973
- return { text: msgs.map(formatMessage).join("\n\n"), count: msgs.length };
1974
- }
1975
- const nestedChildren = [];
1976
- const nestedCovered = /* @__PURE__ */ new Set();
1977
- for (const childId of block.directBlockIds) {
1978
- const child = state.blocks.find((b) => b.blockId === childId);
1979
- if (!child?.active) continue;
1980
- nestedChildren.push(child);
1981
- for (const id of child.effectiveMessageIds) nestedCovered.add(id);
1982
- }
1983
- const parts = [];
1984
- for (const child of nestedChildren) {
1985
- const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;
1986
- parts.push(`${SUMMARY_HEADER} \u2014 ${label}
1987
- ${child.summary}`);
1988
- }
1989
- let directCount = 0;
1990
- for (const m of messages) {
1991
- if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {
1992
- parts.push(formatMessage(m));
1993
- directCount++;
1994
- }
1995
- }
1996
- const count = directCount + nestedChildren.length;
1997
- if (count === 0) return { text: "", count: 0 };
1998
- return { text: parts.join("\n\n"), count };
1999
- }
2000
- function formatMessage(message) {
2001
- const text = message.text ?? "";
2002
- if (message.toolName && message.contentType !== "text") {
2003
- return `[${message.role} \u2022 ${message.toolName}]
2004
- ${text}`;
2005
- }
2006
- return `[${message.role}]
2007
- ${text}`;
2008
- }
2009
- function numericPart(blockId) {
2010
- const match = /^b(\d+)$/.exec(blockId);
2011
- return match && match[1] !== void 0 ? Number(match[1]) : 0;
2012
- }
2013
- function markBlockRestoredInline(state, blockId) {
2014
- const existing = state.blocks.find((b) => b.blockId === blockId);
2015
- if (!existing) return { state, result: null };
2016
- const blocks = state.blocks.map(
2017
- (block) => block.blockId === blockId ? { ...block, restoredInline: true } : block
2018
- );
2019
- const span = resolveBlockSpan(existing, state.messageRefs.byRaw);
2020
- const result = {
2021
- restored: true,
2022
- blockId,
2023
- ...span ? { restoredStartRef: span.startRef, restoredEndRef: span.endRef } : {}
2024
- };
2025
- return { state: { ...state, blocks }, result };
2026
- }
2027
- function activeAncestorIds(state, blockId) {
2028
- const out = [];
2029
- const visited = /* @__PURE__ */ new Set([blockId]);
2030
- let frontier = [blockId];
2031
- while (frontier.length > 0) {
2032
- const next = [];
2033
- for (const block of state.blocks) {
2034
- if (visited.has(block.blockId)) continue;
2035
- if (!block.directBlockIds.some((id) => frontier.includes(id))) continue;
2036
- visited.add(block.blockId);
2037
- if (block.active) out.push(block.blockId);
2038
- next.push(block.blockId);
2039
- }
2040
- frontier = next;
2041
- }
2042
- return out;
2043
- }
2044
-
2045
1774
  // src/truncate-tools.ts
2046
1775
  var TRUNCATION_MARKER = "[truncated for context space]";
2047
1776
  var DEFAULTS = {
@@ -2226,26 +1955,14 @@ function hideConsumedCompressCalls(state, messages) {
2226
1955
  }
2227
1956
  }
2228
1957
  let hidden = 0;
2229
- const hiddenOrphanRefs = [];
2230
- const rememberHiddenRef = (message) => {
2231
- if (message.toolCallId && allBlockCallIds.has(message.toolCallId)) {
2232
- return;
2233
- }
2234
- const ref = state.messageRefs.byRaw[message.id];
2235
- if (ref && ref !== BLOCKED_REF && !hiddenOrphanRefs.includes(ref)) {
2236
- hiddenOrphanRefs.push(ref);
2237
- }
2238
- };
2239
1958
  const result = [];
2240
1959
  for (const message of messages) {
2241
1960
  if (message.toolName === "compress" && message.contentType === "tool-call" && (!message.toolCallId || !keepCallIds.has(message.toolCallId))) {
2242
1961
  hidden++;
2243
- rememberHiddenRef(message);
2244
1962
  continue;
2245
1963
  }
2246
1964
  if (message.contentType === "tool-result" && message.toolCallId && hiddenCallIds.has(message.toolCallId)) {
2247
1965
  hidden++;
2248
- rememberHiddenRef(message);
2249
1966
  continue;
2250
1967
  }
2251
1968
  if (message.toolName === "compress" && message.contentType === "tool-call" && message.toolCallId && keepCallIds.has(message.toolCallId)) {
@@ -2265,7 +1982,7 @@ function hideConsumedCompressCalls(state, messages) {
2265
1982
  }
2266
1983
  result.push(message);
2267
1984
  }
2268
- return { messages: result, hidden, hiddenOrphanRefs };
1985
+ return { messages: result, hidden };
2269
1986
  }
2270
1987
 
2271
1988
  // src/absorb.ts
@@ -3915,6 +3632,42 @@ function applyMessageFilters(messages, config) {
3915
3632
  return { messages: working, ...tally };
3916
3633
  }
3917
3634
 
3635
+ // src/block-map.ts
3636
+ function refNum(ref) {
3637
+ const m = ref.match(/\d+/);
3638
+ return m ? parseInt(m[0], 10) : 0;
3639
+ }
3640
+ var M_REF = /^m\d+$/;
3641
+ function resolveBlockSpan(block, byRaw) {
3642
+ if (block.startRef && block.endRef && M_REF.test(block.startRef) && M_REF.test(block.endRef)) {
3643
+ return { startRef: block.startRef, endRef: block.endRef };
3644
+ }
3645
+ const refs = block.effectiveMessageIds.map((id) => byRaw[id]).filter((r) => typeof r === "string" && r !== "BLOCKED");
3646
+ if (refs.length === 0) return null;
3647
+ const sorted = [...refs].sort((a, b) => refNum(a) - refNum(b));
3648
+ return { startRef: sorted[0], endRef: sorted[sorted.length - 1] };
3649
+ }
3650
+ function activeBlockSpans(state) {
3651
+ const spans = [];
3652
+ for (const block of state.blocks) {
3653
+ if (!block.active) continue;
3654
+ const span = resolveBlockSpan(block, state.messageRefs.byRaw);
3655
+ if (!span) continue;
3656
+ spans.push({ blockId: block.blockId, tier: block.tier, ...span });
3657
+ }
3658
+ return spans;
3659
+ }
3660
+ function formatCreatedBlocks(state, newBlocks) {
3661
+ const parts = [];
3662
+ for (const block of newBlocks) {
3663
+ const span = resolveBlockSpan(block, state.messageRefs.byRaw);
3664
+ parts.push(
3665
+ span ? `${block.blockId}=${span.startRef}\u2013${span.endRef}` : block.blockId
3666
+ );
3667
+ }
3668
+ return parts.length > 0 ? `blocks: ${parts.join(", ")}` : "";
3669
+ }
3670
+
3918
3671
  // src/render-refs.ts
3919
3672
  function formatTokens(tokens) {
3920
3673
  if (tokens < 1e3) return String(tokens);
@@ -3987,11 +3740,6 @@ function createRenderRefsNode(strategy) {
3987
3740
  }
3988
3741
  var renderRefsNode = createRenderRefsNode("all");
3989
3742
 
3990
- // src/message-kind.ts
3991
- function isToolMessage(message) {
3992
- return message.contentType === "tool-call" || message.contentType === "tool-result";
3993
- }
3994
-
3995
3743
  // src/tool-pairs.ts
3996
3744
  function adjustBoundariesForToolPairs(startIndex, endIndex, messages, maxScan = 20) {
3997
3745
  const callIdsInRange = /* @__PURE__ */ new Set();
@@ -4196,6 +3944,9 @@ function computeIntegrityWithdrawals(messages, foldedIds) {
4196
3944
  function estimateTextTokens(text) {
4197
3945
  return Math.ceil(text.length / 4);
4198
3946
  }
3947
+ function isToolMessage(message) {
3948
+ return message.contentType === "tool-call" || message.contentType === "tool-result";
3949
+ }
4199
3950
  function isSyntheticOrPruned(message, state) {
4200
3951
  if (message.text?.startsWith("[Compressed conversation section]")) return true;
4201
3952
  for (const block of state.blocks) {
@@ -4515,79 +4266,6 @@ function tierActionHint(config, state) {
4515
4266
  }
4516
4267
  return "";
4517
4268
  }
4518
- function requestedRefoldSpan(state, spec) {
4519
- const nums = [];
4520
- for (const ref of [spec.startRef, spec.endRef]) {
4521
- const parsed = parseBoundary(ref);
4522
- if (!parsed) return null;
4523
- if (parsed.kind === "message") {
4524
- nums.push(parsed.numericId);
4525
- continue;
4526
- }
4527
- const block = blockById(state, `b${parsed.numericId}`);
4528
- if (!block) return null;
4529
- const span = resolveBlockSpan(block, state.messageRefs.byRaw);
4530
- if (!span) return null;
4531
- const lo = parseBoundary(span.startRef);
4532
- const hi = parseBoundary(span.endRef);
4533
- if (!lo || !hi) return null;
4534
- nums.push(lo.numericId, hi.numericId);
4535
- }
4536
- return { lo: Math.min(...nums), hi: Math.max(...nums) };
4537
- }
4538
- function evaluateRefold(state, spec) {
4539
- const span = requestedRefoldSpan(state, spec);
4540
- if (!span) return { kind: "blocked", reasons: [] };
4541
- const reasons = [];
4542
- const blocks = [];
4543
- for (const block of state.blocks) {
4544
- if (!block.active) continue;
4545
- const resolved = resolveBlockSpan(block, state.messageRefs.byRaw);
4546
- if (!resolved) {
4547
- reasons.push(`partially covered ${block.blockId}`);
4548
- continue;
4549
- }
4550
- const ownLo = parseBoundary(resolved.startRef)?.numericId;
4551
- const ownHi = parseBoundary(resolved.endRef)?.numericId;
4552
- if (ownLo === void 0 || ownHi === void 0) continue;
4553
- if (ownHi < span.lo || ownLo > span.hi) continue;
4554
- if (!block.restoredInline) {
4555
- reasons.push(`blocked by ${block.blockId} (not restored)`);
4556
- continue;
4557
- }
4558
- if (ownLo < span.lo || ownHi > span.hi) {
4559
- reasons.push(`partially covered ${block.blockId}`);
4560
- continue;
4561
- }
4562
- const blockers = activeAncestorIds(state, block.blockId).filter(
4563
- (ancestorId) => !blockById(state, ancestorId)?.restoredInline
4564
- );
4565
- if (blockers.length > 0) {
4566
- for (const id of blockers)
4567
- reasons.push(`blocked by ${id} (not restored)`);
4568
- continue;
4569
- }
4570
- blocks.push(block);
4571
- }
4572
- const uniqueReasons = [...new Set(reasons)];
4573
- if (uniqueReasons.length > 0)
4574
- return { kind: "blocked", reasons: uniqueReasons };
4575
- if (blocks.length > 0) return { kind: "refold", blocks };
4576
- return { kind: "blocked", reasons: [] };
4577
- }
4578
- function applyRefolds(input) {
4579
- validateSummaryLength(input.spec, input.config.compress);
4580
- const targets = new Set(input.blockIds);
4581
- input.state.blocks = input.state.blocks.map(
4582
- (block) => targets.has(block.blockId) ? {
4583
- ...block,
4584
- summary: input.spec.summary,
4585
- topic: input.spec.topic ?? block.topic,
4586
- runId: input.runId,
4587
- restoredInline: false
4588
- } : block
4589
- );
4590
- }
4591
4269
  function createCore(ports = {}) {
4592
4270
  const countTokens = ports.countTokens ?? defaultCountTokens;
4593
4271
  function applyCompression(input) {
@@ -4597,7 +4275,6 @@ function createCore(ports = {}) {
4597
4275
  let tokensCompressed = 0;
4598
4276
  const errors = [];
4599
4277
  const warnings = [];
4600
- const notes = [];
4601
4278
  const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(
4602
4279
  input.messages,
4603
4280
  input.state,
@@ -4648,18 +4325,6 @@ function createCore(ports = {}) {
4648
4325
  if (resolution.status === "ok") resolvableCount++;
4649
4326
  else if (resolution.status === "unknown") unknownCount++;
4650
4327
  }
4651
- const refoldDecisions = /* @__PURE__ */ new Map();
4652
- const isRefoldCandidate = (resolution) => resolution.status === "consumed" || resolution.status === "ok" && resolution.resolved.boundaryKind !== "block" && resolution.resolved.messageIds.every(
4653
- (id) => preExistingCoverage.has(id)
4654
- );
4655
- for (const [spec, resolution] of classifications) {
4656
- if (isRefoldCandidate(resolution)) {
4657
- refoldDecisions.set(spec, evaluateRefold(state, spec));
4658
- }
4659
- }
4660
- const allRefold = input.ranges.length > 0 && unknownCount === 0 && [...classifications.entries()].every(
4661
- ([spec, resolution]) => isRefoldCandidate(resolution) && refoldDecisions.get(spec)?.kind === "refold"
4662
- );
4663
4328
  const rangeSpans = [];
4664
4329
  for (const [spec, resolution] of classifications) {
4665
4330
  if (resolution.status !== "ok") continue;
@@ -4698,7 +4363,7 @@ function createCore(ports = {}) {
4698
4363
  totalRangeChars += msg?.text?.length ?? 0;
4699
4364
  }
4700
4365
  }
4701
- if (!allRefold && !hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
4366
+ if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
4702
4367
  const diagnostics = refGateDiagnostics(
4703
4368
  state,
4704
4369
  input.ranges.length,
@@ -4707,50 +4372,10 @@ function createCore(ports = {}) {
4707
4372
  const firstConsumed = consumedRanges[0];
4708
4373
  const covering = firstConsumed ? coveringBlockIds(state, firstConsumed) : [];
4709
4374
  const coverDetail = covering.length > 0 ? `its content is already summarized in active block(s) ${covering.join(", ")}${covering.length === 1 ? ` \u2014 use search_context or decompress ${covering[0]} if you need details from it` : ""}` : `its refs no longer point to directly compressible content (stale block ref(s) distilled or consumed by higher-tier blocks)`;
4710
- const refoldReasons = [
4711
- ...new Set(
4712
- consumedRanges.flatMap((spec) => {
4713
- const decision = refoldDecisions.get(spec);
4714
- return decision?.kind === "blocked" ? decision.reasons : [];
4715
- })
4716
- )
4717
- ];
4718
- const refoldSuffix = (reasons) => reasons.length > 0 ? ` Refold blocked: ${reasons.join("; ")}. Restore the affected block(s) inline (decompress with inline:true), then recompressing the same range updates them in place` : "";
4719
- const refoldDetail = refoldSuffix(refoldReasons);
4720
- const okBlockedReasons = [
4721
- ...new Set(
4722
- [...classifications.entries()].flatMap(([spec, resolution]) => {
4723
- if (skipSpecs.has(spec) || resolution.status !== "ok") return [];
4724
- const decision = refoldDecisions.get(spec);
4725
- return decision?.kind === "blocked" ? decision.reasons : [];
4726
- })
4727
- )
4728
- ];
4729
4375
  const danglingRefs = consumedRanges.flatMap(
4730
4376
  (spec) => danglingMessageRefs(state, input.messages, spec)
4731
4377
  );
4732
- let gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref is unknown to this session. Refs are per-session snapshots, assigned once when a message is first rendered; no compress reassigns them, so unknown refs cannot come from an earlier compress in this session. They come from a different generation: a previous session instance (switching model or upstream mid-conversation starts a fresh session whose refs restart at m00001), the generation before a native-compaction rebase (which also resets refs to m00001), or a typo. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? danglingRefs.length > 0 ? `Requested range(s) cannot be anchored (e.g. ${firstConsumed.startRef}..${firstConsumed.endRef}) \u2014 the refs exist in this session's ref map, but the messages they point to are no longer in the visible context and no active block covers them: the message content changed (or the message was filtered out of the view) and now carries a new ref, leaving your old refs dangling. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : `Requested range(s) already compressed (e.g. ${firstConsumed.startRef}..${firstConsumed.endRef}) \u2014 ${coverDetail}${refoldDetail}. Nothing new to compress in that window. ${diagnostics} Continue the task, or run acp_status and target one of the CURRENT compressible ranges it reports.${tierActionHint(input.config, state)}` : countedRanges > 0 ? `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.${refoldSuffix(okBlockedReasons)}` : null;
4733
- if (gateMessage === null) {
4734
- return {
4735
- state: input.state,
4736
- result: {
4737
- blocksCreated: 0,
4738
- tokensCompressed: 0,
4739
- errors: [...classificationErrors],
4740
- warnings: []
4741
- }
4742
- };
4743
- }
4744
- const reversalNotes = [];
4745
- for (const [spec, resolution] of classifications) {
4746
- if (resolution.status === "ok" && !skipSpecs.has(spec)) {
4747
- const note = resolution.resolved.reversedNote;
4748
- if (note) reversalNotes.push(note);
4749
- }
4750
- }
4751
- if (reversalNotes.length > 0) {
4752
- gateMessage += ` ${reversalNotes.join(" ")}`;
4753
- }
4378
+ const gateMessage = resolvableCount === 0 && consumedRanges.length === 0 && unknownCount > 0 ? `None of the ${input.ranges.length} requested range(s) resolved \u2014 every ref is unknown to this session. Refs are per-session snapshots, assigned once when a message is first rendered; no compress reassigns them, so unknown refs cannot come from an earlier compress in this session. They come from a different generation: a previous session instance (switching model or upstream mid-conversation starts a fresh session whose refs restart at m00001), the generation before a native-compaction rebase (which also resets refs to m00001), or a typo. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : consumedRanges.length > 0 ? danglingRefs.length > 0 ? `Requested range(s) cannot be anchored (e.g. ${firstConsumed.startRef}..${firstConsumed.endRef}) \u2014 the refs exist in this session's ref map, but the messages they point to are no longer in the visible context and no active block covers them: the message content changed (or the message was filtered out of the view) and now carries a new ref, leaving your old refs dangling. ${diagnostics} Run acp_status, then call the compress tool again using only the refs it reports.` : `Requested range(s) already compressed (e.g. ${firstConsumed.startRef}..${firstConsumed.endRef}) \u2014 ${coverDetail}. Nothing new to compress in that window. ${diagnostics} Continue the task, or run acp_status and target one of the CURRENT compressible ranges it reports.${tierActionHint(input.config, state)}` : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;
4754
4379
  return {
4755
4380
  state: input.state,
4756
4381
  result: {
@@ -4767,30 +4392,8 @@ function createCore(ports = {}) {
4767
4392
  const resolution = classifications.get(spec);
4768
4393
  if (resolution === void 0) continue;
4769
4394
  if (resolution.status === "consumed") {
4770
- const decision = refoldDecisions.get(spec);
4771
- if (decision?.kind === "refold") {
4772
- try {
4773
- applyRefolds({
4774
- spec,
4775
- state,
4776
- runId,
4777
- config: input.config,
4778
- blockIds: decision.blocks.map((block) => block.blockId)
4779
- });
4780
- blocksCreated += decision.blocks.length;
4781
- } catch (error) {
4782
- errors.push(
4783
- rangeError(
4784
- spec,
4785
- error instanceof Error ? error.message : String(error)
4786
- )
4787
- );
4788
- }
4789
- continue;
4790
- }
4791
- const reasons = decision?.kind === "blocked" ? decision.reasons : [];
4792
4395
  warnings.push(
4793
- `Skipped range (${spec.startRef}..${spec.endRef}) \u2014 already compressed${reasons.length > 0 ? `: ${reasons.join("; ")}` : " (messages consumed by existing block(s))"}; nothing to compress.`
4396
+ `Skipped range (${spec.startRef}..${spec.endRef}) \u2014 already compressed (messages consumed by existing block(s)); nothing to compress.`
4794
4397
  );
4795
4398
  continue;
4796
4399
  }
@@ -4799,8 +4402,6 @@ function createCore(ports = {}) {
4799
4402
  continue;
4800
4403
  }
4801
4404
  warnings.push(...resolution.resolved.snappedBoundaries);
4802
- const note = resolution.resolved.reversedNote;
4803
- if (note) notes.push(note);
4804
4405
  try {
4805
4406
  const outcome = applySingleRange({
4806
4407
  spec,
@@ -4812,7 +4413,7 @@ function createCore(ports = {}) {
4812
4413
  countTokens,
4813
4414
  preExistingCoverage
4814
4415
  });
4815
- blocksCreated += outcome.refolded ? outcome.refolded.length : 1;
4416
+ blocksCreated++;
4816
4417
  tokensCompressed += outcome.tokens;
4817
4418
  warnings.push(...outcome.warnings);
4818
4419
  } catch (error) {
@@ -4834,13 +4435,7 @@ function createCore(ports = {}) {
4834
4435
  }
4835
4436
  return {
4836
4437
  state,
4837
- result: {
4838
- blocksCreated,
4839
- tokensCompressed,
4840
- errors,
4841
- warnings,
4842
- ...notes.length > 0 ? { notes } : {}
4843
- }
4438
+ result: { blocksCreated, tokensCompressed, errors, warnings }
4844
4439
  };
4845
4440
  }
4846
4441
  function processTurn(input) {
@@ -5024,11 +4619,7 @@ var hideCompressCallsNode = {
5024
4619
  name: "hide-compress-calls",
5025
4620
  run(io) {
5026
4621
  const hidden = hideConsumedCompressCalls(io.state, io.messages);
5027
- return {
5028
- ...io,
5029
- messages: hidden.messages,
5030
- state: { ...io.state, hiddenOrphanRefs: hidden.hiddenOrphanRefs }
5031
- };
4622
+ return { ...io, messages: hidden.messages };
5032
4623
  }
5033
4624
  };
5034
4625
  var recommendNode = {
@@ -5286,21 +4877,6 @@ function applySingleRange(input) {
5286
4877
  }
5287
4878
  }
5288
4879
  if (!isBlockBoundary && filteredIds.length === 0 && consumedBlockIds.length > 0) {
5289
- const decision = evaluateRefold(input.state, input.spec);
5290
- if (decision.kind === "refold") {
5291
- applyRefolds({
5292
- spec: input.spec,
5293
- state: input.state,
5294
- runId: input.runId,
5295
- config: input.config,
5296
- blockIds: decision.blocks.map((block2) => block2.blockId)
5297
- });
5298
- return {
5299
- tokens: 0,
5300
- warnings,
5301
- refolded: decision.blocks.map((block2) => block2.blockId)
5302
- };
5303
- }
5304
4880
  const first = consumedBlockIds[0];
5305
4881
  const last = consumedBlockIds[consumedBlockIds.length - 1];
5306
4882
  throw new Error(
@@ -5381,15 +4957,7 @@ function applyPairBoundaryAdjustments(resolved, messages) {
5381
4957
  }
5382
4958
  function validateCompressionRange(input, directMessageIds, consumedBlockCount) {
5383
4959
  const cfg = input.config.compress;
5384
- validateSummaryLength(input.spec, cfg);
5385
- if (directMessageIds.length === 0 && consumedBlockCount === 0) {
5386
- throw new Error(
5387
- "Range contains no compressible messages \u2014 all are already covered by active blocks or protected."
5388
- );
5389
- }
5390
- }
5391
- function validateSummaryLength(spec, cfg) {
5392
- const summary = spec.summary?.trim() ?? "";
4960
+ const summary = input.spec.summary?.trim() ?? "";
5393
4961
  if (summary.length === 0) {
5394
4962
  throw new Error(
5395
4963
  "Summary is empty \u2014 provide a meaningful summary of the compressed range."
@@ -5400,12 +4968,17 @@ function validateSummaryLength(spec, cfg) {
5400
4968
  `Summary too short (${summary.length} chars, min ${cfg.minSummaryLength}). The summary must capture the compressed range's key information.`
5401
4969
  );
5402
4970
  }
5403
- const effectiveMax = spec.summaryMaxChars ?? cfg.maxSummaryLength;
4971
+ const effectiveMax = input.spec.summaryMaxChars ?? cfg.maxSummaryLength;
5404
4972
  if (effectiveMax > 0 && summary.length > effectiveMax) {
5405
4973
  throw new Error(
5406
4974
  `Summary too long (${summary.length} chars, max ${effectiveMax}). Strip noise \u2014 keep critical paths, decisions, errors, and code references. Or pass summaryMaxChars to increase the limit \u2014 don't lose critical info just to fit.`
5407
4975
  );
5408
4976
  }
4977
+ if (directMessageIds.length === 0 && consumedBlockCount === 0) {
4978
+ throw new Error(
4979
+ "Range contains no compressible messages \u2014 all are already covered by active blocks or protected."
4980
+ );
4981
+ }
5409
4982
  }
5410
4983
  function filterProtectedToolMessages(directMessageIds, messages, config) {
5411
4984
  const protectedCallIds = /* @__PURE__ */ new Set();
@@ -5646,7 +5219,7 @@ function computeContextBreakdown(messages, total, growth, countTokens) {
5646
5219
  const tokens = countMessageTokens(msg, count);
5647
5220
  if (msg.text?.startsWith("[Compressed conversation section]")) {
5648
5221
  summaries += tokens;
5649
- } else if (isToolMessage(msg)) {
5222
+ } else if (msg.contentType === "tool-call" || msg.contentType === "tool-result") {
5650
5223
  tool += tokens;
5651
5224
  } else if (msg.role === "system") {
5652
5225
  system += tokens;
@@ -5678,8 +5251,7 @@ function cloneState(state) {
5678
5251
  nextRuleId: state.nextRuleId,
5679
5252
  terminalStreak: state.terminalStreak,
5680
5253
  nextBlockId: state.nextBlockId,
5681
- nextRunId: state.nextRunId,
5682
- hiddenOrphanRefs: state.hiddenOrphanRefs ? [...state.hiddenOrphanRefs] : void 0
5254
+ nextRunId: state.nextRunId
5683
5255
  };
5684
5256
  }
5685
5257
  function scoreRelevance(block, terms) {
@@ -5832,7 +5404,7 @@ var leanPack = {
5832
5404
  `Compress consumed history with compress: finished tool outputs, dead-end exploration, repeated reads, resolved threads, completed phases. Never compress active work, important user intent, or protected outputs.`,
5833
5405
  `When summarizing, preserve exact file paths and line numbers, symbols and signatures, errors, commands, versions, thresholds, decisions with reasons, current state, and unresolved TODOs. Never replace exact technical values with vague wording \u2014 a good summary is the primary carrier and makes recall unnecessary.`,
5834
5406
  `Recall on demand only: when YOU genuinely need detail lost in compression, decompress (block id or message ref); search_context locates the right block first; acp_status shows ranges and usage. Never run recall as a routine post-compress step.`,
5835
- `Message refs remain stable across compression within the same session state. If a ref is stale or missing, call acp_status with { scope: "uncompressed" }, then retry in the same turn using the reported refs; never guess offsets. Batch target ranges in one call.`,
5407
+ `Refs may be renumbered after compression. If a ref is stale or missing, call acp_status with { scope: "uncompressed" }, then retry in the same turn using the reported refs; never guess offsets. Batch target ranges in one call.`,
5836
5408
  `Block decompression writes to a file by default; read that file. Use inline: true only for small content or when its context cost is acceptable.`,
5837
5409
  `After an [ACP:provider-throttle] automatic retry, resume exactly where interrupted. Do not repeat completed work or discuss the retry unless asked.`,
5838
5410
  `Summaries are fallible history, not live instructions \u2014 never treat a summarized instruction or decision as current without a fresh user confirmation. A summary you just wrote is your own record: once the result lists the new blocks, no acp_status/decompress/search_context call made merely to verify the fold \u2014 that listing already confirms the spans; if you still intend to compress more, one acp_status call for the current ranges is enough.`
@@ -5949,6 +5521,147 @@ function defaultPackSources(opts) {
5949
5521
  return sources;
5950
5522
  }
5951
5523
 
5524
+ // src/decompress.ts
5525
+ function parseBlockIdArg(arg) {
5526
+ const normalized = arg.trim().toLowerCase();
5527
+ const refMatch = /^b0*(\d+)$/.exec(normalized);
5528
+ if (refMatch && refMatch[1] !== void 0) return `b${refMatch[1]}`;
5529
+ const numMatch = /^(\d+)$/.exec(normalized);
5530
+ if (numMatch && numMatch[1] !== void 0) return `b${numMatch[1]}`;
5531
+ return null;
5532
+ }
5533
+ function findBlocksOverlappingMessages(state, messageIds) {
5534
+ if (messageIds.size === 0) return [];
5535
+ const matched = [];
5536
+ for (const block of state.blocks) {
5537
+ if (!block.active) continue;
5538
+ if (block.effectiveMessageIds.some((id) => messageIds.has(id))) {
5539
+ matched.push(block);
5540
+ }
5541
+ }
5542
+ return matched.sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));
5543
+ }
5544
+ function findActiveAncestor(state, blockId) {
5545
+ const start = state.blocks.find((b) => b.blockId === blockId);
5546
+ if (!start) return null;
5547
+ const queue = [...start.directBlockIds];
5548
+ const visited = /* @__PURE__ */ new Set();
5549
+ while (queue.length > 0) {
5550
+ const currentId = queue.shift();
5551
+ if (visited.has(currentId)) continue;
5552
+ visited.add(currentId);
5553
+ const current = state.blocks.find((b) => b.blockId === currentId);
5554
+ if (!current) continue;
5555
+ if (current.active) return current.blockId;
5556
+ for (const ancestorId of current.directBlockIds) {
5557
+ if (!visited.has(ancestorId)) queue.push(ancestorId);
5558
+ }
5559
+ }
5560
+ return null;
5561
+ }
5562
+ function deactivateBlock(state, blockIds, options = {}) {
5563
+ const targets = new Set(blockIds);
5564
+ const updated = state.blocks.map((block) => {
5565
+ if (!targets.has(block.blockId) || !block.active) return block;
5566
+ return {
5567
+ ...block,
5568
+ active: false,
5569
+ durationMs: block.durationMs,
5570
+ createdAt: block.createdAt
5571
+ };
5572
+ });
5573
+ let final = updated;
5574
+ if (options.deep) {
5575
+ const visited = /* @__PURE__ */ new Set();
5576
+ const queue = [];
5577
+ for (const id of blockIds) {
5578
+ const block = updated.find((b) => b.blockId === id);
5579
+ if (block) queue.push(...block.directBlockIds);
5580
+ }
5581
+ while (queue.length > 0) {
5582
+ const id = queue.shift();
5583
+ if (visited.has(id)) continue;
5584
+ visited.add(id);
5585
+ final = final.map((block) => {
5586
+ if (block.blockId !== id) return block;
5587
+ queue.push(...block.directBlockIds);
5588
+ return block.active ? { ...block, active: false } : block;
5589
+ });
5590
+ }
5591
+ }
5592
+ return { ...state, blocks: final };
5593
+ }
5594
+ function buildRestoredContentPreview(messages, beforeActiveMessageIds, state) {
5595
+ const restored = [];
5596
+ for (const message of messages) {
5597
+ if (!beforeActiveMessageIds.has(message.id)) continue;
5598
+ const stillCovered = state.blocks.some(
5599
+ (b) => b.active && b.effectiveMessageIds.includes(message.id)
5600
+ );
5601
+ if (!stillCovered) restored.push(message);
5602
+ }
5603
+ if (restored.length === 0) return { preview: "", restoredCount: 0 };
5604
+ const lines = [];
5605
+ let totalLength = 0;
5606
+ const MAX_PREVIEW = 2e3;
5607
+ const MAX_PER_MESSAGE = 200;
5608
+ for (const message of restored) {
5609
+ if (totalLength >= MAX_PREVIEW) break;
5610
+ const text = message.text ?? "";
5611
+ const truncated = text.length > MAX_PER_MESSAGE ? clampPrefix(text, MAX_PER_MESSAGE) + "..." : text;
5612
+ const label = message.toolName && message.contentType !== "text" ? `${message.toolName}: ${truncated}` : `[${message.role}] ${truncated}`;
5613
+ lines.push(label);
5614
+ totalLength += label.length + 1;
5615
+ }
5616
+ return { preview: lines.join("\n"), restoredCount: restored.length };
5617
+ }
5618
+ function collectBlockContent(state, block, messages, options = {}) {
5619
+ const full = options.full ?? false;
5620
+ const targetIds = new Set(block.effectiveMessageIds);
5621
+ if (full) {
5622
+ const msgs = messages.filter((m) => targetIds.has(m.id));
5623
+ if (msgs.length === 0) return { text: "", count: 0 };
5624
+ return { text: msgs.map(formatMessage).join("\n\n"), count: msgs.length };
5625
+ }
5626
+ const nestedChildren = [];
5627
+ const nestedCovered = /* @__PURE__ */ new Set();
5628
+ for (const childId of block.directBlockIds) {
5629
+ const child = state.blocks.find((b) => b.blockId === childId);
5630
+ if (!child?.active) continue;
5631
+ nestedChildren.push(child);
5632
+ for (const id of child.effectiveMessageIds) nestedCovered.add(id);
5633
+ }
5634
+ const parts = [];
5635
+ for (const child of nestedChildren) {
5636
+ const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;
5637
+ parts.push(`${SUMMARY_HEADER} \u2014 ${label}
5638
+ ${child.summary}`);
5639
+ }
5640
+ let directCount = 0;
5641
+ for (const m of messages) {
5642
+ if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {
5643
+ parts.push(formatMessage(m));
5644
+ directCount++;
5645
+ }
5646
+ }
5647
+ const count = directCount + nestedChildren.length;
5648
+ if (count === 0) return { text: "", count: 0 };
5649
+ return { text: parts.join("\n\n"), count };
5650
+ }
5651
+ function formatMessage(message) {
5652
+ const text = message.text ?? "";
5653
+ if (message.toolName && message.contentType !== "text") {
5654
+ return `[${message.role} \u2022 ${message.toolName}]
5655
+ ${text}`;
5656
+ }
5657
+ return `[${message.role}]
5658
+ ${text}`;
5659
+ }
5660
+ function numericPart(blockId) {
5661
+ const match = /^b(\d+)$/.exec(blockId);
5662
+ return match && match[1] !== void 0 ? Number(match[1]) : 0;
5663
+ }
5664
+
5952
5665
  // src/report.ts
5953
5666
  function formatTokens2(n) {
5954
5667
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -5973,16 +5686,14 @@ function tierLabel(block) {
5973
5686
  }
5974
5687
  function tierBreakdown(blocks, countTokens) {
5975
5688
  const tierTokens = {};
5976
- const tierCounts = {};
5977
5689
  for (const block of blocks) {
5978
5690
  tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);
5979
- tierCounts[block.tier] = (tierCounts[block.tier] ?? 0) + 1;
5980
5691
  }
5981
5692
  const tiers = Object.keys(tierTokens).map(Number);
5982
5693
  if (tiers.length <= 1) return null;
5983
5694
  const parts = [];
5984
5695
  for (const tier of [1, 2, 3]) {
5985
- if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])} (${tierCounts[tier]} blocks)`);
5696
+ if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])}`);
5986
5697
  }
5987
5698
  return parts.join(" | ");
5988
5699
  }
@@ -6008,9 +5719,8 @@ function collectVisible(messages, state, countTokens) {
6008
5719
  const ref = refForRaw(state.messageRefs, message.id);
6009
5720
  if (!ref) return;
6010
5721
  const tokens = countMessageTokens(message, countTokens);
6011
- const isTool = isToolMessage(message);
6012
- const tool = isTool ? message.toolName ?? (message.toolCallId ? toolCallNames.get(message.toolCallId) : void 0) ?? "tool" : "text";
6013
- if (tokens > 0) visible.push({ ref, tokens, tool, isTool, index });
5722
+ const tool = isToolMessage(message) ? message.toolName ?? (message.toolCallId ? toolCallNames.get(message.toolCallId) : void 0) ?? "tool" : "text";
5723
+ if (tokens > 0) visible.push({ ref, tokens, tool, index });
6014
5724
  });
6015
5725
  return { visible, summaryTokens };
6016
5726
  }
@@ -6053,8 +5763,8 @@ function renderOverview(visible, summaryTokens, blocks, state, countTokens, limi
6053
5763
  toolTypeMap.set(message.tool, (toolTypeMap.get(message.tool) ?? 0) + message.tokens);
6054
5764
  }
6055
5765
  const topTool = [...toolTypeMap.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
6056
- const totalTool = visible.filter((m) => m.isTool).reduce((sum, m) => sum + m.tokens, 0);
6057
- const totalText = visible.filter((m) => !m.isTool).reduce((sum, m) => sum + m.tokens, 0);
5766
+ const totalTool = visible.filter((m) => m.tool !== "text").reduce((sum, m) => sum + m.tokens, 0);
5767
+ const totalText = visible.filter((m) => m.tool === "text").reduce((sum, m) => sum + m.tokens, 0);
6058
5768
  const total = summaryTokens + totalTool + totalText;
6059
5769
  lines.push("CONTEXT BREAKDOWN");
6060
5770
  lines.push(
@@ -6090,11 +5800,6 @@ function renderOverview(visible, summaryTokens, blocks, state, countTokens, limi
6090
5800
  ` ${block.blockId} (${tierLabel(block)}) ${formatTokens2(eff)}\u2192${formatTokens2(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs "${topic}"`
6091
5801
  );
6092
5802
  }
6093
- if (blocks.length > limit) {
6094
- lines.push(
6095
- ` ... and ${blocks.length - limit} more blocks not shown (scope:"compressed", limit:${blocks.length} for full list)`
6096
- );
6097
- }
6098
5803
  }
6099
5804
  lines.push("");
6100
5805
  lines.push(
@@ -7777,11 +7482,7 @@ function build(text) {
7777
7482
  }
7778
7483
  function docFeatures(text) {
7779
7484
  const hit = cache.get(text);
7780
- if (hit) {
7781
- cache.delete(text);
7782
- cache.set(text, hit);
7783
- return hit;
7784
- }
7485
+ if (hit) return hit;
7785
7486
  const f = build(text);
7786
7487
  if (text.length > 0 && text.length <= capChars) {
7787
7488
  while (cachedChars + text.length > capChars && cache.size > 0) {
@@ -7892,8 +7593,8 @@ var hybridAlgorithm = {
7892
7593
  score(docs, query) {
7893
7594
  const bm = bm25Algorithm.score(docs, query);
7894
7595
  const fz = fuzzyAlgorithm.score(docs, query);
7895
- const maxBm = bm.reduce((m, r) => r.score > m ? r.score : m, 1e-9);
7896
- const maxFz = fz.reduce((m, r) => r.score > m ? r.score : m, 1e-9);
7596
+ const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);
7597
+ const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);
7897
7598
  const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));
7898
7599
  const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));
7899
7600
  return docs.map((d) => ({
@@ -8240,7 +7941,6 @@ export {
8240
7941
  TIER3_CONDENSE_RULES,
8241
7942
  VERBOSITY_LEVELS,
8242
7943
  VIABLE_RANGE_MIN_TOKENS,
8243
- activeAncestorIds,
8244
7944
  activeBlockSpans,
8245
7945
  activeBlocks,
8246
7946
  addRule,
@@ -8258,7 +7958,6 @@ export {
8258
7958
  applySectionOverrides,
8259
7959
  applySteeringToPrompt,
8260
7960
  assignRefs,
8261
- baseIdOf,
8262
7961
  blockById,
8263
7962
  blockDocs,
8264
7963
  blockVisibleInRange,
@@ -8335,7 +8034,6 @@ export {
8335
8034
  imageShrinksForRef,
8336
8035
  indexToRef,
8337
8036
  isAbsorbCandidate,
8338
- isCovered,
8339
8037
  isImageFullRestored,
8340
8038
  isMessageLatestProtected,
8341
8039
  isMessageProtected,
@@ -8343,7 +8041,6 @@ export {
8343
8041
  isRetrievedMessage,
8344
8042
  isStoredPlaceholderText,
8345
8043
  isSummaryMessageId,
8346
- isToolMessage,
8347
8044
  isValidPackName,
8348
8045
  jsonFoldPlugin,
8349
8046
  leanPack,
@@ -8353,7 +8050,6 @@ export {
8353
8050
  listSearchAlgorithms,
8354
8051
  logSelectPlugin,
8355
8052
  makeIO,
8356
- markBlockRestoredInline,
8357
8053
  matchSession,
8358
8054
  matchToolPattern,
8359
8055
  messageDocs,