dsh-context-compression-improved 0.4.0 → 0.5.1
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/CHANGELOG.ja.md +34 -0
- package/CHANGELOG.ko.md +34 -0
- package/CHANGELOG.md +38 -0
- package/CHANGELOG.zh.md +30 -0
- package/docs/installation.md +25 -1
- package/docs/installation.zh.md +24 -1
- package/package.json +1 -1
- package/packages/selector/lib/{review-registry.js → advisor-state.js} +105 -4
- package/packages/selector/lib/client.d.ts +7 -0
- package/packages/selector/lib/client.js +32 -2
- package/packages/selector/lib/index.d.ts +7 -0
- package/packages/selector/lib/index.js +150 -3
- package/packages/selector/lib/pruner.d.ts +82 -0
- package/packages/selector/lib/pruner.js +545 -11
- package/packages/selector/src/client/preset-options.ts +2 -0
- package/packages/selector/src/index.ts +108 -0
- package/packages/selector/src/preset-overlay.ts +60 -1
- package/packages/selector/src/profiles.ts +48 -0
- package/packages/selector/src/pruner/state.ts +3 -0
- package/packages/selector/src/pruner.ts +113 -0
- package/packages/selector/src/runtime/audit.ts +22 -0
- package/packages/selector/src/runtime/config.ts +58 -0
- package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -0
- package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +133 -0
- package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -0
- package/packages/selector/src/runtime/tokenpilot/sidechannel.ts +24 -9
- package/packages/selector/src/runtime/types.ts +21 -0
- package/packages/selector/tests/advisor-report.host.spec.ts +223 -0
- package/packages/selector/tests/built/client-artifact.spec.ts +9 -5
- package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -0
- package/packages/selector/tests/runtime/advisor.spec.ts +226 -0
- package/packages/selector/tests/runtime/audit.spec.ts +44 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +12 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +54 -5
- package/scripts/packed-components-smoke.mjs +30 -8
- package/scripts/packed-install-e2e.mjs +69 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as ContextCompressionSettingsSchema, o as resolveReviewPruner, t as getAdvisorState, u as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE } from "./advisor-state.js";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import "@deepseek-ai/dsh-settings";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -118,6 +118,15 @@ function standingStampMsAtWindow(identity, windowIndex) {
|
|
|
118
118
|
const subSecond = parseInt(identity.slice(start + 8, start + STANDING_MTIME_WINDOW_HEX).padEnd(3, "0"), 16) % 1e3;
|
|
119
119
|
return seconds * 1e3 + subSecond;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* True for the filesystem errors a concurrent publish can raise on Windows:
|
|
123
|
+
* `MoveFileEx` reports a lost race — or a reader holding the destination open —
|
|
124
|
+
* as EPERM/EBUSY/EACCES, where POSIX `rename` simply replaces the destination.
|
|
125
|
+
*/
|
|
126
|
+
function isPublishRace(error) {
|
|
127
|
+
const code = error?.code;
|
|
128
|
+
return code === "EPERM" || code === "EBUSY" || code === "EACCES" || code === "EEXIST";
|
|
129
|
+
}
|
|
121
130
|
const COMPRESSION_IDS = /* @__PURE__ */ new Set([
|
|
122
131
|
"compaction",
|
|
123
132
|
"compaction-basic",
|
|
@@ -191,7 +200,7 @@ var PresetOverlayStore = class {
|
|
|
191
200
|
});
|
|
192
201
|
await chmod(staging, 384);
|
|
193
202
|
await this.disambiguateStamp(staging, identity);
|
|
194
|
-
await
|
|
203
|
+
await this.publish(staging, path);
|
|
195
204
|
} catch (error) {
|
|
196
205
|
try {
|
|
197
206
|
await rm(staging, { force: true });
|
|
@@ -203,6 +212,45 @@ var PresetOverlayStore = class {
|
|
|
203
212
|
path
|
|
204
213
|
};
|
|
205
214
|
}
|
|
215
|
+
/** In-flight publish chains, one per destination path. */
|
|
216
|
+
publishChains = /* @__PURE__ */ new Map();
|
|
217
|
+
/**
|
|
218
|
+
* Publish one stamped staging file to its final path, serialized per path.
|
|
219
|
+
*
|
|
220
|
+
* POSIX `rename` replaces an existing destination atomically, so concurrent
|
|
221
|
+
* composers of one identity are naturally idempotent there. Windows
|
|
222
|
+
* `MoveFileEx` instead fails the loser of that race with EPERM/EBUSY, which
|
|
223
|
+
* surfaced as `standingKeyFor()` throwing during concurrent composition.
|
|
224
|
+
* Every composer of one identity writes identical bytes and re-derives the
|
|
225
|
+
* same deterministic identity stamp, so a destination that already observes
|
|
226
|
+
* this staging file's {mtimeMs, size} key IS the intended end state: confirm
|
|
227
|
+
* it and treat the lost race as success. Every other outcome still throws, so
|
|
228
|
+
* a silently reused generation stays forbidden, and the caller still removes
|
|
229
|
+
* the staging file when this rejects.
|
|
230
|
+
*/
|
|
231
|
+
publish(staging, path) {
|
|
232
|
+
const tracked = (this.publishChains.get(path) ?? Promise.resolve()).catch(() => void 0).then(async () => {
|
|
233
|
+
try {
|
|
234
|
+
await rename(staging, path);
|
|
235
|
+
return;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (!isPublishRace(error)) throw error;
|
|
238
|
+
try {
|
|
239
|
+
const intended = await this.metadataIo.read(staging);
|
|
240
|
+
const published = await this.metadataIo.read(path);
|
|
241
|
+
if (published.size === intended.size && Math.abs(published.mtimeMs - intended.mtimeMs) < 2) {
|
|
242
|
+
await rm(staging, { force: true });
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
} catch {}
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
}).finally(() => {
|
|
249
|
+
if (this.publishChains.get(path) === tracked) this.publishChains.delete(path);
|
|
250
|
+
});
|
|
251
|
+
this.publishChains.set(path, tracked);
|
|
252
|
+
return tracked;
|
|
253
|
+
}
|
|
206
254
|
/** Observed {mtimeMs,size} keys published by this store, per identity. */
|
|
207
255
|
standingKeys = /* @__PURE__ */ new Map();
|
|
208
256
|
/**
|
|
@@ -459,6 +507,7 @@ const CONTEXT_COMPRESSION_NAMESPACE = CONTEXT_COMPRESSION_SETTINGS_NAMESPACE;
|
|
|
459
507
|
const ESTIMATOR_CATALOG_ROUTES = ["/endpoint/dsh-context-compression-improved/estimator-catalog", "/api/dsh-context-compression-improved/estimator-catalog"];
|
|
460
508
|
const REVIEW_QUEUE_ROUTES = ["/endpoint/dsh-context-compression-improved/review-queue", "/api/dsh-context-compression-improved/review-queue"];
|
|
461
509
|
const REVIEW_DECIDE_ROUTES = ["/endpoint/dsh-context-compression-improved/review-decide", "/api/dsh-context-compression-improved/review-decide"];
|
|
510
|
+
const ADVISOR_REPORT_ROUTES = ["/endpoint/dsh-context-compression-improved/advisor-report", "/api/dsh-context-compression-improved/advisor-report"];
|
|
462
511
|
/**
|
|
463
512
|
* Resolve the review pipeline for the top-level routes.
|
|
464
513
|
*
|
|
@@ -694,6 +743,102 @@ function registerReviewQueueRoutes(ctx) {
|
|
|
694
743
|
log("warn", "context-compression webServer not active yet — review routes pending: %s", REVIEW_QUEUE_ROUTES.join(", "));
|
|
695
744
|
}
|
|
696
745
|
/**
|
|
746
|
+
* Serve the advisory advisor's read-only report route (same registration
|
|
747
|
+
* skeleton as the review routes):
|
|
748
|
+
*
|
|
749
|
+
* `GET .../advisor-report?sessionId=…` → the session's prefix-decay figure,
|
|
750
|
+
* the todolist-bound task summary, and the score distribution. Content-free
|
|
751
|
+
* by construction: task semantics are LLM-derived summaries, never message
|
|
752
|
+
* text, and no score reason or candidate preview is ever returned.
|
|
753
|
+
* Unknown session → 404; no agents service → 503. A session whose advisor
|
|
754
|
+
* never ran reports nulls and empty arrays, not an error.
|
|
755
|
+
*/
|
|
756
|
+
function registerAdvisorReportRoute(ctx) {
|
|
757
|
+
const readService = (name) => {
|
|
758
|
+
try {
|
|
759
|
+
return ctx.get(name);
|
|
760
|
+
} catch {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
const log = (level, message, ...args) => {
|
|
765
|
+
console[level](message, ...args);
|
|
766
|
+
};
|
|
767
|
+
const getHandler = (req, res) => {
|
|
768
|
+
if (typeof readService("agents")?.get !== "function") {
|
|
769
|
+
reviewJson(res, 503, {
|
|
770
|
+
ok: false,
|
|
771
|
+
error: "advisor report unavailable"
|
|
772
|
+
});
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
let sessionId = "";
|
|
776
|
+
try {
|
|
777
|
+
sessionId = new URL(String(req.url ?? ""), "http://localhost").searchParams.get("sessionId") ?? "";
|
|
778
|
+
} catch {}
|
|
779
|
+
if (sessionId === "") {
|
|
780
|
+
reviewJson(res, 400, {
|
|
781
|
+
ok: false,
|
|
782
|
+
error: "sessionId is required"
|
|
783
|
+
});
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const session = sessionFor(readService, sessionId);
|
|
787
|
+
if (session === void 0) {
|
|
788
|
+
reviewJson(res, 404, {
|
|
789
|
+
ok: false,
|
|
790
|
+
error: "unknown session"
|
|
791
|
+
});
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const state = getAdvisorState(session);
|
|
795
|
+
reviewJson(res, 200, {
|
|
796
|
+
ok: true,
|
|
797
|
+
sessionId,
|
|
798
|
+
advisor: {
|
|
799
|
+
summary: state.summary ?? null,
|
|
800
|
+
decay: state.lastDecay?.decay ?? null,
|
|
801
|
+
weightedChars: state.lastDecay?.weightedChars ?? null,
|
|
802
|
+
decayTurn: state.lastDecay?.turn ?? null,
|
|
803
|
+
scores: [...state.scores].map(([seq, entry]) => ({
|
|
804
|
+
seq,
|
|
805
|
+
score: entry.score,
|
|
806
|
+
turn: entry.turn
|
|
807
|
+
})),
|
|
808
|
+
lowRelevanceSeqs: [...state.recertified.keys()]
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
};
|
|
812
|
+
const register = (webServer) => {
|
|
813
|
+
const disposers = [...ADVISOR_REPORT_ROUTES].map((path) => ({
|
|
814
|
+
path,
|
|
815
|
+
handler: getHandler
|
|
816
|
+
})).map((entry) => webServer.register({
|
|
817
|
+
kind: "exact",
|
|
818
|
+
path: entry.path,
|
|
819
|
+
handler: entry.handler
|
|
820
|
+
})).filter((off) => typeof off === "function");
|
|
821
|
+
ctx.effect(() => () => {
|
|
822
|
+
for (const off of disposers) off();
|
|
823
|
+
}, "contextCompressionSelector.advisor report route");
|
|
824
|
+
log("info", "context-compression advisor report route registered: %s", ADVISOR_REPORT_ROUTES.join(", "));
|
|
825
|
+
};
|
|
826
|
+
const active = asWebServer(readService("webServer"));
|
|
827
|
+
if (active !== void 0) {
|
|
828
|
+
register(active);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
ctx.inject(["webServer"], (injected) => {
|
|
832
|
+
const webServer = asWebServer(injected.webServer);
|
|
833
|
+
if (webServer === void 0) {
|
|
834
|
+
log("warn", "context-compression webServer exposes no register() — advisor report route not registered");
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
register(webServer);
|
|
838
|
+
});
|
|
839
|
+
log("warn", "context-compression webServer not active yet — advisor report route pending: %s", ADVISOR_REPORT_ROUTES.join(", "));
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
697
842
|
* The one service the catalog route actually needs. `llm` and
|
|
698
843
|
* `agentDefaultModel` are payload enrichment the handler resolves per request,
|
|
699
844
|
* never reasons to withhold the route.
|
|
@@ -797,7 +942,8 @@ const SHARED_SETTINGS = Symbol.for("dsh-context-compression-improved/settings-re
|
|
|
797
942
|
const Config = z.object({
|
|
798
943
|
presetOverlay: z.boolean().default(false),
|
|
799
944
|
estimatorCatalogRoute: z.boolean().default(false),
|
|
800
|
-
reviewQueueRoute: z.boolean().default(false)
|
|
945
|
+
reviewQueueRoute: z.boolean().default(false),
|
|
946
|
+
advisorReportRoute: z.boolean().default(false)
|
|
801
947
|
});
|
|
802
948
|
/** Register the persisted default read by the currently mounted root pruner. */
|
|
803
949
|
function apply(ctx, config = {}) {
|
|
@@ -807,6 +953,7 @@ function apply(ctx, config = {}) {
|
|
|
807
953
|
});
|
|
808
954
|
if (config.estimatorCatalogRoute === true) registerEstimatorCatalogRoute(ctx);
|
|
809
955
|
if (config.reviewQueueRoute === true) registerReviewQueueRoutes(ctx);
|
|
956
|
+
if (config.advisorReportRoute === true) registerAdvisorReportRoute(ctx);
|
|
810
957
|
if (config.presetOverlay !== true) return;
|
|
811
958
|
ctx.inject(["agentPresets"], (presetsCtx) => {
|
|
812
959
|
const installation = decorateAgentPresets(presetsCtx.agentPresets, {
|
|
@@ -82,6 +82,20 @@ interface PresetOptions {
|
|
|
82
82
|
readonly estimator: {
|
|
83
83
|
readonly mode: '' | 'host' | 'direct';
|
|
84
84
|
};
|
|
85
|
+
/**
|
|
86
|
+
* Advisory relevance advisor: statistics and suggestions only — every output
|
|
87
|
+
* (summaries, scores, decay, recertification) is observational and must never
|
|
88
|
+
* suppress, delay, or rewrite any reduction that would land. `''` (the
|
|
89
|
+
* default) keeps the advisor fully off.
|
|
90
|
+
*/
|
|
91
|
+
readonly advisor: {
|
|
92
|
+
readonly mode: '' | 'host' | 'direct';
|
|
93
|
+
readonly timeoutMs: number;
|
|
94
|
+
readonly refreshTurns: number;
|
|
95
|
+
readonly scoreThreshold: number;
|
|
96
|
+
readonly sampleLimit: number;
|
|
97
|
+
readonly minTokens: number;
|
|
98
|
+
};
|
|
85
99
|
/**
|
|
86
100
|
* Human-gated review pipeline (beta): edge/high-impact candidates queue for
|
|
87
101
|
* manual approval and execute in one merged batch at the next turn boundary
|
|
@@ -158,6 +172,13 @@ interface PresetOptionsSettings {
|
|
|
158
172
|
readonly estimatorBaseUrl?: string;
|
|
159
173
|
readonly estimatorApiKey?: string;
|
|
160
174
|
readonly estimatorTimeoutMs?: number;
|
|
175
|
+
/** Advisory advisor channel; `''` (the default) keeps the advisor off. */
|
|
176
|
+
readonly advisorMode?: '' | 'host' | 'direct';
|
|
177
|
+
readonly advisorTimeoutMs?: number;
|
|
178
|
+
readonly advisorRefreshTurns?: number;
|
|
179
|
+
readonly advisorScoreThreshold?: number;
|
|
180
|
+
readonly advisorSampleLimit?: number;
|
|
181
|
+
readonly advisorMinTokens?: number;
|
|
161
182
|
}
|
|
162
183
|
/** Durable global preference exposed through `ctx.settings`. */
|
|
163
184
|
interface ContextCompressionSettings {
|
|
@@ -336,6 +357,56 @@ interface PruneResult {
|
|
|
336
357
|
readonly tokensRemoved: number;
|
|
337
358
|
}
|
|
338
359
|
//#endregion
|
|
360
|
+
//#region src/runtime/tokenpilot/sidechannel.d.ts
|
|
361
|
+
/** Audit record of one side-channel call (Phase 13). All fields optional-safe. */
|
|
362
|
+
interface SideChannelAudit {
|
|
363
|
+
readonly ok: boolean;
|
|
364
|
+
readonly latencyMs: number;
|
|
365
|
+
/** host|direct plus the resolved provider/model identity. */
|
|
366
|
+
readonly channel?: string;
|
|
367
|
+
/** L2 coverage: content shown N of node content total M. */
|
|
368
|
+
readonly coverage?: {
|
|
369
|
+
readonly shown: number;
|
|
370
|
+
readonly total: number;
|
|
371
|
+
};
|
|
372
|
+
readonly reason?: string;
|
|
373
|
+
}
|
|
374
|
+
interface SideChannelRequest {
|
|
375
|
+
readonly system: string;
|
|
376
|
+
readonly user: string;
|
|
377
|
+
readonly signal: AbortSignal;
|
|
378
|
+
}
|
|
379
|
+
/** One bound side channel. `ask` resolves `undefined` on ANY failure. */
|
|
380
|
+
declare class SideChannel {
|
|
381
|
+
private readonly ctx;
|
|
382
|
+
private readonly options;
|
|
383
|
+
private readonly overrides?;
|
|
384
|
+
/**
|
|
385
|
+
* @param overrides - per-consumer overrides of the estimator-named options.
|
|
386
|
+
* The estimator itself never passes them (byte-identical behavior); the
|
|
387
|
+
* advisory advisor passes its own mode/timeout/output budget so both
|
|
388
|
+
* consumers share one transport without sharing one configuration.
|
|
389
|
+
*/
|
|
390
|
+
constructor(ctx: Context, options: PresetOptionsSettings, overrides?: {
|
|
391
|
+
readonly mode?: "" | "host" | "direct";
|
|
392
|
+
readonly timeoutMs?: number;
|
|
393
|
+
readonly maxTokens?: number;
|
|
394
|
+
} | undefined);
|
|
395
|
+
private get mode();
|
|
396
|
+
get enabled(): boolean;
|
|
397
|
+
ask(request: SideChannelRequest): Promise<string | undefined>;
|
|
398
|
+
/** Failure-open wrapper that also records one audit record per call. */
|
|
399
|
+
askAudited(request: SideChannelRequest): Promise<{
|
|
400
|
+
text?: string;
|
|
401
|
+
audit: SideChannelAudit;
|
|
402
|
+
}>;
|
|
403
|
+
identity(): string | undefined;
|
|
404
|
+
/** Same host-route resolution as the estimator: explicit, then host default. */
|
|
405
|
+
private resolveHostRoute;
|
|
406
|
+
private askHost;
|
|
407
|
+
private askDirect;
|
|
408
|
+
}
|
|
409
|
+
//#endregion
|
|
339
410
|
//#region src/runtime/tokenpilot/estimator.d.ts
|
|
340
411
|
/** Per-session estimator failure bookkeeping for exponential backoff. */
|
|
341
412
|
interface EstimatorFailures {
|
|
@@ -532,6 +603,8 @@ interface PrunerState {
|
|
|
532
603
|
readonly reviewClocks: WeakMap<Session, number>;
|
|
533
604
|
/** Estimator-reported remaining turns Ŝ per Session; advisory only. */
|
|
534
605
|
readonly estimatorRemainingTurns: WeakMap<Session, number>;
|
|
606
|
+
/** Per-session advisor side channel, constructed once with the advisor overrides. */
|
|
607
|
+
readonly advisorChannels: WeakMap<Session, SideChannel>;
|
|
535
608
|
/** Four-state outcome counters per Session (floating-window summary row). */
|
|
536
609
|
readonly reviewSummaries: WeakMap<Session, ReviewSessionSummary>;
|
|
537
610
|
}
|
|
@@ -847,6 +920,15 @@ declare class ToolResultPruner extends Service {
|
|
|
847
920
|
* superseded classification.
|
|
848
921
|
*/
|
|
849
922
|
private postflightEstimatorPass;
|
|
923
|
+
/**
|
|
924
|
+
* Advisory advisor pass at the turn boundary, strictly fire-and-forget.
|
|
925
|
+
* Produces todolist-bound tail-task summaries, incremental relevance
|
|
926
|
+
* scores, and a prefix-decay figure — all observational. Every short
|
|
927
|
+
* circuit below (mode off, re-entry, cooldown, no task semantics, no
|
|
928
|
+
* direct endpoint) returns without touching any state the pruning chain
|
|
929
|
+
* reads, so the default configuration adds exactly zero behavior.
|
|
930
|
+
*/
|
|
931
|
+
private postflightAdvisorPass;
|
|
850
932
|
/**
|
|
851
933
|
* The per-session review queue, or `undefined` while review mode is off
|
|
852
934
|
* (every review path must then behave exactly like before).
|