acp-kernel 0.0.82-pr.342.217 → 0.0.82-pr.343.218

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/DESIGN.md CHANGED
@@ -219,19 +219,3 @@ A default `countTokens` ships with the core (word-level + unicode CJK tokenizer,
219
219
  ## 7. Why the algorithm, but not the DCP-derived code, comes here
220
220
 
221
221
  Copyright protects *expression*, not ideas, methods, or algorithms (17 USC §102(b)). The compression *methods* (3-tier, growth cadence, protected filtering) are the author's. This core reimplements them in **fresh expression** — it is not a copy or refactor of DCP-derived files. See [PROVENANCE.md](./PROVENANCE.md) for the per-module origin classification (original-bring / DCP-derived-reimplement / adapter-only).
222
-
223
- ---
224
-
225
- ## 8. Prune Semantics & Wire Invariants
226
-
227
- `prune` rebuilds the visible view every turn: messages covered by active blocks are dropped, and each active block's rendered summary (`[Compressed conversation section]`, id `acp_summary_bN`) is inserted at the block's earliest covered position (pair-safe, per `pairSafeAnchorIndex`). One behavior of the rebuilt view is a load-bearing contract for downstream consumers:
228
-
229
- ### 8.1 First-user-message pin
230
-
231
- The session's **first user message survives prune unconditionally** — even when it is covered by an active block. In `rebuildMessages` (`src/prune.ts`) the pin check runs *before* the covered-by-active-block check, so after compressing a range that includes the first user message, the rebuilt wire contains **both** the rendered summary **and** the first user message verbatim; all other covered messages drop as usual. Only the *first* user message is pinned — later covered user messages are pruned normally.
232
-
233
- - **Why:** strict providers reject conversations with no user message (e.g., Anthropic requires the conversation to start from the user role). Pinning guarantees the rebuilt wire retains at least one user message whenever the input had one. Deliberate since prune's first implementation (v0.0.2) — the ordering of the two checks is part of the contract, not incidental. Regression-tested by `tests/state-prune.test.ts` ("prune preserves first user message even when covered") and `tests/orphan-fixes.test.ts` ("prune: first user message pruned when covered (no duplication)").
234
- - **Consequences for consumers:**
235
- - Consumers cannot assert byte-level disappearance of the first user message after compression; post-compress wire assertions must expect the rendered summary **plus** the pinned message verbatim.
236
- - Token-size and prefix-cache stability estimates must account for the pinned message staying verbatim (it is often the task description and can be long).
237
- - Boundary resolution compensates: `blockVisibleInRange` (`src/boundaries.ts`) treats a block as present in a range via its rendered summary **or** its earliest surviving raw — required precisely because the summary anchors *before* the pinned raw.
package/dist/index.js CHANGED
@@ -2403,18 +2403,30 @@ function mergeRangesToThreshold(ranges, minChars) {
2403
2403
  const result = [];
2404
2404
  let batch = [];
2405
2405
  let batchChars = 0;
2406
- for (const r of ranges) {
2407
- batch.push(r);
2408
- batchChars += rangeChars(r);
2409
- if (batchChars >= minChars) {
2410
- result.push(mergeBatch(batch));
2406
+ const closeBatch = () => {
2407
+ if (batch.length > 0) {
2408
+ if (batchChars >= minChars) result.push(mergeBatch(batch));
2411
2409
  batch = [];
2412
2410
  batchChars = 0;
2413
2411
  }
2412
+ };
2413
+ for (const r of ranges) {
2414
+ const prev = batch[batch.length - 1];
2415
+ if (prev && prev.endIndex !== void 0 && r.startIndex !== void 0 && r.startIndex > prev.endIndex + 1) {
2416
+ closeBatch();
2417
+ }
2418
+ batch.push(r);
2419
+ batchChars += rangeChars(r);
2420
+ if (batchChars >= minChars) closeBatch();
2414
2421
  }
2415
2422
  if (batch.length > 0 && result.length > 0) {
2416
2423
  const prev = result[result.length - 1];
2417
- result[result.length - 1] = mergeBatch([prev, ...batch]);
2424
+ const contiguous = prev.endIndex === void 0 || batch[0].startIndex === void 0 || batch[0].startIndex <= prev.endIndex + 1;
2425
+ if (contiguous) {
2426
+ result[result.length - 1] = mergeBatch([prev, ...batch]);
2427
+ } else if (batchChars >= minChars) {
2428
+ result.push(mergeBatch(batch));
2429
+ }
2418
2430
  }
2419
2431
  return result;
2420
2432
  }