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.
- package/.gitattributes +1 -0
- package/CHANGELOG.ja.md +144 -83
- package/CHANGELOG.ko.md +143 -82
- package/CHANGELOG.md +278 -212
- package/CHANGELOG.zh.md +131 -77
- package/docs/installation.md +103 -103
- package/docs/installation.zh.md +100 -100
- package/package.json +1 -1
- package/packages/selector/cordis.patch.yml +5 -6
- package/packages/selector/lib/advisor-state.js +4 -231
- package/packages/selector/lib/client.d.ts +0 -24
- package/packages/selector/lib/client.js +6 -501
- package/packages/selector/lib/index.d.ts +4 -10
- package/packages/selector/lib/index.js +16 -234
- package/packages/selector/lib/pruner.d.ts +13 -248
- package/packages/selector/lib/pruner.js +148 -552
- package/packages/selector/src/client/EstimatorControls.tsx +0 -101
- package/packages/selector/src/client/index.ts +0 -17
- package/packages/selector/src/client/locales.ts +0 -38
- package/packages/selector/src/client/preset-options.ts +3 -2
- package/packages/selector/src/client/settings-section.tsx +8 -17
- package/packages/selector/src/index.ts +24 -271
- package/packages/selector/src/profiles.ts +4 -27
- package/packages/selector/src/pruner/state.ts +2 -25
- package/packages/selector/src/pruner.ts +75 -403
- package/packages/selector/src/runtime/audit.ts +27 -21
- package/packages/selector/src/runtime/config.ts +6 -32
- package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -188
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +149 -133
- package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -419
- package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
- package/packages/selector/src/runtime/types.ts +0 -17
- package/packages/selector/tests/advisor-report.host.spec.ts +223 -223
- package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
- package/packages/selector/tests/public/package-contract.client.spec.ts +20 -0
- package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
- package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -272
- package/packages/selector/tests/runtime/advisor.spec.ts +226 -226
- package/packages/selector/tests/runtime/audit.spec.ts +35 -21
- package/packages/selector/tests/runtime/char-basis.spec.ts +30 -30
- package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
- package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
- package/packages/selector/tests/settings-seat.client.spec.ts +45 -14
- package/scripts/toolclass-corpus-replay.mjs +281 -281
- package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
- package/packages/selector/src/client/review-scope.ts +0 -16
- package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
- package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
- package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
- package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
- package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
- package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
- package/packages/selector/tests/review-routes.host.spec.ts +0 -290
- package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
- package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
- package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
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/
|
|
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
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
*
|
|
3099
|
-
*
|
|
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
|
-
*
|
|
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
|
|
3187
|
-
*
|
|
3188
|
-
*
|
|
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
|
-
*
|
|
3193
|
-
* -
|
|
3194
|
-
* -
|
|
3195
|
-
* - Ŝ known and `paybackTurns
|
|
3196
|
-
* -
|
|
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
|
-
*
|
|
3199
|
-
*
|
|
3200
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
*
|
|
4606
|
-
* (
|
|
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
|
-
|
|
4624
|
-
const previous = this.state.
|
|
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.
|
|
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
|
-
*
|
|
4650
|
-
*
|
|
4651
|
-
*
|
|
4652
|
-
*
|
|
4653
|
-
*
|
|
4654
|
-
*
|
|
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
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
const
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
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
|
-
|
|
4666
|
-
...
|
|
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
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
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.
|
|
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,
|