acp-kernel 0.0.99-pr.490.346 → 0.0.99-pr.491.348
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 +11 -8
- package/dist/boundaries.d.ts.map +1 -1
- package/dist/index.js +13 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/DESIGN.md
CHANGED
|
@@ -260,14 +260,17 @@ Copyright protects _expression_, not ideas, methods, or algorithms (17 USC §102
|
|
|
260
260
|
|
|
261
261
|
`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:
|
|
262
262
|
|
|
263
|
-
### 8.1 First-user-message pin
|
|
263
|
+
### 8.1 First-user-message pin (conditional since #489)
|
|
264
264
|
|
|
265
|
-
The
|
|
265
|
+
The contract is a **property**, not a specific-message guarantee: the rebuilt wire retains a **leading user message** whenever one can lead — strict providers reject conversations with no user message or that start from an assistant. An _uncovered_ first user survives prune via the ordinary covered check anyway; the pin only bites when the first user is **covered by an active block**, and then only conditionally (`firstUserDropSafe`, `src/prune.ts`):
|
|
266
266
|
|
|
267
|
-
- **
|
|
268
|
-
- **
|
|
267
|
+
- **Drop-safe → dropped.** When the earliest message that would survive _after_ the first user (non-covered, non-rendered-summary) is itself a user, the covered first user drops like any other covered message. This covers both #489 trigger shapes: a divergent resend that shifts an earlier-folded message into the first-user slot, and a direct fold of the opening message. Its coverage now takes effect — the payload stops re-sending while its block stays active.
|
|
268
|
+
- **Degenerate → pinned (residual limitation).** When no user would lead after it (the earliest survivor is an assistant/tool message, or nothing survives), the covered first user stays pinned verbatim next to its rendered summary, exactly as before #489. Dropping it there would hand the provider an assistant-led or user-less conversation; the cost is the pre-existing one (payload re-sends while the block covers it).
|
|
269
|
+
|
|
270
|
+
Pre-#489 the pin was **unconditional**: it ran before the covered check for every covered first user. Combined with `syncBlocks` keeping the block active (its base id is present in the pre-prune view by definition), this voided fold coverage whenever a covered message became the view's first user — the fold reported success but the payload re-sent itself verbatim on every turn (#489, found analyzing billion-context#1869). The fix keeps the property, drops the over-guarantee.
|
|
271
|
+
|
|
272
|
+
- **Why:** deliberate since prune's first implementation (v0.0.2) — protecting the leading-user property is part of the contract, not incidental. What changed in #489 is _which_ message carries the property: the property now moves to the earliest surviving user instead of being welded to the specific first message. Regression-tested by `tests/state-prune.test.ts` ("prune drops covered first user when another user survives", "prune keeps covered first user when no user survives behind"), `tests/orphan-fixes.test.ts` ("prune: first user message pruned when covered (no duplication)" — degenerate assistant-led shape), and `tests/regression-first-user-pin.test.ts` (#489 pipeline A/B, both trigger shapes).
|
|
269
273
|
- **Consequences for consumers:**
|
|
270
|
-
-
|
|
271
|
-
-
|
|
272
|
-
- Boundary resolution
|
|
273
|
-
- **Regression-tested by** `tests/state-prune.test.ts` (both the drop and the degenerate-keep shapes), `tests/orphan-fixes.test.ts` ("prune: first user message pruned when covered (no duplication)"), and `tests/issue1476-reid.test.ts` (pinned-echo id/ref stability across post-fold passes).
|
|
274
|
+
- Consumers may now observe byte-level disappearance of the first user message after compression whenever another user message survives behind it; post-compress wire assertions must expect the rendered summary alone in that case. Token-size and prefix-cache estimates should drop the pinned-message allowance for those sessions.
|
|
275
|
+
- Degenerate shapes (assistant-led survivors) still carry the pinned message verbatim next to its summary — estimates must keep the allowance there.
|
|
276
|
+
- Boundary resolution is unchanged: `blockVisibleInRange` (`src/boundaries.ts`) treats a block as present in a range via its rendered summary **or** its earliest surviving raw — still required for the degenerate pinned shape, where the summary anchors _before_ the pinned raw.
|
package/dist/boundaries.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAehE;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;gBAGjC,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,QAAQ,EAAE,OAAO,GAAG,KAAK,EACzB,OAAO,EAAE,MAAM;CAQlB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CAiFf;AAsMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CAIf;
|
|
1
|
+
{"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAehE;AAED;;;;;;GAMG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,UAAU,CAAC;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,KAAK,CAAC;gBAGjC,IAAI,EAAE,SAAS,GAAG,UAAU,EAC5B,QAAQ,EAAE,OAAO,GAAG,KAAK,EACzB,OAAO,EAAE,MAAM;CAQlB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CAiFf;AAsMD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CAIf;AAQD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EACvB,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAcT;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,MAAM,EAAE,EACb,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,GAAG,IAAI,CASf;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAMzE"}
|
package/dist/index.js
CHANGED
|
@@ -209,15 +209,6 @@ function pairSafeAnchorIndex(messages, index) {
|
|
|
209
209
|
}
|
|
210
210
|
return safe;
|
|
211
211
|
}
|
|
212
|
-
function firstUserDropSafe(messages, firstUserIndex, coveredBases) {
|
|
213
|
-
for (let i = firstUserIndex + 1; i < messages.length; i++) {
|
|
214
|
-
const message = messages[i];
|
|
215
|
-
if (isCovered(message.id, coveredBases)) continue;
|
|
216
|
-
if (isRenderedSummaryMessage(message)) continue;
|
|
217
|
-
return message.role === "user";
|
|
218
|
-
}
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
212
|
function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
|
|
222
213
|
const safeAnchors = anchors.map((anchor) => ({
|
|
223
214
|
...anchor,
|
|
@@ -228,16 +219,14 @@ function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
|
|
|
228
219
|
const anchoredSummaryIds = new Set(
|
|
229
220
|
anchors.map((anchor) => summaryMessageId(anchor.blockId))
|
|
230
221
|
);
|
|
222
|
+
const pinFirstUser = firstUserIndex < 0 || !isCovered(messages[firstUserIndex].id, coveredBases) || !firstUserDropSafe(messages, firstUserIndex, coveredBases);
|
|
231
223
|
for (let index = 0; index < messages.length; index++) {
|
|
232
224
|
while (pending.length > 0 && pending[0].insertAt === index) {
|
|
233
225
|
result.push(renderSummary(pending.shift()));
|
|
234
226
|
}
|
|
235
|
-
if (index === firstUserIndex
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
result.push(pinned);
|
|
239
|
-
continue;
|
|
240
|
-
}
|
|
227
|
+
if (pinFirstUser && index === firstUserIndex) {
|
|
228
|
+
result.push(messages[index]);
|
|
229
|
+
continue;
|
|
241
230
|
}
|
|
242
231
|
if (isCovered(messages[index].id, coveredBases)) continue;
|
|
243
232
|
if (isRenderedSummaryMessage(messages[index]) && anchoredSummaryIds.has(messages[index].id))
|
|
@@ -249,6 +238,15 @@ function rebuildMessages(messages, coveredBases, firstUserIndex, anchors) {
|
|
|
249
238
|
}
|
|
250
239
|
return result;
|
|
251
240
|
}
|
|
241
|
+
function firstUserDropSafe(messages, firstUserIndex, coveredBases) {
|
|
242
|
+
for (let i = firstUserIndex + 1; i < messages.length; i++) {
|
|
243
|
+
const message = messages[i];
|
|
244
|
+
if (isCovered(message.id, coveredBases)) continue;
|
|
245
|
+
if (isRenderedSummaryMessage(message)) continue;
|
|
246
|
+
return message.role === "user";
|
|
247
|
+
}
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
252
250
|
function renderSummary(anchor) {
|
|
253
251
|
const body = anchor.summary.trim();
|
|
254
252
|
const topicLine = anchor.topic ? `${SUMMARY_HEADER} \u2014 ${anchor.topic}` : SUMMARY_HEADER;
|