acp-kernel 0.0.82-pr.342.217 → 0.0.82-pr.345.219
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 +0 -16
- package/dist/index.js +87 -10
- package/dist/index.js.map +1 -1
- package/dist/recommend.d.ts +12 -11
- package/dist/recommend.d.ts.map +1 -1
- package/package.json +1 -1
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
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
activeBlocks,
|
|
3
|
+
advanceSurvival,
|
|
4
|
+
allocateBlockId,
|
|
5
|
+
allocateRunId,
|
|
6
|
+
blockById,
|
|
7
|
+
coveredMessageIds,
|
|
8
|
+
createInitialState,
|
|
9
|
+
highestActiveTier
|
|
10
|
+
} from "./chunk-5TQA7ZQM.js";
|
|
1
11
|
import {
|
|
2
12
|
COMPRESS_PHILOSOPHY,
|
|
3
13
|
HOW_TO_COMPRESS_RULES,
|
|
@@ -17,16 +27,6 @@ import {
|
|
|
17
27
|
thinkingTokenValue,
|
|
18
28
|
viableRanges
|
|
19
29
|
} from "./chunk-VPRKYTHW.js";
|
|
20
|
-
import {
|
|
21
|
-
activeBlocks,
|
|
22
|
-
advanceSurvival,
|
|
23
|
-
allocateBlockId,
|
|
24
|
-
allocateRunId,
|
|
25
|
-
blockById,
|
|
26
|
-
coveredMessageIds,
|
|
27
|
-
createInitialState,
|
|
28
|
-
highestActiveTier
|
|
29
|
-
} from "./chunk-5TQA7ZQM.js";
|
|
30
30
|
|
|
31
31
|
// src/refs.ts
|
|
32
32
|
var REF_WIDTH = 5;
|
|
@@ -2233,6 +2233,27 @@ function computeProtectedRefs(messages, state, config, countTokens = estimateTex
|
|
|
2233
2233
|
}
|
|
2234
2234
|
return result;
|
|
2235
2235
|
}
|
|
2236
|
+
function computeOpenTurnMemberIds(messages) {
|
|
2237
|
+
const resultCallIds = /* @__PURE__ */ new Set();
|
|
2238
|
+
for (const msg of messages) {
|
|
2239
|
+
if (msg.contentType === "tool-result" && typeof msg.toolCallId === "string") {
|
|
2240
|
+
resultCallIds.add(msg.toolCallId);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
const openCallIds = /* @__PURE__ */ new Set();
|
|
2244
|
+
for (const msg of messages) {
|
|
2245
|
+
if (msg.id && msg.role === "assistant" && msg.contentType === "tool-call" && typeof msg.toolCallId === "string" && !resultCallIds.has(msg.toolCallId)) {
|
|
2246
|
+
openCallIds.add(msg.id);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (openCallIds.size === 0) return /* @__PURE__ */ new Set();
|
|
2250
|
+
const excluded = /* @__PURE__ */ new Set();
|
|
2251
|
+
for (const group of computeTurnGroups(messages)) {
|
|
2252
|
+
if (!group.some((id) => openCallIds.has(id))) continue;
|
|
2253
|
+
for (const id of group) excluded.add(id);
|
|
2254
|
+
}
|
|
2255
|
+
return excluded;
|
|
2256
|
+
}
|
|
2236
2257
|
function buildCompressibleRanges(messages, state, config, protectedZoneRefs, countTokens = estimateTextTokens) {
|
|
2237
2258
|
let compressibleMsgs = [];
|
|
2238
2259
|
const protectedMsgs = [];
|
|
@@ -2281,6 +2302,20 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
2281
2302
|
skipSinceCompressible = false;
|
|
2282
2303
|
skipSinceProtected = true;
|
|
2283
2304
|
}
|
|
2305
|
+
const openTurnIds = computeOpenTurnMemberIds(messages);
|
|
2306
|
+
if (openTurnIds.size > 0) {
|
|
2307
|
+
let gapPending = false;
|
|
2308
|
+
const kept = [];
|
|
2309
|
+
for (const info of compressibleMsgs) {
|
|
2310
|
+
if (openTurnIds.has(info.id)) {
|
|
2311
|
+
gapPending = true;
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
kept.push(gapPending ? { ...info, gapBefore: true } : info);
|
|
2315
|
+
gapPending = false;
|
|
2316
|
+
}
|
|
2317
|
+
compressibleMsgs = kept;
|
|
2318
|
+
}
|
|
2284
2319
|
const unfoldedIds = computeIntegrityWithdrawals(
|
|
2285
2320
|
messages,
|
|
2286
2321
|
new Set(compressibleMsgs.map((info) => info.id))
|
|
@@ -2298,6 +2333,13 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
2298
2333
|
}
|
|
2299
2334
|
compressibleMsgs = kept;
|
|
2300
2335
|
}
|
|
2336
|
+
const gapCount = compressibleMsgs.reduce(
|
|
2337
|
+
(s, info) => s + (info.gapBefore ? 1 : 0),
|
|
2338
|
+
0
|
|
2339
|
+
);
|
|
2340
|
+
if (gapCount > 0) {
|
|
2341
|
+
compressibleMsgs = refineSegmentsForFoldability(messages, compressibleMsgs);
|
|
2342
|
+
}
|
|
2301
2343
|
const compressible = [];
|
|
2302
2344
|
let cur = null;
|
|
2303
2345
|
for (const info of compressibleMsgs) {
|
|
@@ -2367,6 +2409,41 @@ function buildCompressibleRanges(messages, state, config, protectedZoneRefs, cou
|
|
|
2367
2409
|
protected: protectedRanges
|
|
2368
2410
|
};
|
|
2369
2411
|
}
|
|
2412
|
+
var MAX_REFINEMENT_PASSES = 8;
|
|
2413
|
+
function refineSegmentsForFoldability(messages, infos) {
|
|
2414
|
+
let current = infos;
|
|
2415
|
+
for (let pass = 0; pass < MAX_REFINEMENT_PASSES; pass++) {
|
|
2416
|
+
const segments = [];
|
|
2417
|
+
for (const info of current) {
|
|
2418
|
+
const seg = segments[segments.length - 1];
|
|
2419
|
+
if (seg === void 0 || info.gapBefore) segments.push([info]);
|
|
2420
|
+
else seg.push(info);
|
|
2421
|
+
}
|
|
2422
|
+
const removed = /* @__PURE__ */ new Set();
|
|
2423
|
+
for (const seg of segments) {
|
|
2424
|
+
const withdrawn = computeIntegrityWithdrawals(
|
|
2425
|
+
messages,
|
|
2426
|
+
new Set(seg.map((info) => info.id))
|
|
2427
|
+
).withdrawn;
|
|
2428
|
+
for (const info of seg) {
|
|
2429
|
+
if (withdrawn.has(info.id)) removed.add(info.id);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
if (removed.size === 0) break;
|
|
2433
|
+
let gapPending = false;
|
|
2434
|
+
const kept = [];
|
|
2435
|
+
for (const info of current) {
|
|
2436
|
+
if (removed.has(info.id)) {
|
|
2437
|
+
gapPending = true;
|
|
2438
|
+
continue;
|
|
2439
|
+
}
|
|
2440
|
+
kept.push(gapPending ? { ...info, gapBefore: true } : info);
|
|
2441
|
+
gapPending = false;
|
|
2442
|
+
}
|
|
2443
|
+
current = kept;
|
|
2444
|
+
}
|
|
2445
|
+
return current;
|
|
2446
|
+
}
|
|
2370
2447
|
function mergeBatch(batch) {
|
|
2371
2448
|
const first = batch[0];
|
|
2372
2449
|
const last = batch[batch.length - 1];
|