dsh-context-compression-improved 0.5.1 → 0.5.3

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.
Files changed (57) hide show
  1. package/.gitattributes +1 -0
  2. package/CHANGELOG.ja.md +144 -83
  3. package/CHANGELOG.ko.md +143 -82
  4. package/CHANGELOG.md +278 -212
  5. package/CHANGELOG.zh.md +131 -77
  6. package/docs/installation.md +103 -103
  7. package/docs/installation.zh.md +100 -100
  8. package/package.json +1 -1
  9. package/packages/selector/cordis.patch.yml +5 -6
  10. package/packages/selector/lib/advisor-state.js +4 -231
  11. package/packages/selector/lib/client.d.ts +0 -24
  12. package/packages/selector/lib/client.js +6 -501
  13. package/packages/selector/lib/index.d.ts +4 -10
  14. package/packages/selector/lib/index.js +16 -234
  15. package/packages/selector/lib/pruner.d.ts +13 -248
  16. package/packages/selector/lib/pruner.js +148 -552
  17. package/packages/selector/src/client/EstimatorControls.tsx +0 -101
  18. package/packages/selector/src/client/index.ts +0 -17
  19. package/packages/selector/src/client/locales.ts +0 -38
  20. package/packages/selector/src/client/preset-options.ts +3 -2
  21. package/packages/selector/src/client/settings-section.tsx +8 -17
  22. package/packages/selector/src/index.ts +24 -271
  23. package/packages/selector/src/profiles.ts +4 -27
  24. package/packages/selector/src/pruner/state.ts +2 -25
  25. package/packages/selector/src/pruner.ts +75 -403
  26. package/packages/selector/src/runtime/audit.ts +27 -21
  27. package/packages/selector/src/runtime/config.ts +6 -32
  28. package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -188
  29. package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +149 -133
  30. package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -419
  31. package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
  32. package/packages/selector/src/runtime/types.ts +0 -17
  33. package/packages/selector/tests/advisor-report.host.spec.ts +223 -223
  34. package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
  35. package/packages/selector/tests/public/package-contract.client.spec.ts +20 -0
  36. package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
  37. package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -272
  38. package/packages/selector/tests/runtime/advisor.spec.ts +226 -226
  39. package/packages/selector/tests/runtime/audit.spec.ts +35 -21
  40. package/packages/selector/tests/runtime/char-basis.spec.ts +30 -30
  41. package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
  42. package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
  43. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
  44. package/packages/selector/tests/settings-seat.client.spec.ts +45 -14
  45. package/scripts/toolclass-corpus-replay.mjs +281 -281
  46. package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
  47. package/packages/selector/src/client/review-scope.ts +0 -16
  48. package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
  49. package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
  50. package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
  51. package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
  52. package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
  53. package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
  54. package/packages/selector/tests/review-routes.host.spec.ts +0 -290
  55. package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
  56. package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
  57. package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
@@ -1,4 +1,4 @@
1
- import { C as DEFAULT_CUSTOM_COMPRESSION_POLICY, D as COMPRESSION_PROFILES, E as deepFreeze, S as CustomCompressionPolicySchema, T as assertNever, _ as isCompressionProfile, a as registerReviewPruner, b as resolveConfig, c as ReviewQueue, d as ContextCompressionSettingsSchema, f as DEFAULTS, g as codePointLength, h as charsToTokens, i as recordScore, l as AUTO_COMPACT_THRESHOLD_LIMITS, m as charsForTokens, n as invalidateOnTaskChange, p as PRUNE_MARKER, r as recordRecertified, s as sharedReviewStore, t as getAdvisorState, u as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, v as isValidAutoCompactThresholdPercent, w as resolveCustomPolicy, x as resolvePolicy, y as parseContextCompressionSettings } from "./advisor-state.js";
1
+ import { C as COMPRESSION_PROFILES, S as deepFreeze, _ as resolvePolicy, a as AUTO_COMPACT_THRESHOLD_LIMITS, b as resolveCustomPolicy, c as DEFAULTS, d as charsToTokens, f as codePointLength, g as resolveConfig, h as parseContextCompressionSettings, i as recordScore, l as PRUNE_MARKER, m as isValidAutoCompactThresholdPercent, n as invalidateOnTaskChange, o as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, p as isCompressionProfile, r as recordRecertified, s as ContextCompressionSettingsSchema, t as getAdvisorState, u as charsForTokens, v as CustomCompressionPolicySchema, x as assertNever, y as DEFAULT_CUSTOM_COMPRESSION_POLICY } from "./advisor-state.js";
2
2
  import { a as validatePublishedTailTrim, i as tailTrimStub, n as tailTrimMessage, o as eventBySeq, r as tailTrimRef, s as sessionEvents, t as parseTailTrimRef } from "./tail-trim.js";
3
3
  import z from "@deepseek-ai/schemastery";
4
4
  import { createHash } from "node:crypto";
@@ -3032,91 +3032,15 @@ var Estimator = class {
3032
3032
  }
3033
3033
  };
3034
3034
  //#endregion
3035
- //#region src/runtime/tokenpilot/dedup.ts
3036
- /**
3037
- * TokenPilot-inspired A1: byte-identical repeated tool-result dedup.
3038
- *
3039
- * Pure helpers behind the ToolResultPruner fresh pass. The per-session table
3040
- * maps a canonical-content SHA-256 to the first surface seq that produced it;
3041
- * later identical results may be replaced with a pointer placeholder that the
3042
- * recovery tool can resolve back to the original full text via the append-only
3043
- * session log. Only hash+seq metadata is stored — never content.
3044
- */
3045
- /** Per-session dedup index with insertion-order eviction. */
3046
- var DedupeTable = class {
3047
- maxEntries;
3048
- entries = /* @__PURE__ */ new Map();
3049
- constructor(maxEntries = 2048) {
3050
- this.maxEntries = maxEntries;
3051
- }
3052
- /** Look up the first occurrence for one canonical hash, if any. */
3053
- get(hash) {
3054
- return this.entries.get(hash);
3055
- }
3056
- /** Record a first occurrence; existing hashes only refresh insertion order. */
3057
- record(hash, entry) {
3058
- if (this.entries.has(hash)) return;
3059
- while (this.entries.size >= this.maxEntries) {
3060
- const oldest = this.entries.keys().next().value;
3061
- if (oldest === void 0) break;
3062
- this.entries.delete(oldest);
3063
- }
3064
- this.entries.set(hash, entry);
3065
- }
3066
- };
3067
- /** Canonicalize tool-result text for hashing. */
3068
- function canonicalizeForDedupe(text, mode) {
3069
- if (mode === "exact") return text;
3070
- return text.replace(/[ \t]+\r?\n/g, "\n").replace(/(^\s+)|(\s+$)/g, "");
3071
- }
3072
- /** SHA-256 hex of the canonicalized text. */
3073
- function dedupeHash(text, mode) {
3074
- return createHash("sha256").update(canonicalizeForDedupe(text, mode), "utf8").digest("hex");
3075
- }
3076
- /** Concatenated text of an all-text content block list; null when rich. */
3077
- function flattenPlainText(content) {
3078
- let text = "";
3079
- for (const block of content) {
3080
- if (block.type !== "text") return void 0;
3081
- text += block.text;
3082
- }
3083
- return text;
3084
- }
3085
- /** Pointer placeholder pointing at the first occurrence's original event. */
3086
- function dedupePlaceholder(entry, originalChars) {
3087
- return [
3088
- `[... identical to the earlier ${entry.toolName} result; first seen at ${entry.sourceRef};`,
3089
- `original_chars=${String(originalChars)};`,
3090
- "retrieve with context_compression_retrieve({\"ref\":\"" + entry.sourceRef + "\",\"start_line\":1}) if the omitted evidence is necessary.]"
3091
- ].join(" ");
3092
- }
3093
- //#endregion
3094
- //#region src/runtime/tokenpilot/proposal.ts
3035
+ //#region src/runtime/tokenpilot/benefit.ts
3095
3036
  /**
3096
- * TokenPilot-inspired R4: benefit model for the human-gated review pipeline.
3097
- *
3098
- * Pure functions only: the classifier needs no I/O, no session state, and no
3099
- * host services, so every decision is unit-testable and audit-replayable.
3100
- *
3101
- * The cost model follows the TokenPilot paper's cache-accounting view: one
3102
- * merged mutation pays a one-time tail KV-cache refill penalty of
3103
- * `(1−α)·tailTokens`, and every later turn recovers the reclaimed tokens at
3104
- * the cache-hit discount `α`:
3105
- *
3106
- * ```
3107
- * R = Σ(tokensBefore − tokensAfter) // net reclaimed tokens
3108
- * paybackTurns = (1−α)·tailTokens / (α·R) // one-time refill / per-turn saving
3109
- * expectedSaving = α·R·max(0, Ŝ − paybackTurns) // Ŝ = estimated remaining turns
3110
- * ```
3111
- *
3112
- * The refill penalty only models mutations of already-cached context. A
3113
- * fresh-stage batch (shaped before its first request) is exempt via
3114
- * `refillPenaltyExempt`: payback is 0 and every reclaimed token saves from
3115
- * the very first turn.
3116
- *
3117
- * `expectedSaving` is only produced when Ŝ is known (the estimator answered
3118
- * with `expectedRemainingTurns`); it is never fabricated from a guess.
3037
+ * Shipped defaults of the advisory model. They are constants rather than
3038
+ * settings keys because the machine no longer acts on them: after the review
3039
+ * gate was retired these numbers only shape a label, so exposing them as
3040
+ * tunable configuration would advertise a control that changes no behavior.
3119
3041
  */
3042
+ const DEFAULT_ADVICE_ALPHA = .1;
3043
+ const DEFAULT_ADVICE_HIGH_IMPACT_TOKENS = 4e3;
3120
3044
  /**
3121
3045
  * Aggregate the batch-level benefit of a set of reduction candidates.
3122
3046
  *
@@ -3151,72 +3075,32 @@ function computeBenefit(candidates, input) {
3151
3075
  };
3152
3076
  }
3153
3077
  /**
3154
- * Stable proposal identity: the sha-256 of the serialized item digests, cut to
3155
- * 12 hex chars. Stable across re-enqueues of the same content so a repeated
3156
- * classification cannot duplicate a pending proposal.
3157
- */
3158
- function proposalId(itemDigests) {
3159
- const hash = createHash("sha256");
3160
- for (const digest of itemDigests) hash.update(digest);
3161
- hash.update(String(itemDigests.length));
3162
- return hash.digest("hex").slice(0, 12);
3163
- }
3164
- /**
3165
- * Canonical content digest reused from the dedup hash: plain-text results hash
3166
- * through the dedupe canonicalization; rich blocks fall back to canonical JSON
3167
- * so every candidate is freezable.
3168
- */
3169
- function contentDigest(content) {
3170
- return dedupeHash(flattenPlainText(content) ?? JSON.stringify(content), "trim-eol");
3171
- }
3172
- function proposalKindFor(candidate, estimatorSeqs) {
3173
- if (estimatorSeqs?.has(candidate.sourceSeq) === true) return "estimator";
3174
- if (candidate.reducer === "dedupe-pointer") return "dedup";
3175
- return "read-state";
3176
- }
3177
- /**
3178
- * Triage planned replacements into the three review-mode buckets, pricing the
3179
- * pass as ONE merged mutation (R1): the tail KV-cache refill penalty is a
3180
- * property of the landing event, not of any single candidate, so it must be
3181
- * paid exactly once per batch. Pricing per candidate overstates the payback
3182
- * N-fold and starves every real batch out of the auto path.
3078
+ * Label one batch of planned replacements.
3183
3079
  *
3184
3080
  * Pipeline: zero/negative-recovery candidates are priced out first (they never
3185
3081
  * make a batch look better), the surviving batch is priced once through
3186
- * `computeBenefit`, the verdict is a batch decision, and any high-impact
3187
- * candidate (`tokensBefore reviewHighImpactTokens`) covers the whole batch
3188
- * into review — splitting the batch would pay a second cache break that the
3189
- * accounting does not model. Review skeletons are grouped one proposal per
3190
- * kind; a proposal id covers every item digest.
3082
+ * `computeBenefit`, and the band is a batch decision the refill penalty is a
3083
+ * property of the landing event, not of any single candidate, so pricing per
3084
+ * candidate would overstate payback N-fold.
3191
3085
  *
3192
- * Batch verdict bands (identical thresholds to the per-candidate model):
3193
- * - any high-impact candidate, or α too small to price a payback → review;
3194
- * - `paybackTurns 1`, or Ŝ known and `paybackTurns ≤ 0.25·Ŝ` auto;
3195
- * - Ŝ known and `paybackTurns (1, 3]` review;
3196
- * - everything else unknown with a slow payback) drop.
3086
+ * Band precedence (identical thresholds to the retired triage model):
3087
+ * - α too small to price a payback → `unpriceable`;
3088
+ * - any candidate at `highImpactTokens``high-impact`;
3089
+ * - `paybackTurns ≤ 1`, or Ŝ known and `paybackTurns 0.25·Ŝ``profitable`;
3090
+ * - Ŝ known and `paybackTurns (1, 3]``slow-payback`;
3091
+ * - everything else → `not-worth-it`.
3197
3092
  *
3198
- * Stage asymmetry: a `'fresh'` batch is exempt from the tail-refill penalty
3199
- * (`refillPenaltyExempt`) its content was never served, so compressing it
3200
- * breaks no cache and payback is 0 while a `'history'` batch mutates
3201
- * already-cached context and pays `(1−α)·tailTokens` in full. Without this
3202
- * exemption every realistic fresh batch prices into the drop band and the
3203
- * auto bucket stays structurally unreachable.
3093
+ * @param candidates - planned replacements of one pass, in any order.
3094
+ * @param input - pricing inputs and the stage of the batch.
3095
+ * @returns the advice, or `undefined` when nothing carried a positive recovery.
3204
3096
  */
3205
- function classifyCandidates(candidates, input) {
3206
- const drop = [];
3097
+ function adviseCandidates(candidates, input) {
3207
3098
  const usable = [];
3208
3099
  for (const candidate of candidates) {
3209
- if (Math.max(0, candidate.tokensBefore - candidate.tokensAfter) <= 0) {
3210
- drop.push(candidate);
3211
- continue;
3212
- }
3100
+ if (Math.max(0, candidate.tokensBefore - candidate.tokensAfter) <= 0) continue;
3213
3101
  usable.push(candidate);
3214
3102
  }
3215
- if (usable.length === 0) return {
3216
- auto: [],
3217
- review: [],
3218
- drop
3219
- };
3103
+ if (usable.length === 0) return void 0;
3220
3104
  const benefit = computeBenefit(usable, {
3221
3105
  alpha: input.alpha,
3222
3106
  tailTokens: input.tailTokens,
@@ -3224,125 +3108,15 @@ function classifyCandidates(candidates, input) {
3224
3108
  refillPenaltyExempt: input.stage === "fresh"
3225
3109
  });
3226
3110
  const payback = benefit.paybackTurns;
3227
- const highImpact = usable.some((candidate) => candidate.tokensBefore >= input.reviewHighImpactTokens);
3228
- let verdict;
3229
- if (highImpact || payback === void 0) verdict = "review";
3230
- else if (payback <= 1 || input.remainingTurns !== void 0 && payback <= .25 * input.remainingTurns) verdict = "auto";
3231
- else if (input.remainingTurns !== void 0 && payback <= 3) verdict = "review";
3232
- else verdict = "drop";
3233
- if (verdict === "auto") return {
3234
- auto: usable,
3235
- review: [],
3236
- drop
3237
- };
3238
- if (verdict === "drop") return {
3239
- auto: [],
3240
- review: [],
3241
- drop: [...drop, ...usable]
3242
- };
3243
- const itemsByKind = /* @__PURE__ */ new Map();
3244
- for (const candidate of usable) {
3245
- const item = {
3246
- seq: candidate.sourceSeq,
3247
- component: candidate.component,
3248
- kind: proposalKindFor(candidate, input.estimatorSeqs),
3249
- tokensBefore: candidate.tokensBefore,
3250
- tokensAfter: candidate.tokensAfter,
3251
- digest: contentDigest(candidate.content)
3252
- };
3253
- const bucket = itemsByKind.get(item.kind) ?? [];
3254
- bucket.push(item);
3255
- itemsByKind.set(item.kind, bucket);
3256
- }
3257
- const review = [];
3258
- for (const [kind, items] of itemsByKind) review.push({
3259
- id: proposalId(items.map((item) => item.digest)),
3260
- kind,
3261
- items,
3262
- benefit
3263
- });
3111
+ const maxTokensBefore = usable.reduce((max, candidate) => Math.max(max, candidate.tokensBefore), 0);
3264
3112
  return {
3265
- auto: [],
3266
- review,
3267
- drop
3113
+ band: payback === void 0 ? "unpriceable" : maxTokensBefore >= input.highImpactTokens ? "high-impact" : payback <= 1 || input.remainingTurns !== void 0 && payback <= .25 * input.remainingTurns ? "profitable" : input.remainingTurns !== void 0 && payback <= 3 ? "slow-payback" : "not-worth-it",
3114
+ benefit,
3115
+ priced: usable.length,
3116
+ maxTokensBefore
3268
3117
  };
3269
3118
  }
3270
3119
  //#endregion
3271
- //#region src/runtime/tokenpilot/review-storage.ts
3272
- /** Domain name — `UNIT_NAME_RE` (`/^[a-z][a-z0-9_]*$/`) allows no hyphens. */
3273
- const REVIEW_STORAGE_DOMAIN = "context_compression_review";
3274
- /** The one declared table: one record per session id. */
3275
- const REVIEW_STORAGE_TABLE = "sessions";
3276
- /** Structural validator: accepts exactly the shape this module persists. */
3277
- function reviewSessionRecordValidator() {
3278
- return { safeParse(value) {
3279
- if (typeof value !== "object" || value === null) return { success: false };
3280
- const record = value;
3281
- if (record.version !== 1 || !Array.isArray(record.proposals)) return { success: false };
3282
- for (const proposal of record.proposals) {
3283
- if (typeof proposal !== "object" || proposal === null) return { success: false };
3284
- const entry = proposal;
3285
- if (typeof entry.id !== "string" || typeof entry.sessionId !== "string") return { success: false };
3286
- if (entry.kind !== "estimator" && entry.kind !== "dedup" && entry.kind !== "read-state") return { success: false };
3287
- if (entry.status !== "pending" && entry.status !== "approved") return { success: false };
3288
- if (!Number.isSafeInteger(entry.enqueuedTurn) || !Number.isSafeInteger(entry.lastTurnIndex)) return { success: false };
3289
- if (!Array.isArray(entry.items) || typeof entry.benefit !== "object" || entry.benefit === null) return { success: false };
3290
- for (const item of entry.items) {
3291
- if (typeof item !== "object" || item === null) return { success: false };
3292
- const one = item;
3293
- if (!Number.isSafeInteger(one.seq) || typeof one.digest !== "string") return { success: false };
3294
- }
3295
- }
3296
- return {
3297
- success: true,
3298
- data: value
3299
- };
3300
- } };
3301
- }
3302
- function reviewStorageSpec() {
3303
- return {
3304
- name: REVIEW_STORAGE_DOMAIN,
3305
- version: 1,
3306
- layout: "per-record",
3307
- tables: { [REVIEW_STORAGE_TABLE]: { valueSchema: reviewSessionRecordValidator() } }
3308
- };
3309
- }
3310
- /** Adapter presenting the sync KV face the queue expects over the domain table. */
3311
- var StorageDomainReviewStore = class {
3312
- table;
3313
- constructor(table) {
3314
- this.table = table;
3315
- }
3316
- load(sessionId) {
3317
- const value = this.table.get(sessionId);
3318
- return typeof value === "object" && value !== null ? value : void 0;
3319
- }
3320
- save(sessionId, record) {
3321
- this.table.put(sessionId, record).catch(() => void 0);
3322
- }
3323
- ids() {
3324
- return [...this.table.keys()];
3325
- }
3326
- };
3327
- /**
3328
- * Attempt to open the review storage domain through the optional
3329
- * `storageDomain` seam.
3330
- * @param getService - resolved once with the seam name; `undefined` means the
3331
- * host lacks the service.
3332
- * @returns the durable store, or `undefined` when the seam is absent or fails
3333
- * (the caller falls back to the in-memory store and logs one warning).
3334
- */
3335
- async function openReviewStorage(getService) {
3336
- let service;
3337
- try {
3338
- service = getService("storageDomain");
3339
- } catch {
3340
- return;
3341
- }
3342
- if (service === void 0 || service === null) return void 0;
3343
- return new StorageDomainReviewStore((await service.open(reviewStorageSpec())).table(REVIEW_STORAGE_TABLE));
3344
- }
3345
- //#endregion
3346
3120
  //#region src/runtime/tokenpilot/advisor-prompt.ts
3347
3121
  function buildAdvisorSummarySystemPrompt() {
3348
3122
  return [
@@ -3782,6 +3556,65 @@ async function runSessionAdvisorPass(session, channel, emit, input) {
3782
3556
  }
3783
3557
  }
3784
3558
  //#endregion
3559
+ //#region src/runtime/tokenpilot/dedup.ts
3560
+ /**
3561
+ * TokenPilot-inspired A1: byte-identical repeated tool-result dedup.
3562
+ *
3563
+ * Pure helpers behind the ToolResultPruner fresh pass. The per-session table
3564
+ * maps a canonical-content SHA-256 to the first surface seq that produced it;
3565
+ * later identical results may be replaced with a pointer placeholder that the
3566
+ * recovery tool can resolve back to the original full text via the append-only
3567
+ * session log. Only hash+seq metadata is stored — never content.
3568
+ */
3569
+ /** Per-session dedup index with insertion-order eviction. */
3570
+ var DedupeTable = class {
3571
+ maxEntries;
3572
+ entries = /* @__PURE__ */ new Map();
3573
+ constructor(maxEntries = 2048) {
3574
+ this.maxEntries = maxEntries;
3575
+ }
3576
+ /** Look up the first occurrence for one canonical hash, if any. */
3577
+ get(hash) {
3578
+ return this.entries.get(hash);
3579
+ }
3580
+ /** Record a first occurrence; existing hashes only refresh insertion order. */
3581
+ record(hash, entry) {
3582
+ if (this.entries.has(hash)) return;
3583
+ while (this.entries.size >= this.maxEntries) {
3584
+ const oldest = this.entries.keys().next().value;
3585
+ if (oldest === void 0) break;
3586
+ this.entries.delete(oldest);
3587
+ }
3588
+ this.entries.set(hash, entry);
3589
+ }
3590
+ };
3591
+ /** Canonicalize tool-result text for hashing. */
3592
+ function canonicalizeForDedupe(text, mode) {
3593
+ if (mode === "exact") return text;
3594
+ return text.replace(/[ \t]+\r?\n/g, "\n").replace(/(^\s+)|(\s+$)/g, "");
3595
+ }
3596
+ /** SHA-256 hex of the canonicalized text. */
3597
+ function dedupeHash(text, mode) {
3598
+ return createHash("sha256").update(canonicalizeForDedupe(text, mode), "utf8").digest("hex");
3599
+ }
3600
+ /** Concatenated text of an all-text content block list; null when rich. */
3601
+ function flattenPlainText(content) {
3602
+ let text = "";
3603
+ for (const block of content) {
3604
+ if (block.type !== "text") return void 0;
3605
+ text += block.text;
3606
+ }
3607
+ return text;
3608
+ }
3609
+ /** Pointer placeholder pointing at the first occurrence's original event. */
3610
+ function dedupePlaceholder(entry, originalChars) {
3611
+ return [
3612
+ `[... identical to the earlier ${entry.toolName} result; first seen at ${entry.sourceRef};`,
3613
+ `original_chars=${String(originalChars)};`,
3614
+ "retrieve with context_compression_retrieve({\"ref\":\"" + entry.sourceRef + "\",\"start_line\":1}) if the omitted evidence is necessary.]"
3615
+ ].join(" ");
3616
+ }
3617
+ //#endregion
3785
3618
  //#region src/runtime/deepseek-official-pricing.ts
3786
3619
  /** Checked-in DeepSeek official prices and fixed-point provider-usage accounting. */
3787
3620
  const DEEPSEEK_OFFICIAL_PRICE_CATALOG_VERSION = "deepseek-official-2026-08-25";
@@ -4212,19 +4045,10 @@ var ToolResultPruner = class extends Service {
4212
4045
  activeRequestBoundaries: /* @__PURE__ */ new WeakMap(),
4213
4046
  tailTrimBoundaryAttempts: /* @__PURE__ */ new WeakMap(),
4214
4047
  policyResolutionAudits: /* @__PURE__ */ new WeakMap(),
4215
- reviewStore: sharedReviewStore(),
4216
- reviewQueues: /* @__PURE__ */ new WeakMap(),
4217
- reviewClocks: /* @__PURE__ */ new WeakMap(),
4048
+ turnClocks: /* @__PURE__ */ new WeakMap(),
4218
4049
  estimatorRemainingTurns: /* @__PURE__ */ new WeakMap(),
4219
- advisorChannels: /* @__PURE__ */ new WeakMap(),
4220
- reviewSummaries: /* @__PURE__ */ new WeakMap()
4050
+ advisorChannels: /* @__PURE__ */ new WeakMap()
4221
4051
  };
4222
- ctx.effect(() => registerReviewPruner(this), "contextCompressionSelector.reviewRegistry()");
4223
- openReviewStorage((name) => this.ctx.get(name)).then((store) => {
4224
- if (store !== void 0) this.state.reviewStore = store;
4225
- }).catch(() => {
4226
- this.ctx.logger.warn("context-compression review storage unavailable; keeping in-memory review queue");
4227
- });
4228
4052
  ctx.on("session/event", (session, event) => {
4229
4053
  this.scanForSeededNativeSummary(session);
4230
4054
  if (event.type === "compaction/summary") {
@@ -4243,7 +4067,7 @@ var ToolResultPruner = class extends Service {
4243
4067
  this.state.activeRequestBoundaries.set(agent.session, boundary);
4244
4068
  try {
4245
4069
  if (!signal.aborted) try {
4246
- this.reviewClock(agent.session, turn);
4070
+ this.turnClock(agent.session, turn);
4247
4071
  this.runRequestBoundary(agent.session, turn, step - 1, signal);
4248
4072
  } catch (error) {
4249
4073
  this.auditFailure(agent.session, "fresh", "request-boundary", error);
@@ -4265,12 +4089,6 @@ var ToolResultPruner = class extends Service {
4265
4089
  this.auditFailure(agent.session, "fresh", "terminal-pass", error);
4266
4090
  ctx.logger.warn("context-compression terminal pass failed open: %o", error);
4267
4091
  }
4268
- try {
4269
- this.expireReviewProposals(agent.session, turn);
4270
- this.applyApprovedProposals(agent.session);
4271
- } catch (error) {
4272
- ctx.logger.warn("context-compression review turn-boundary pass failed open: %o", error);
4273
- }
4274
4092
  this.postflightEstimatorPass(agent.session, signal).catch(() => void 0);
4275
4093
  this.postflightAdvisorPass(agent.session, turn, signal).catch(() => void 0);
4276
4094
  });
@@ -4311,7 +4129,7 @@ var ToolResultPruner = class extends Service {
4311
4129
  const eligible = this.snapshot(session, view).filter((candidate) => !this.isRecoveryExempt(session, candidate));
4312
4130
  if (eligible.some((candidate) => candidate.count.kind !== "exact-tokenizer")) this.warnExactUnavailable(session, view, "native");
4313
4131
  const planned = eligible.map((candidate) => this.planNative(candidate, session, stage, policy, view)).filter((entry) => entry !== null);
4314
- landed.push(...this.landAll(session, this.triageForReview(session, policy, planned, "history")));
4132
+ landed.push(...this.landAll(session, this.adviseReplacements(session, policy, planned, "history")));
4315
4133
  if (landed.length === 0) {
4316
4134
  const chars = eligible.map((candidate) => candidate.characterPressure);
4317
4135
  this.auditComponent(session, policy, "native-tool-result", "pressure", "skipped", chars.length === 0 ? "no-tool-result-candidates" : Math.max(...chars) <= charsForTokens(policy.nativeTriggerTokens) ? "at-or-below-trigger" : planned.length === 0 ? "no-valid-reduction" : "recovery-tool-unavailable", {
@@ -4333,13 +4151,13 @@ var ToolResultPruner = class extends Service {
4333
4151
  if (historyOutcome.kind === "planned") {
4334
4152
  const capacityPressure = this.capacityPressureActive(session, view, policy);
4335
4153
  historyAllowed = this.adaptiveHistoryAllowed(session, view, historyOutcome.plans, capacityPressure);
4336
- if (historyAllowed) landed.push(...this.landAll(session, this.triageForReview(session, policy, historyOutcome.plans, "history")));
4154
+ if (historyAllowed) landed.push(...this.landAll(session, this.adviseReplacements(session, policy, historyOutcome.plans, "history")));
4337
4155
  }
4338
4156
  } else {
4339
4157
  historyAllowed = this.historyAllowed(session, policy, view);
4340
4158
  if (historyAllowed) {
4341
4159
  historyOutcome = this.planHistoricalAging(session, policy, view);
4342
- if (historyOutcome.kind === "planned") landed.push(...this.landAll(session, this.triageForReview(session, policy, historyOutcome.plans, "history")));
4160
+ if (historyOutcome.kind === "planned") landed.push(...this.landAll(session, this.adviseReplacements(session, policy, historyOutcome.plans, "history")));
4343
4161
  }
4344
4162
  }
4345
4163
  if (!landed.some((entry) => entry.stage === "pressure")) this.auditHistoryEvaluation(session, policy, view, historyAllowed, historyOutcome);
@@ -4602,289 +4420,68 @@ var ToolResultPruner = class extends Service {
4602
4420
  else if (outcome !== void 0) advisorState.failures = void 0;
4603
4421
  }
4604
4422
  /**
4605
- * The per-session review queue, or `undefined` while review mode is off
4606
- * (every review path must then behave exactly like before).
4607
- */
4608
- reviewQueueFor(session, policy) {
4609
- const presetOptions = policy?.presetOptions;
4610
- if (presetOptions?.reviewMode !== true) return void 0;
4611
- let queue = this.state.reviewQueues.get(session);
4612
- if (queue === void 0) {
4613
- queue = new ReviewQueue(this.state.reviewStore, { timeoutTurns: presetOptions.reviewTimeoutTurns });
4614
- this.state.reviewQueues.set(session, queue);
4615
- }
4616
- return queue;
4617
- }
4618
- /**
4619
- * Monotonic per-session turn clock for review patience and expiry. Bumped by
4620
- * the agent loop payloads (`pre-step` / `turn-stopping`); passes without a
4621
- * turn coordinate reuse the last observed value.
4423
+ * Monotonic per-session turn clock for advisory records. Bumped by the agent
4424
+ * loop payloads (`pre-step`); passes without a turn coordinate reuse the last
4425
+ * observed value.
4622
4426
  */
4623
- reviewClock(session, turn) {
4624
- const previous = this.state.reviewClocks.get(session) ?? 0;
4427
+ turnClock(session, turn) {
4428
+ const previous = this.state.turnClocks.get(session) ?? 0;
4625
4429
  const next = typeof turn === "number" && Number.isSafeInteger(turn) && turn > previous ? turn : previous;
4626
- this.state.reviewClocks.set(session, next);
4430
+ this.state.turnClocks.set(session, next);
4627
4431
  return next;
4628
4432
  }
4629
- auditReviewOutcome(session, proposal, event, extra = {}, turnIndex) {
4630
- const tokensBefore = proposal.items.reduce((sum, item) => sum + item.tokensBefore, 0);
4631
- const tokensAfter = proposal.items.reduce((sum, item) => sum + item.tokensAfter, 0);
4632
- emitCompressionAudit(this.ctx.logger, {
4633
- schemaVersion: 1,
4634
- kind: "review-outcome",
4635
- sessionId: String(session.id),
4636
- proposalId: proposal.id,
4637
- proposalKind: proposal.kind,
4638
- event,
4639
- ...extra.decision === void 0 ? {} : { decision: extra.decision },
4640
- ...extra.receiptStatus === void 0 ? {} : { receiptStatus: extra.receiptStatus },
4641
- ...extra.reasonCode === void 0 ? {} : { reasonCode: extra.reasonCode },
4642
- itemSeqs: proposal.items.map((item) => item.seq),
4643
- tokensBefore,
4644
- tokensAfter,
4645
- ...turnIndex === void 0 ? {} : { turnIndex }
4646
- });
4647
- }
4648
4433
  /**
4649
- * Review-mode triage hook: classify one pass's planned replacements and
4650
- * withhold the review bucket from landing, enqueuing it for human approval
4651
- * instead. With review mode off (or nothing planned) this is the identity.
4652
- *
4653
- * The digest freezes each candidate's ORIGINAL surface content, so the apply
4654
- * point can prove "what is removed now is what was approved then".
4434
+ * Advisory benefit-model hook what the retired human-gated review pipeline
4435
+ * left behind. It is the IDENTITY on the landing path: every plan it is given
4436
+ * comes back unchanged, because a reduction must never block automatic
4437
+ * processing. The model's band is published as a `reduction-advice` audit and
4438
+ * snapshotted onto the advisor state for the read-only report route, so the
4439
+ * cache-accounting insight survives without a gate.
4655
4440
  */
4656
- triageForReview(session, policy, plans, stage = "history") {
4657
- const queue = this.reviewQueueFor(session, policy);
4658
- if (queue === void 0 || plans.length === 0) return plans;
4659
- const presetOptions = policy.presetOptions;
4660
- const estimatorVerdicts = this.state.estimatorVerdicts.get(session);
4661
- const estimatorSeqs = new Set([...estimatorVerdicts?.entries() ?? []].filter(([, expired]) => expired).map(([seq]) => seq));
4662
- const input = {
4663
- alpha: presetOptions.cacheHitDiscountAlpha,
4441
+ adviseReplacements(session, policy, plans, stage = "history") {
4442
+ if (plans.length === 0) return plans;
4443
+ const remainingTurns = this.state.estimatorRemainingTurns.get(session);
4444
+ const advice = adviseCandidates(plans.map((plan) => ({
4445
+ sourceSeq: plan.sourceSeq,
4446
+ tokensBefore: plan.tokensBefore,
4447
+ tokensAfter: plan.tokensAfter
4448
+ })), {
4449
+ alpha: DEFAULT_ADVICE_ALPHA,
4664
4450
  tailTokens: Math.max(1, policy.historyKeepRecentTokens),
4665
- reviewHighImpactTokens: presetOptions.reviewHighImpactTokens,
4666
- ...this.state.estimatorRemainingTurns.get(session) === void 0 ? {} : { remainingTurns: this.state.estimatorRemainingTurns.get(session) },
4667
- estimatorSeqs,
4451
+ highImpactTokens: DEFAULT_ADVICE_HIGH_IMPACT_TOKENS,
4452
+ ...remainingTurns === void 0 ? {} : { remainingTurns },
4668
4453
  stage
4454
+ });
4455
+ if (advice === void 0) return plans;
4456
+ const turn = this.turnClock(session);
4457
+ const itemSeqs = plans.map((plan) => plan.sourceSeq);
4458
+ getAdvisorState(session).lastAdvice = {
4459
+ band: advice.band,
4460
+ turn,
4461
+ itemSeqs,
4462
+ recoveredTokens: advice.benefit.recoveredTokens,
4463
+ penaltyTokens: advice.benefit.penaltyTokens,
4464
+ ...advice.benefit.paybackTurns === void 0 ? {} : { paybackTurns: advice.benefit.paybackTurns }
4669
4465
  };
4670
- const classified = classifyCandidates(plans.map((plan) => ({
4671
- sourceSeq: plan.sourceSeq,
4672
- tokensBefore: plan.tokensBefore,
4673
- tokensAfter: plan.tokensAfter,
4674
- component: plan.component,
4675
- reducer: plan.reducer,
4676
- content: plan.candidate.event.data.message.content[0].content
4677
- })), input);
4678
- const autoSeqs = new Set(classified.auto.map((candidate) => candidate.sourceSeq));
4679
- const clock = this.reviewClock(session);
4680
- for (const skeleton of classified.review) if (queue.enqueue(String(session.id), skeleton, clock)) this.auditReviewOutcome(session, {
4681
- id: skeleton.id,
4682
- kind: skeleton.kind,
4683
- items: skeleton.items
4684
- }, "enqueue", {}, clock);
4685
- return plans.filter((plan) => autoSeqs.has(plan.sourceSeq));
4686
- }
4687
- /**
4688
- * Execute every approved proposal of one session as ONE merged replacement
4689
- * batch at the current turn boundary, following the upstream applied-receipt
4690
- * discipline: applied receipts are built only from real mutation evidence —
4691
- * estimates never cross into applied savings.
4692
- *
4693
- * Per proposal: every item's frozen digest is re-checked against the current
4694
- * surface content; any mismatch voids the whole proposal (deferred with a
4695
- * reason code) instead of deleting something the user never approved.
4696
- * Fail-open: any unexpected error only logs and leaves the queue intact.
4697
- */
4698
- applyApprovedProposals(session) {
4699
- const policy = this.activePolicy(session);
4700
- const queue = this.reviewQueueFor(session, policy);
4701
- if (queue === void 0) return;
4702
- const sessionId = String(session.id);
4703
- const approved = [...queue.listApproved(sessionId)];
4704
- if (approved.length === 0) return;
4705
- try {
4706
- const view = measureForCompaction(this.ctx, session);
4707
- const candidatesBySeq = new Map(this.snapshot(session, view).map((candidate) => [candidate.seq, candidate]));
4708
- const settled = [];
4709
- const now = (/* @__PURE__ */ new Date()).toISOString();
4710
- for (const proposal of approved) {
4711
- const voidedReason = this.reviewProposalVoided(session, proposal);
4712
- if (voidedReason !== void 0) {
4713
- settled.push({
4714
- proposal,
4715
- receipt: {
4716
- status: "deferred",
4717
- reasonCode: voidedReason,
4718
- estimatedTokens: proposal.benefit.recoveredTokens,
4719
- updatedAt: now
4720
- }
4721
- });
4722
- continue;
4723
- }
4724
- const batchPlans = [];
4725
- let planned = true;
4726
- for (const item of proposal.items) {
4727
- const candidate = candidatesBySeq.get(item.seq);
4728
- if (candidate === void 0) {
4729
- planned = false;
4730
- break;
4731
- }
4732
- const plan = this.planAggregate(candidate, session, view, "review-approved-whole-result", "pressure", void 0, "history", policy?.historyMode);
4733
- if (plan === null) {
4734
- planned = false;
4735
- break;
4736
- }
4737
- batchPlans.push(plan);
4738
- }
4739
- if (!planned || batchPlans.length === 0) {
4740
- settled.push({
4741
- proposal,
4742
- receipt: {
4743
- status: "deferred",
4744
- reasonCode: "review_receipt_execution_invalid",
4745
- estimatedTokens: proposal.benefit.recoveredTokens,
4746
- updatedAt: now
4747
- }
4748
- });
4749
- continue;
4750
- }
4751
- const landed = this.landAll(session, batchPlans);
4752
- if (landed.length === 0) {
4753
- settled.push({
4754
- proposal,
4755
- receipt: {
4756
- status: "deferred",
4757
- reasonCode: "review_receipt_execution_invalid",
4758
- estimatedTokens: proposal.benefit.recoveredTokens,
4759
- updatedAt: now
4760
- }
4761
- });
4762
- continue;
4763
- }
4764
- const landedForProposal = new Map(landed.map((entry) => [entry.originalSeq, entry]));
4765
- const measuredItems = proposal.items.map((item) => {
4766
- const entry = landedForProposal.get(item.seq);
4767
- return entry === void 0 ? item : {
4768
- ...item,
4769
- tokensBefore: entry.tokensBefore,
4770
- tokensAfter: entry.tokensAfter
4771
- };
4772
- });
4773
- const appliedTokens = measuredItems.reduce((sum, item) => sum + item.tokensBefore - item.tokensAfter, 0);
4774
- settled.push({
4775
- proposal,
4776
- auditItems: measuredItems,
4777
- receipt: {
4778
- status: "applied",
4779
- estimatedTokens: proposal.benefit.recoveredTokens,
4780
- appliedTokens,
4781
- updatedAt: now
4782
- }
4783
- });
4784
- }
4785
- for (const { proposal, auditItems, receipt } of settled) {
4786
- queue.recordReceipt(sessionId, proposal.id, receipt);
4787
- const summary = this.reviewSummaryFor(session);
4788
- if (receipt.status === "applied") summary.reviewApplied += 1;
4789
- else summary.voided += 1;
4790
- this.auditReviewOutcome(session, auditItems === void 0 ? proposal : {
4791
- ...proposal,
4792
- items: auditItems
4793
- }, receipt.status === "applied" ? "apply-receipt" : "apply-void", receipt.status === "applied" ? { receiptStatus: "applied" } : {
4794
- receiptStatus: "deferred",
4795
- reasonCode: receipt.reasonCode
4796
- });
4797
- }
4798
- } catch (error) {
4799
- this.ctx.logger.warn("context-compression review apply failed open: %o", error);
4800
- }
4801
- }
4802
- /**
4803
- * Current surface content at one seq: the newest covering replacement's
4804
- * blocks when the seq was rewritten, otherwise the original event's blocks.
4805
- */
4806
- surfaceContentAt(session, seq) {
4807
- let content;
4808
- for (const event of sessionEvents(session)) {
4809
- if (event.type !== "tool/result") continue;
4810
- const op = event.surfaceOp;
4811
- if (typeof op === "object" && op.op === "replace" && op.startSeq <= seq && seq <= op.endSeq) content = event.data.message.content[0].content;
4812
- }
4813
- if (content !== void 0) return content;
4814
- const original = sessionEvents(session).find((entry) => entry.seq === seq);
4815
- return original?.type === "tool/result" ? original.data.message.content[0].content : void 0;
4816
- }
4817
- /**
4818
- * The execution-point digest check: `undefined` when every item's frozen
4819
- * digest still matches the current surface content, otherwise the aligned
4820
- * reason code explaining the void.
4821
- */
4822
- reviewProposalVoided(session, proposal) {
4823
- for (const item of proposal.items) {
4824
- const current = this.surfaceContentAt(session, item.seq);
4825
- if (current === void 0) return "review_receipt_missing_candidate";
4826
- if (contentDigest(current) !== item.digest) return "review_receipt_digest_invalid";
4827
- }
4828
- }
4829
- reviewSummaryFor(session) {
4830
- let summary = this.state.reviewSummaries.get(session);
4831
- if (summary === void 0) {
4832
- summary = {
4833
- autoApplied: 0,
4834
- reviewApplied: 0,
4835
- expired: 0,
4836
- voided: 0
4837
- };
4838
- this.state.reviewSummaries.set(session, summary);
4839
- }
4840
- return summary;
4841
- }
4842
- /** Live pending review proposals of one session; empty when review mode is off. */
4843
- listReviewProposals(session) {
4844
- return this.reviewQueueFor(session, this.activePolicy(session))?.listPending(String(session.id)) ?? [];
4845
- }
4846
- /**
4847
- * Every session's live pending proposals, for the floating window's
4848
- * aggregate badge (the client carries no session id of its own).
4849
- */
4850
- listAllReviewProposals() {
4851
- const ids = this.state.reviewStore.ids?.() ?? [];
4852
- const reader = new ReviewQueue(this.state.reviewStore, { timeoutTurns: 1 });
4853
- return ids.map((sessionId) => ({
4854
- sessionId,
4855
- proposals: [...reader.listPending(sessionId)]
4856
- })).filter((entry) => entry.proposals.length > 0);
4857
- }
4858
- /** Four-state outcome counters of one session (floating-window summary row). */
4859
- reviewSummary(session) {
4860
- return { ...this.reviewSummaryFor(session) };
4861
- }
4862
- /**
4863
- * Record one human decision. Returns the outcome, or `undefined` when
4864
- * review mode is off for this session (the route maps that to 503).
4865
- */
4866
- decideReviewProposal(session, proposalId, decision) {
4867
- const queue = this.reviewQueueFor(session, this.activePolicy(session));
4868
- if (queue === void 0) return void 0;
4869
- const sessionId = String(session.id);
4870
- const pending = queue.listPending(sessionId).find((entry) => entry.id === proposalId);
4871
- const outcome = queue.decide(sessionId, proposalId, decision);
4872
- if (outcome.ok && pending !== void 0) this.auditReviewOutcome(session, pending, "decide", { decision }, this.reviewClock(session));
4873
- return outcome;
4874
- }
4875
- /**
4876
- * Expire stale pending proposals at one turn boundary and audit each.
4877
- * Public because tests drive it directly; the turn-stopping handler calls
4878
- * it with the loop's own turn index.
4879
- */
4880
- expireReviewProposals(session, turnIndex) {
4881
- const queue = this.reviewQueueFor(session, this.activePolicy(session));
4882
- if (queue === void 0) return [];
4883
- const clock = this.reviewClock(session, turnIndex);
4884
- const expired = queue.expireTurn(String(session.id), clock);
4885
- if (expired.length > 0) this.reviewSummaryFor(session).expired += expired.length;
4886
- for (const proposal of expired) this.auditReviewOutcome(session, proposal, "expire", {}, clock);
4887
- return expired;
4466
+ emitCompressionAudit(this.ctx.logger, {
4467
+ schemaVersion: 1,
4468
+ kind: "reduction-advice",
4469
+ sessionId: String(session.id),
4470
+ profile: policy.profile,
4471
+ band: advice.band,
4472
+ stage,
4473
+ itemSeqs,
4474
+ pricedCandidates: advice.priced,
4475
+ maxTokensBefore: advice.maxTokensBefore,
4476
+ tokensBefore: plans.reduce((sum, plan) => sum + plan.tokensBefore, 0),
4477
+ tokensAfter: plans.reduce((sum, plan) => sum + plan.tokensAfter, 0),
4478
+ recoveredTokens: advice.benefit.recoveredTokens,
4479
+ penaltyTokens: advice.benefit.penaltyTokens,
4480
+ ...advice.benefit.paybackTurns === void 0 ? {} : { paybackTurns: advice.benefit.paybackTurns },
4481
+ ...advice.benefit.expectedSaving === void 0 ? {} : { expectedSaving: advice.benefit.expectedSaving },
4482
+ turnIndex: turn
4483
+ });
4484
+ return plans;
4888
4485
  }
4889
4486
  activePolicy(session, contextWindowTokens, stage = "pressure") {
4890
4487
  const settings = this.activeSettings(session);
@@ -5211,7 +4808,7 @@ var ToolResultPruner = class extends Service {
5211
4808
  }
5212
4809
  }
5213
4810
  const freshCandidates = candidates.map((candidate) => plans.get(candidate.seq)).filter((plan) => plan !== void 0);
5214
- const landed = this.landAll(session, this.triageForReview(session, policy, freshCandidates, "fresh"));
4811
+ const landed = this.landAll(session, this.adviseReplacements(session, policy, freshCandidates, "fresh"));
5215
4812
  const freshLanded = landed.some((entry) => entry.stage === "fresh" && plans.get(entry.originalSeq)?.component === "fresh");
5216
4813
  const aggregateLanded = landed.some((entry) => entry.stage === "fresh" && plans.get(entry.originalSeq)?.component === "aggregate");
5217
4814
  if (!freshLanded) this.auditComponent(session, policy, "fresh", "fresh", policy.freshEnabled ? "skipped" : "disabled", !policy.freshEnabled ? "profile-policy" : (maxCandidateChars ?? 0) <= charsForTokens(policy.freshTriggerTokens) ? "at-or-below-trigger" : freshPlanned > 0 && aggregatePlanned > 0 ? "superseded-by-aggregate" : freshPlanned === 0 ? "no-valid-reduction" : "recovery-tool-unavailable", {
@@ -5795,7 +5392,6 @@ var ToolResultPruner = class extends Service {
5795
5392
  tokenizerRevision: plan.tokenizerRevision,
5796
5393
  ...plan.elidedLines === void 0 ? {} : { elidedLines: plan.elidedLines }
5797
5394
  });
5798
- if (plan.reducer !== "review-approved-whole-result") this.reviewSummaryFor(session).autoApplied += 1;
5799
5395
  return {
5800
5396
  originalSeq: candidate.seq,
5801
5397
  sourceSeq: plan.sourceSeq,