acp-kernel 0.0.89-pr.415.269 → 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();
@@ -4192,26 +3940,13 @@ function computeIntegrityWithdrawals(messages, foldedIds) {
4192
3940
  };
4193
3941
  }
4194
3942
 
4195
- // src/segment.ts
4196
- function segmentGroups(items) {
4197
- const groups = [];
4198
- let cur = null;
4199
- for (const item of items) {
4200
- if (cur !== null && (item.isUser && cur.length >= 3 || item.gapBefore)) {
4201
- groups.push(cur);
4202
- cur = null;
4203
- }
4204
- if (cur === null) cur = [item];
4205
- else cur.push(item);
4206
- }
4207
- if (cur !== null) groups.push(cur);
4208
- return groups;
4209
- }
4210
-
4211
3943
  // src/recommend.ts
4212
3944
  function estimateTextTokens(text) {
4213
3945
  return Math.ceil(text.length / 4);
4214
3946
  }
3947
+ function isToolMessage(message) {
3948
+ return message.contentType === "tool-call" || message.contentType === "tool-result";
3949
+ }
4215
3950
  function isSyntheticOrPruned(message, state) {
4216
3951
  if (message.text?.startsWith("[Compressed conversation section]")) return true;
4217
3952
  for (const block of state.blocks) {
@@ -4325,37 +4060,41 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
4325
4060
  compressibleMsgs = kept;
4326
4061
  }
4327
4062
  const compressible = [];
4328
- for (const group of segmentGroups(compressibleMsgs)) {
4329
- const first = group[0];
4330
- const range = {
4331
- startRef: first.ref,
4332
- endRef: first.ref,
4333
- startIndex: first.index,
4334
- endIndex: first.index,
4335
- count: 1,
4336
- tokens: first.tokens,
4337
- chars: first.chars,
4338
- toolPct: first.isTool ? 100 : 0,
4339
- textPct: first.isTool ? 0 : 100,
4340
- userMsgs: first.isUser ? 1 : 0
4341
- };
4342
- for (let i = 1; i < group.length; i++) {
4343
- const info = group[i];
4344
- range.endRef = info.ref;
4345
- range.endIndex = info.index;
4346
- range.count++;
4347
- range.tokens += info.tokens;
4348
- range.chars = (range.chars ?? 0) + info.chars;
4349
- if (info.isUser) range.userMsgs = (range.userMsgs ?? 0) + 1;
4063
+ let cur = null;
4064
+ for (const info of compressibleMsgs) {
4065
+ if (cur && (info.isUser && cur.count >= 3 || info.gapBefore)) {
4066
+ compressible.push(cur);
4067
+ cur = null;
4068
+ }
4069
+ if (!cur) {
4070
+ cur = {
4071
+ startRef: info.ref,
4072
+ endRef: info.ref,
4073
+ count: 1,
4074
+ tokens: info.tokens,
4075
+ chars: info.chars,
4076
+ toolPct: info.isTool ? 100 : 0,
4077
+ textPct: info.isTool ? 0 : 100,
4078
+ userMsgs: info.isUser ? 1 : 0,
4079
+ startIndex: info.index,
4080
+ endIndex: info.index
4081
+ };
4082
+ } else {
4083
+ cur.endRef = info.ref;
4084
+ cur.endIndex = info.index;
4085
+ cur.count++;
4086
+ cur.tokens += info.tokens;
4087
+ cur.chars = (cur.chars ?? 0) + info.chars;
4088
+ if (info.isUser) cur.userMsgs = (cur.userMsgs ?? 0) + 1;
4350
4089
  if (info.isTool) {
4351
- range.toolPct = Math.round((range.toolPct * (range.count - 1) + 100) / range.count);
4090
+ cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);
4352
4091
  } else {
4353
- range.toolPct = Math.round(range.toolPct * (range.count - 1) / range.count);
4092
+ cur.toolPct = Math.round(cur.toolPct * (cur.count - 1) / cur.count);
4354
4093
  }
4355
- range.textPct = 100 - range.toolPct;
4094
+ cur.textPct = 100 - cur.toolPct;
4356
4095
  }
4357
- compressible.push(range);
4358
4096
  }
4097
+ if (cur) compressible.push(cur);
4359
4098
  const protectedRanges = [];
4360
4099
  let pcur = null;
4361
4100
  for (const info of protectedMsgs) {
@@ -4527,79 +4266,6 @@ function tierActionHint(config, state) {
4527
4266
  }
4528
4267
  return "";
4529
4268
  }
4530
- function requestedRefoldSpan(state, spec) {
4531
- const nums = [];
4532
- for (const ref of [spec.startRef, spec.endRef]) {
4533
- const parsed = parseBoundary(ref);
4534
- if (!parsed) return null;
4535
- if (parsed.kind === "message") {
4536
- nums.push(parsed.numericId);
4537
- continue;
4538
- }
4539
- const block = blockById(state, `b${parsed.numericId}`);
4540
- if (!block) return null;
4541
- const span = resolveBlockSpan(block, state.messageRefs.byRaw);
4542
- if (!span) return null;
4543
- const lo = parseBoundary(span.startRef);
4544
- const hi = parseBoundary(span.endRef);
4545
- if (!lo || !hi) return null;
4546
- nums.push(lo.numericId, hi.numericId);
4547
- }
4548
- return { lo: Math.min(...nums), hi: Math.max(...nums) };
4549
- }
4550
- function evaluateRefold(state, spec) {
4551
- const span = requestedRefoldSpan(state, spec);
4552
- if (!span) return { kind: "blocked", reasons: [] };
4553
- const reasons = [];
4554
- const blocks = [];
4555
- for (const block of state.blocks) {
4556
- if (!block.active) continue;
4557
- const resolved = resolveBlockSpan(block, state.messageRefs.byRaw);
4558
- if (!resolved) {
4559
- reasons.push(`partially covered ${block.blockId}`);
4560
- continue;
4561
- }
4562
- const ownLo = parseBoundary(resolved.startRef)?.numericId;
4563
- const ownHi = parseBoundary(resolved.endRef)?.numericId;
4564
- if (ownLo === void 0 || ownHi === void 0) continue;
4565
- if (ownHi < span.lo || ownLo > span.hi) continue;
4566
- if (!block.restoredInline) {
4567
- reasons.push(`blocked by ${block.blockId} (not restored)`);
4568
- continue;
4569
- }
4570
- if (ownLo < span.lo || ownHi > span.hi) {
4571
- reasons.push(`partially covered ${block.blockId}`);
4572
- continue;
4573
- }
4574
- const blockers = activeAncestorIds(state, block.blockId).filter(
4575
- (ancestorId) => !blockById(state, ancestorId)?.restoredInline
4576
- );
4577
- if (blockers.length > 0) {
4578
- for (const id of blockers)
4579
- reasons.push(`blocked by ${id} (not restored)`);
4580
- continue;
4581
- }
4582
- blocks.push(block);
4583
- }
4584
- const uniqueReasons = [...new Set(reasons)];
4585
- if (uniqueReasons.length > 0)
4586
- return { kind: "blocked", reasons: uniqueReasons };
4587
- if (blocks.length > 0) return { kind: "refold", blocks };
4588
- return { kind: "blocked", reasons: [] };
4589
- }
4590
- function applyRefolds(input) {
4591
- validateSummaryLength(input.spec, input.config.compress);
4592
- const targets = new Set(input.blockIds);
4593
- input.state.blocks = input.state.blocks.map(
4594
- (block) => targets.has(block.blockId) ? {
4595
- ...block,
4596
- summary: input.spec.summary,
4597
- topic: input.spec.topic ?? block.topic,
4598
- runId: input.runId,
4599
- restoredInline: false
4600
- } : block
4601
- );
4602
- }
4603
4269
  function createCore(ports = {}) {
4604
4270
  const countTokens = ports.countTokens ?? defaultCountTokens;
4605
4271
  function applyCompression(input) {
@@ -4609,7 +4275,6 @@ function createCore(ports = {}) {
4609
4275
  let tokensCompressed = 0;
4610
4276
  const errors = [];
4611
4277
  const warnings = [];
4612
- const notes = [];
4613
4278
  const protectedMessageIds = input.protectedMessageIds ?? computeProtectedRefs(
4614
4279
  input.messages,
4615
4280
  input.state,
@@ -4660,18 +4325,6 @@ function createCore(ports = {}) {
4660
4325
  if (resolution.status === "ok") resolvableCount++;
4661
4326
  else if (resolution.status === "unknown") unknownCount++;
4662
4327
  }
4663
- const refoldDecisions = /* @__PURE__ */ new Map();
4664
- const isRefoldCandidate = (resolution) => resolution.status === "consumed" || resolution.status === "ok" && resolution.resolved.boundaryKind !== "block" && resolution.resolved.messageIds.every(
4665
- (id) => preExistingCoverage.has(id)
4666
- );
4667
- for (const [spec, resolution] of classifications) {
4668
- if (isRefoldCandidate(resolution)) {
4669
- refoldDecisions.set(spec, evaluateRefold(state, spec));
4670
- }
4671
- }
4672
- const allRefold = input.ranges.length > 0 && unknownCount === 0 && [...classifications.entries()].every(
4673
- ([spec, resolution]) => isRefoldCandidate(resolution) && refoldDecisions.get(spec)?.kind === "refold"
4674
- );
4675
4328
  const rangeSpans = [];
4676
4329
  for (const [spec, resolution] of classifications) {
4677
4330
  if (resolution.status !== "ok") continue;
@@ -4710,7 +4363,7 @@ function createCore(ports = {}) {
4710
4363
  totalRangeChars += msg?.text?.length ?? 0;
4711
4364
  }
4712
4365
  }
4713
- if (!allRefold && !hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
4366
+ if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {
4714
4367
  const diagnostics = refGateDiagnostics(
4715
4368
  state,
4716
4369
  input.ranges.length,
@@ -4719,50 +4372,10 @@ function createCore(ports = {}) {
4719
4372
  const firstConsumed = consumedRanges[0];
4720
4373
  const covering = firstConsumed ? coveringBlockIds(state, firstConsumed) : [];
4721
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)`;
4722
- const refoldReasons = [
4723
- ...new Set(
4724
- consumedRanges.flatMap((spec) => {
4725
- const decision = refoldDecisions.get(spec);
4726
- return decision?.kind === "blocked" ? decision.reasons : [];
4727
- })
4728
- )
4729
- ];
4730
- 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` : "";
4731
- const refoldDetail = refoldSuffix(refoldReasons);
4732
- const okBlockedReasons = [
4733
- ...new Set(
4734
- [...classifications.entries()].flatMap(([spec, resolution]) => {
4735
- if (skipSpecs.has(spec) || resolution.status !== "ok") return [];
4736
- const decision = refoldDecisions.get(spec);
4737
- return decision?.kind === "blocked" ? decision.reasons : [];
4738
- })
4739
- )
4740
- ];
4741
4375
  const danglingRefs = consumedRanges.flatMap(
4742
4376
  (spec) => danglingMessageRefs(state, input.messages, spec)
4743
4377
  );
4744
- 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;
4745
- if (gateMessage === null) {
4746
- return {
4747
- state: input.state,
4748
- result: {
4749
- blocksCreated: 0,
4750
- tokensCompressed: 0,
4751
- errors: [...classificationErrors],
4752
- warnings: []
4753
- }
4754
- };
4755
- }
4756
- const reversalNotes = [];
4757
- for (const [spec, resolution] of classifications) {
4758
- if (resolution.status === "ok" && !skipSpecs.has(spec)) {
4759
- const note = resolution.resolved.reversedNote;
4760
- if (note) reversalNotes.push(note);
4761
- }
4762
- }
4763
- if (reversalNotes.length > 0) {
4764
- gateMessage += ` ${reversalNotes.join(" ")}`;
4765
- }
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.`;
4766
4379
  return {
4767
4380
  state: input.state,
4768
4381
  result: {
@@ -4779,30 +4392,8 @@ function createCore(ports = {}) {
4779
4392
  const resolution = classifications.get(spec);
4780
4393
  if (resolution === void 0) continue;
4781
4394
  if (resolution.status === "consumed") {
4782
- const decision = refoldDecisions.get(spec);
4783
- if (decision?.kind === "refold") {
4784
- try {
4785
- applyRefolds({
4786
- spec,
4787
- state,
4788
- runId,
4789
- config: input.config,
4790
- blockIds: decision.blocks.map((block) => block.blockId)
4791
- });
4792
- blocksCreated += decision.blocks.length;
4793
- } catch (error) {
4794
- errors.push(
4795
- rangeError(
4796
- spec,
4797
- error instanceof Error ? error.message : String(error)
4798
- )
4799
- );
4800
- }
4801
- continue;
4802
- }
4803
- const reasons = decision?.kind === "blocked" ? decision.reasons : [];
4804
4395
  warnings.push(
4805
- `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.`
4806
4397
  );
4807
4398
  continue;
4808
4399
  }
@@ -4811,8 +4402,6 @@ function createCore(ports = {}) {
4811
4402
  continue;
4812
4403
  }
4813
4404
  warnings.push(...resolution.resolved.snappedBoundaries);
4814
- const note = resolution.resolved.reversedNote;
4815
- if (note) notes.push(note);
4816
4405
  try {
4817
4406
  const outcome = applySingleRange({
4818
4407
  spec,
@@ -4824,7 +4413,7 @@ function createCore(ports = {}) {
4824
4413
  countTokens,
4825
4414
  preExistingCoverage
4826
4415
  });
4827
- blocksCreated += outcome.refolded ? outcome.refolded.length : 1;
4416
+ blocksCreated++;
4828
4417
  tokensCompressed += outcome.tokens;
4829
4418
  warnings.push(...outcome.warnings);
4830
4419
  } catch (error) {
@@ -4846,13 +4435,7 @@ function createCore(ports = {}) {
4846
4435
  }
4847
4436
  return {
4848
4437
  state,
4849
- result: {
4850
- blocksCreated,
4851
- tokensCompressed,
4852
- errors,
4853
- warnings,
4854
- ...notes.length > 0 ? { notes } : {}
4855
- }
4438
+ result: { blocksCreated, tokensCompressed, errors, warnings }
4856
4439
  };
4857
4440
  }
4858
4441
  function processTurn(input) {
@@ -5036,11 +4619,7 @@ var hideCompressCallsNode = {
5036
4619
  name: "hide-compress-calls",
5037
4620
  run(io) {
5038
4621
  const hidden = hideConsumedCompressCalls(io.state, io.messages);
5039
- return {
5040
- ...io,
5041
- messages: hidden.messages,
5042
- state: { ...io.state, hiddenOrphanRefs: hidden.hiddenOrphanRefs }
5043
- };
4622
+ return { ...io, messages: hidden.messages };
5044
4623
  }
5045
4624
  };
5046
4625
  var recommendNode = {
@@ -5298,21 +4877,6 @@ function applySingleRange(input) {
5298
4877
  }
5299
4878
  }
5300
4879
  if (!isBlockBoundary && filteredIds.length === 0 && consumedBlockIds.length > 0) {
5301
- const decision = evaluateRefold(input.state, input.spec);
5302
- if (decision.kind === "refold") {
5303
- applyRefolds({
5304
- spec: input.spec,
5305
- state: input.state,
5306
- runId: input.runId,
5307
- config: input.config,
5308
- blockIds: decision.blocks.map((block2) => block2.blockId)
5309
- });
5310
- return {
5311
- tokens: 0,
5312
- warnings,
5313
- refolded: decision.blocks.map((block2) => block2.blockId)
5314
- };
5315
- }
5316
4880
  const first = consumedBlockIds[0];
5317
4881
  const last = consumedBlockIds[consumedBlockIds.length - 1];
5318
4882
  throw new Error(
@@ -5393,15 +4957,7 @@ function applyPairBoundaryAdjustments(resolved, messages) {
5393
4957
  }
5394
4958
  function validateCompressionRange(input, directMessageIds, consumedBlockCount) {
5395
4959
  const cfg = input.config.compress;
5396
- validateSummaryLength(input.spec, cfg);
5397
- if (directMessageIds.length === 0 && consumedBlockCount === 0) {
5398
- throw new Error(
5399
- "Range contains no compressible messages \u2014 all are already covered by active blocks or protected."
5400
- );
5401
- }
5402
- }
5403
- function validateSummaryLength(spec, cfg) {
5404
- const summary = spec.summary?.trim() ?? "";
4960
+ const summary = input.spec.summary?.trim() ?? "";
5405
4961
  if (summary.length === 0) {
5406
4962
  throw new Error(
5407
4963
  "Summary is empty \u2014 provide a meaningful summary of the compressed range."
@@ -5412,12 +4968,17 @@ function validateSummaryLength(spec, cfg) {
5412
4968
  `Summary too short (${summary.length} chars, min ${cfg.minSummaryLength}). The summary must capture the compressed range's key information.`
5413
4969
  );
5414
4970
  }
5415
- const effectiveMax = spec.summaryMaxChars ?? cfg.maxSummaryLength;
4971
+ const effectiveMax = input.spec.summaryMaxChars ?? cfg.maxSummaryLength;
5416
4972
  if (effectiveMax > 0 && summary.length > effectiveMax) {
5417
4973
  throw new Error(
5418
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.`
5419
4975
  );
5420
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
+ }
5421
4982
  }
5422
4983
  function filterProtectedToolMessages(directMessageIds, messages, config) {
5423
4984
  const protectedCallIds = /* @__PURE__ */ new Set();
@@ -5658,7 +5219,7 @@ function computeContextBreakdown(messages, total, growth, countTokens) {
5658
5219
  const tokens = countMessageTokens(msg, count);
5659
5220
  if (msg.text?.startsWith("[Compressed conversation section]")) {
5660
5221
  summaries += tokens;
5661
- } else if (isToolMessage(msg)) {
5222
+ } else if (msg.contentType === "tool-call" || msg.contentType === "tool-result") {
5662
5223
  tool += tokens;
5663
5224
  } else if (msg.role === "system") {
5664
5225
  system += tokens;
@@ -5690,8 +5251,7 @@ function cloneState(state) {
5690
5251
  nextRuleId: state.nextRuleId,
5691
5252
  terminalStreak: state.terminalStreak,
5692
5253
  nextBlockId: state.nextBlockId,
5693
- nextRunId: state.nextRunId,
5694
- hiddenOrphanRefs: state.hiddenOrphanRefs ? [...state.hiddenOrphanRefs] : void 0
5254
+ nextRunId: state.nextRunId
5695
5255
  };
5696
5256
  }
5697
5257
  function scoreRelevance(block, terms) {
@@ -5961,6 +5521,147 @@ function defaultPackSources(opts) {
5961
5521
  return sources;
5962
5522
  }
5963
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
+
5964
5665
  // src/report.ts
5965
5666
  function formatTokens2(n) {
5966
5667
  if (!Number.isFinite(n) || n <= 0) return "0";
@@ -5985,16 +5686,14 @@ function tierLabel(block) {
5985
5686
  }
5986
5687
  function tierBreakdown(blocks, countTokens) {
5987
5688
  const tierTokens = {};
5988
- const tierCounts = {};
5989
5689
  for (const block of blocks) {
5990
5690
  tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);
5991
- tierCounts[block.tier] = (tierCounts[block.tier] ?? 0) + 1;
5992
5691
  }
5993
5692
  const tiers = Object.keys(tierTokens).map(Number);
5994
5693
  if (tiers.length <= 1) return null;
5995
5694
  const parts = [];
5996
5695
  for (const tier of [1, 2, 3]) {
5997
- if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])} (${tierCounts[tier]} blocks)`);
5696
+ if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens2(tierTokens[tier])}`);
5998
5697
  }
5999
5698
  return parts.join(" | ");
6000
5699
  }
@@ -6015,19 +5714,13 @@ function collectVisible(messages, state, countTokens) {
6015
5714
  toolCallNames.set(message.toolCallId, message.toolName);
6016
5715
  }
6017
5716
  }
6018
- let pendingGap = false;
6019
5717
  messages.forEach((message, index) => {
5718
+ if (coveredIds.has(message.id)) return;
6020
5719
  const ref = refForRaw(state.messageRefs, message.id);
6021
5720
  if (!ref) return;
6022
5721
  const tokens = countMessageTokens(message, countTokens);
6023
- if (!coveredIds.has(message.id) && tokens > 0) {
6024
- const isTool = isToolMessage(message);
6025
- const tool = isTool ? message.toolName ?? (message.toolCallId ? toolCallNames.get(message.toolCallId) : void 0) ?? "tool" : "text";
6026
- visible.push({ ref, tokens, tool, isTool, index, isUser: message.role === "user", gapBefore: pendingGap });
6027
- pendingGap = false;
6028
- return;
6029
- }
6030
- if (ref !== BLOCKED_REF && tokens > 0) pendingGap = true;
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 });
6031
5724
  });
6032
5725
  return { visible, summaryTokens };
6033
5726
  }
@@ -6046,7 +5739,7 @@ function buildStatusReport(state, messages, countTokens, options = {}) {
6046
5739
  if (view === "messages") {
6047
5740
  return renderMessageDrilldown(visible, toolFilter, sort, limit);
6048
5741
  }
6049
- return renderUncompressedRanges(visible, sort, limit);
5742
+ return renderUncompressedRanges(visible);
6050
5743
  }
6051
5744
  return renderOverview(visible, summaryTokens, activeBlocks2, state, countTokens, limit, options.meta);
6052
5745
  }
@@ -6070,8 +5763,8 @@ function renderOverview(visible, summaryTokens, blocks, state, countTokens, limi
6070
5763
  toolTypeMap.set(message.tool, (toolTypeMap.get(message.tool) ?? 0) + message.tokens);
6071
5764
  }
6072
5765
  const topTool = [...toolTypeMap.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
6073
- const totalTool = visible.filter((m) => m.isTool).reduce((sum, m) => sum + m.tokens, 0);
6074
- 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);
6075
5768
  const total = summaryTokens + totalTool + totalText;
6076
5769
  lines.push("CONTEXT BREAKDOWN");
6077
5770
  lines.push(
@@ -6107,11 +5800,6 @@ function renderOverview(visible, summaryTokens, blocks, state, countTokens, limi
6107
5800
  ` ${block.blockId} (${tierLabel(block)}) ${formatTokens2(eff)}\u2192${formatTokens2(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs "${topic}"`
6108
5801
  );
6109
5802
  }
6110
- if (blocks.length > limit) {
6111
- lines.push(
6112
- ` ... and ${blocks.length - limit} more blocks not shown (scope:"compressed", limit:${blocks.length} for full list)`
6113
- );
6114
- }
6115
5803
  }
6116
5804
  lines.push("");
6117
5805
  lines.push(
@@ -6119,7 +5807,7 @@ function renderOverview(visible, summaryTokens, blocks, state, countTokens, limi
6119
5807
  );
6120
5808
  return lines.join("\n");
6121
5809
  }
6122
- function renderUncompressedRanges(visible, sort, limit) {
5810
+ function renderUncompressedRanges(visible) {
6123
5811
  const lines = [];
6124
5812
  const totalTokens = visible.reduce((s, m) => s + m.tokens, 0);
6125
5813
  lines.push(`UNCOMPRESSED \u2014 ${formatTokens2(totalTokens)} | ${visible.length} visible messages`);
@@ -6128,6 +5816,10 @@ function renderUncompressedRanges(visible, sort, limit) {
6128
5816
  lines.push(" (no uncompressed messages)");
6129
5817
  return lines.join("\n");
6130
5818
  }
5819
+ const refNum2 = (ref) => {
5820
+ const m = ref.match(/\d+/);
5821
+ return m ? parseInt(m[0], 10) : 0;
5822
+ };
6131
5823
  const dominantTool = (toolTokens) => {
6132
5824
  let best = "text";
6133
5825
  let bestN = -1;
@@ -6140,34 +5832,25 @@ function renderUncompressedRanges(visible, sort, limit) {
6140
5832
  return best;
6141
5833
  };
6142
5834
  const merged = [];
6143
- for (const group of segmentGroups(visible)) {
6144
- const first = group[0];
6145
- const r = {
6146
- startRef: first.ref,
6147
- endRef: first.ref,
6148
- startIndex: first.index,
6149
- count: 1,
6150
- tokens: first.tokens,
6151
- toolTokens: /* @__PURE__ */ new Map([[first.tool, first.tokens]])
6152
- };
6153
- for (let i = 1; i < group.length; i++) {
6154
- const m = group[i];
6155
- r.endRef = m.ref;
6156
- r.count += 1;
6157
- r.tokens += m.tokens;
6158
- r.toolTokens.set(m.tool, (r.toolTokens.get(m.tool) ?? 0) + m.tokens);
5835
+ for (const m of visible) {
5836
+ const num = refNum2(m.ref);
5837
+ const last = merged[merged.length - 1];
5838
+ if (last && num === last.startNum + last.count) {
5839
+ last.endRef = m.ref;
5840
+ last.count += 1;
5841
+ last.tokens += m.tokens;
5842
+ last.toolTokens.set(m.tool, (last.toolTokens.get(m.tool) ?? 0) + m.tokens);
5843
+ } else {
5844
+ const toolTokens = /* @__PURE__ */ new Map([[m.tool, m.tokens]]);
5845
+ merged.push({ startRef: m.ref, endRef: m.ref, startNum: num, count: 1, tokens: m.tokens, toolTokens });
6159
5846
  }
6160
- merged.push(r);
6161
5847
  }
6162
- if (sort !== "time") merged.sort((a, b) => b.tokens - a.tokens || a.startIndex - b.startIndex);
6163
- lines.push(`Sorted by ${sort === "time" ? "time" : "size"}`);
6164
- lines.push("");
6165
- for (const r of merged.slice(0, limit)) {
5848
+ for (const r of merged.slice(0, 30)) {
6166
5849
  const range = r.count === 1 ? r.startRef : `${r.startRef}\u2013${r.endRef}`;
6167
5850
  lines.push(` ${range} (${r.count} msgs, ${formatTokens2(r.tokens)}${r.count > 1 ? ` (${Math.round(r.tokens / r.count)}/msg)` : ""}) ${dominantTool(r.toolTokens)}`);
6168
5851
  }
6169
- if (merged.length > limit) {
6170
- lines.push(` ... and ${merged.length - limit} more ranges`);
5852
+ if (merged.length > 30) {
5853
+ lines.push(` ... and ${merged.length - 30} more ranges`);
6171
5854
  }
6172
5855
  return lines.join("\n");
6173
5856
  }
@@ -7799,11 +7482,7 @@ function build(text) {
7799
7482
  }
7800
7483
  function docFeatures(text) {
7801
7484
  const hit = cache.get(text);
7802
- if (hit) {
7803
- cache.delete(text);
7804
- cache.set(text, hit);
7805
- return hit;
7806
- }
7485
+ if (hit) return hit;
7807
7486
  const f = build(text);
7808
7487
  if (text.length > 0 && text.length <= capChars) {
7809
7488
  while (cachedChars + text.length > capChars && cache.size > 0) {
@@ -7914,8 +7593,8 @@ var hybridAlgorithm = {
7914
7593
  score(docs, query) {
7915
7594
  const bm = bm25Algorithm.score(docs, query);
7916
7595
  const fz = fuzzyAlgorithm.score(docs, query);
7917
- const maxBm = bm.reduce((m, r) => r.score > m ? r.score : m, 1e-9);
7918
- 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);
7919
7598
  const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));
7920
7599
  const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));
7921
7600
  return docs.map((d) => ({
@@ -8262,7 +7941,6 @@ export {
8262
7941
  TIER3_CONDENSE_RULES,
8263
7942
  VERBOSITY_LEVELS,
8264
7943
  VIABLE_RANGE_MIN_TOKENS,
8265
- activeAncestorIds,
8266
7944
  activeBlockSpans,
8267
7945
  activeBlocks,
8268
7946
  addRule,
@@ -8280,7 +7958,6 @@ export {
8280
7958
  applySectionOverrides,
8281
7959
  applySteeringToPrompt,
8282
7960
  assignRefs,
8283
- baseIdOf,
8284
7961
  blockById,
8285
7962
  blockDocs,
8286
7963
  blockVisibleInRange,
@@ -8357,7 +8034,6 @@ export {
8357
8034
  imageShrinksForRef,
8358
8035
  indexToRef,
8359
8036
  isAbsorbCandidate,
8360
- isCovered,
8361
8037
  isImageFullRestored,
8362
8038
  isMessageLatestProtected,
8363
8039
  isMessageProtected,
@@ -8365,7 +8041,6 @@ export {
8365
8041
  isRetrievedMessage,
8366
8042
  isStoredPlaceholderText,
8367
8043
  isSummaryMessageId,
8368
- isToolMessage,
8369
8044
  isValidPackName,
8370
8045
  jsonFoldPlugin,
8371
8046
  leanPack,
@@ -8375,7 +8050,6 @@ export {
8375
8050
  listSearchAlgorithms,
8376
8051
  logSelectPlugin,
8377
8052
  makeIO,
8378
- markBlockRestoredInline,
8379
8053
  matchSession,
8380
8054
  matchToolPattern,
8381
8055
  messageDocs,
@@ -8425,7 +8099,6 @@ export {
8425
8099
  sanitizePackSurface,
8426
8100
  searchBlocks,
8427
8101
  searchBlocksAsync,
8428
- segmentGroups,
8429
8102
  setDocCacheCap,
8430
8103
  storeCoveredOriginals,
8431
8104
  storeLargeResults,