dsh-context-compression-improved 0.4.0-beta.1 → 0.5.0
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 +68 -36
- package/CHANGELOG.ko.md +67 -35
- package/CHANGELOG.md +195 -134
- package/CHANGELOG.zh.md +64 -36
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh.md +1 -1
- package/docs/installation.ja.md +2 -2
- package/docs/installation.ko.md +2 -2
- package/docs/installation.md +103 -78
- package/docs/installation.zh.md +100 -77
- package/docs/repair-log.md +54 -0
- package/package.json +1 -1
- package/packages/selector/lib/{config.js → advisor-state.js} +329 -5
- package/packages/selector/lib/client.d.ts +7 -0
- package/packages/selector/lib/client.js +33 -3
- package/packages/selector/lib/index.d.ts +7 -0
- package/packages/selector/lib/index.js +112 -3
- package/packages/selector/lib/pruner.d.ts +128 -1
- package/packages/selector/lib/pruner.js +2802 -1374
- package/packages/selector/src/client/ReviewOverlay.tsx +1 -1
- package/packages/selector/src/client/index.ts +1 -1
- package/packages/selector/src/client/preset-options.ts +2 -0
- package/packages/selector/src/index.ts +129 -49
- package/packages/selector/src/profiles.ts +48 -0
- package/packages/selector/src/pruner/content.ts +18 -5
- package/packages/selector/src/pruner/state.ts +3 -0
- package/packages/selector/src/pruner/types.ts +23 -5
- package/packages/selector/src/pruner.ts +297 -162
- package/packages/selector/src/runtime/adaptive-cost.ts +23 -12
- package/packages/selector/src/runtime/audit.ts +40 -2
- package/packages/selector/src/runtime/config.ts +88 -1
- package/packages/selector/src/runtime/measurement.ts +31 -2
- package/packages/selector/src/runtime/reducers.ts +1115 -97
- 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/dedup.ts +1 -1
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +8 -118
- package/packages/selector/src/runtime/tokenpilot/locator.ts +1 -1
- package/packages/selector/src/runtime/tokenpilot/proposal.ts +76 -32
- package/packages/selector/src/runtime/tokenpilot/read-state.ts +23 -2
- package/packages/selector/src/runtime/tokenpilot/review-registry.ts +117 -0
- package/packages/selector/src/runtime/tokenpilot/sidechannel.ts +303 -0
- package/packages/selector/src/runtime/toolclass.ts +103 -0
- package/packages/selector/src/runtime/types.ts +37 -0
- package/packages/selector/tests/advisor-report.host.spec.ts +223 -0
- package/packages/selector/tests/public/package-contract.client.spec.ts +2 -1
- package/packages/selector/tests/review-routes-registry.host.spec.ts +142 -0
- package/packages/selector/tests/runtime/adaptive-cost.spec.ts +7 -7
- 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 +88 -1
- package/packages/selector/tests/runtime/char-basis.spec.ts +30 -0
- package/packages/selector/tests/runtime/code-skeleton.spec.ts +14 -3
- package/packages/selector/tests/runtime/frequency-longstrings.spec.ts +74 -0
- package/packages/selector/tests/runtime/html-reducer.spec.ts +212 -0
- package/packages/selector/tests/runtime/line-mapping.spec.ts +153 -0
- package/packages/selector/tests/runtime/prose-reducers.spec.ts +133 -0
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +198 -27
- package/packages/selector/tests/runtime/read-input-cap.spec.ts +33 -0
- package/packages/selector/tests/runtime/search-reducer.spec.ts +110 -0
- package/packages/selector/tests/runtime/sidechannel.spec.ts +241 -0
- package/packages/selector/tests/runtime/toc-and-bundled.spec.ts +159 -0
- package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +12 -0
- package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +194 -0
- package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +70 -1
- package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +24 -0
- package/packages/selector/tests/runtime/toolclass.spec.ts +156 -0
- package/scripts/toolclass-corpus-replay.mjs +281 -0
|
@@ -394,7 +394,13 @@ function parsePresetOptionsSettings(value) {
|
|
|
394
394
|
"reviewMode",
|
|
395
395
|
"reviewTimeoutTurns",
|
|
396
396
|
"cacheHitDiscountAlpha",
|
|
397
|
-
"reviewHighImpactTokens"
|
|
397
|
+
"reviewHighImpactTokens",
|
|
398
|
+
"advisorMode",
|
|
399
|
+
"advisorTimeoutMs",
|
|
400
|
+
"advisorRefreshTurns",
|
|
401
|
+
"advisorScoreThreshold",
|
|
402
|
+
"advisorSampleLimit",
|
|
403
|
+
"advisorMinTokens"
|
|
398
404
|
]);
|
|
399
405
|
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
400
406
|
if (unknown !== void 0) throw new TypeError(`Context-compression presetOptions: unknown key "${unknown}"`);
|
|
@@ -410,6 +416,18 @@ function parsePresetOptionsSettings(value) {
|
|
|
410
416
|
}
|
|
411
417
|
const estimatorMode = value.estimatorMode;
|
|
412
418
|
if (estimatorMode !== void 0 && estimatorMode !== "" && estimatorMode !== "host" && estimatorMode !== "direct") throw new TypeError("Context-compression presetOptions.estimatorMode must be \"\", \"host\", or \"direct\"");
|
|
419
|
+
const advisorMode = value.advisorMode;
|
|
420
|
+
if (advisorMode !== void 0 && advisorMode !== "" && advisorMode !== "host" && advisorMode !== "direct") throw new TypeError("Context-compression presetOptions.advisorMode must be \"\", \"host\", or \"direct\"");
|
|
421
|
+
const advisorTimeoutMs = value.advisorTimeoutMs;
|
|
422
|
+
if (advisorTimeoutMs !== void 0 && (typeof advisorTimeoutMs !== "number" || !Number.isSafeInteger(advisorTimeoutMs) || advisorTimeoutMs < 100 || advisorTimeoutMs > 6e4)) throw new TypeError("Context-compression presetOptions.advisorTimeoutMs must be an integer between 100 and 60000");
|
|
423
|
+
const advisorRefreshTurns = value.advisorRefreshTurns;
|
|
424
|
+
if (advisorRefreshTurns !== void 0 && (typeof advisorRefreshTurns !== "number" || !Number.isSafeInteger(advisorRefreshTurns) || advisorRefreshTurns < 1)) throw new TypeError("Context-compression presetOptions.advisorRefreshTurns must be an integer of at least 1");
|
|
425
|
+
const advisorScoreThreshold = value.advisorScoreThreshold;
|
|
426
|
+
if (advisorScoreThreshold !== void 0 && (typeof advisorScoreThreshold !== "number" || !Number.isFinite(advisorScoreThreshold) || advisorScoreThreshold <= 0 || advisorScoreThreshold >= 1)) throw new TypeError("Context-compression presetOptions.advisorScoreThreshold must be a number strictly between 0 and 1");
|
|
427
|
+
const advisorSampleLimit = value.advisorSampleLimit;
|
|
428
|
+
if (advisorSampleLimit !== void 0 && (typeof advisorSampleLimit !== "number" || !Number.isSafeInteger(advisorSampleLimit) || advisorSampleLimit < 1 || advisorSampleLimit > 64)) throw new TypeError("Context-compression presetOptions.advisorSampleLimit must be an integer between 1 and 64");
|
|
429
|
+
const advisorMinTokens = value.advisorMinTokens;
|
|
430
|
+
if (advisorMinTokens !== void 0 && (typeof advisorMinTokens !== "number" || !Number.isSafeInteger(advisorMinTokens) || advisorMinTokens < 1)) throw new TypeError("Context-compression presetOptions.advisorMinTokens must be a positive integer");
|
|
413
431
|
const estimatorTimeoutMs = value.estimatorTimeoutMs;
|
|
414
432
|
if (estimatorTimeoutMs !== void 0 && (typeof estimatorTimeoutMs !== "number" || !Number.isSafeInteger(estimatorTimeoutMs) || estimatorTimeoutMs < 100 || estimatorTimeoutMs > 6e4)) throw new TypeError("Context-compression presetOptions.estimatorTimeoutMs must be an integer between 100 and 60000");
|
|
415
433
|
const reviewTimeoutTurns = value.reviewTimeoutTurns;
|
|
@@ -442,6 +460,12 @@ function parsePresetOptionsSettings(value) {
|
|
|
442
460
|
if (reviewTimeoutTurns !== void 0) result.reviewTimeoutTurns = reviewTimeoutTurns;
|
|
443
461
|
if (cacheHitDiscountAlpha !== void 0) result.cacheHitDiscountAlpha = cacheHitDiscountAlpha;
|
|
444
462
|
if (reviewHighImpactTokens !== void 0) result.reviewHighImpactTokens = reviewHighImpactTokens;
|
|
463
|
+
if (advisorMode !== void 0) result.advisorMode = advisorMode;
|
|
464
|
+
if (advisorTimeoutMs !== void 0) result.advisorTimeoutMs = advisorTimeoutMs;
|
|
465
|
+
if (advisorRefreshTurns !== void 0) result.advisorRefreshTurns = advisorRefreshTurns;
|
|
466
|
+
if (advisorScoreThreshold !== void 0) result.advisorScoreThreshold = advisorScoreThreshold;
|
|
467
|
+
if (advisorSampleLimit !== void 0) result.advisorSampleLimit = advisorSampleLimit;
|
|
468
|
+
if (advisorMinTokens !== void 0) result.advisorMinTokens = advisorMinTokens;
|
|
445
469
|
return result;
|
|
446
470
|
}
|
|
447
471
|
/** Settings schema used by the user-facing profile selector. */
|
|
@@ -518,6 +542,7 @@ const CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
518
542
|
"historyKeepRecentToolCalls",
|
|
519
543
|
"historyKeepRecentTokens",
|
|
520
544
|
"historyMinReclaimTokens",
|
|
545
|
+
"readInputCapChars",
|
|
521
546
|
"autoCompactThresholdPercent",
|
|
522
547
|
"presetOptions"
|
|
523
548
|
]);
|
|
@@ -542,6 +567,15 @@ function codePointLength(text) {
|
|
|
542
567
|
for (const _point of text) length++;
|
|
543
568
|
return length;
|
|
544
569
|
}
|
|
570
|
+
/** Express one token-named policy gate on the character basis. */
|
|
571
|
+
function charsForTokens(tokens) {
|
|
572
|
+
return tokens * 4;
|
|
573
|
+
}
|
|
574
|
+
/** Derive the telemetry-only token figure from a character measurement. */
|
|
575
|
+
function charsToTokens(chars) {
|
|
576
|
+
if (!Number.isFinite(chars) || chars <= 0) return 0;
|
|
577
|
+
return Math.max(1, Math.round(chars / 4));
|
|
578
|
+
}
|
|
545
579
|
/**
|
|
546
580
|
* Test whether a settings value names a supported compression profile.
|
|
547
581
|
* @param value - untrusted settings value.
|
|
@@ -575,6 +609,7 @@ function resolveConfig(config = {}) {
|
|
|
575
609
|
...config.historyKeepRecentToolCalls === void 0 ? {} : { historyKeepRecentToolCalls: config.historyKeepRecentToolCalls },
|
|
576
610
|
...config.historyKeepRecentTokens === void 0 ? {} : { historyKeepRecentTokens: config.historyKeepRecentTokens },
|
|
577
611
|
...config.historyMinReclaimTokens === void 0 ? {} : { historyMinReclaimTokens: config.historyMinReclaimTokens },
|
|
612
|
+
...config.readInputCapChars === void 0 ? {} : { readInputCapChars: config.readInputCapChars },
|
|
578
613
|
...config.autoCompactThresholdPercent === void 0 ? {} : { autoCompactThresholdPercent: config.autoCompactThresholdPercent },
|
|
579
614
|
...config.presetOptions === void 0 ? {} : { presetOptions: config.presetOptions }
|
|
580
615
|
};
|
|
@@ -589,7 +624,8 @@ function resolveConfig(config = {}) {
|
|
|
589
624
|
"aggregateTriggerTokens",
|
|
590
625
|
"aggregateTargetTokens",
|
|
591
626
|
"historyTriggerTokens",
|
|
592
|
-
"historyMinReclaimTokens"
|
|
627
|
+
"historyMinReclaimTokens",
|
|
628
|
+
"readInputCapChars"
|
|
593
629
|
]) {
|
|
594
630
|
const value = resolved[key];
|
|
595
631
|
if (value !== void 0) assertPositiveInteger(key, value);
|
|
@@ -648,7 +684,15 @@ const PRESET_OPTION_DEFAULTS = deepFreeze({
|
|
|
648
684
|
reviewMode: false,
|
|
649
685
|
reviewTimeoutTurns: 6,
|
|
650
686
|
cacheHitDiscountAlpha: .1,
|
|
651
|
-
reviewHighImpactTokens: 4e3
|
|
687
|
+
reviewHighImpactTokens: 4e3,
|
|
688
|
+
advisor: {
|
|
689
|
+
mode: "",
|
|
690
|
+
timeoutMs: 8e3,
|
|
691
|
+
refreshTurns: 8,
|
|
692
|
+
scoreThreshold: .35,
|
|
693
|
+
sampleLimit: 16,
|
|
694
|
+
minTokens: 250
|
|
695
|
+
}
|
|
652
696
|
});
|
|
653
697
|
/**
|
|
654
698
|
* Merge persisted presetOptions overrides over the tokenpilot-inspired
|
|
@@ -668,7 +712,15 @@ function mergePresetOptions(overrides) {
|
|
|
668
712
|
reviewMode: overrides.reviewMode ?? PRESET_OPTION_DEFAULTS.reviewMode,
|
|
669
713
|
reviewTimeoutTurns: overrides.reviewTimeoutTurns ?? PRESET_OPTION_DEFAULTS.reviewTimeoutTurns,
|
|
670
714
|
cacheHitDiscountAlpha: overrides.cacheHitDiscountAlpha ?? PRESET_OPTION_DEFAULTS.cacheHitDiscountAlpha,
|
|
671
|
-
reviewHighImpactTokens: overrides.reviewHighImpactTokens ?? PRESET_OPTION_DEFAULTS.reviewHighImpactTokens
|
|
715
|
+
reviewHighImpactTokens: overrides.reviewHighImpactTokens ?? PRESET_OPTION_DEFAULTS.reviewHighImpactTokens,
|
|
716
|
+
advisor: {
|
|
717
|
+
mode: overrides.advisorMode ?? PRESET_OPTION_DEFAULTS.advisor.mode,
|
|
718
|
+
timeoutMs: overrides.advisorTimeoutMs ?? PRESET_OPTION_DEFAULTS.advisor.timeoutMs,
|
|
719
|
+
refreshTurns: overrides.advisorRefreshTurns ?? PRESET_OPTION_DEFAULTS.advisor.refreshTurns,
|
|
720
|
+
scoreThreshold: overrides.advisorScoreThreshold ?? PRESET_OPTION_DEFAULTS.advisor.scoreThreshold,
|
|
721
|
+
sampleLimit: overrides.advisorSampleLimit ?? PRESET_OPTION_DEFAULTS.advisor.sampleLimit,
|
|
722
|
+
minTokens: overrides.advisorMinTokens ?? PRESET_OPTION_DEFAULTS.advisor.minTokens
|
|
723
|
+
}
|
|
672
724
|
});
|
|
673
725
|
}
|
|
674
726
|
/**
|
|
@@ -840,6 +892,7 @@ function resolvePolicy(config, profile, custom = DEFAULT_CUSTOM_COMPRESSION_POLI
|
|
|
840
892
|
historyKeepRecentToolCalls: config.historyKeepRecentToolCalls ?? preset.historyKeepRecentToolCalls,
|
|
841
893
|
historyKeepRecentTokens: config.historyKeepRecentTokens ?? linkage?.historyKeepRecentTokens ?? preset.historyKeepRecentTokens,
|
|
842
894
|
historyMinReclaimTokens: config.historyMinReclaimTokens ?? linkage?.historyMinReclaimTokens ?? preset.historyMinReclaimTokens,
|
|
895
|
+
...config.readInputCapChars === void 0 ? {} : { readInputCapChars: config.readInputCapChars },
|
|
843
896
|
...linkage === void 0 ? {} : {
|
|
844
897
|
autoCompactTokens: linkage.autoCompactTokens,
|
|
845
898
|
microDeadlineTokens: linkage.microDeadlineTokens
|
|
@@ -849,6 +902,7 @@ function resolvePolicy(config, profile, custom = DEFAULT_CUSTOM_COMPRESSION_POLI
|
|
|
849
902
|
if (policy.nativeTargetTokens >= policy.nativeTriggerTokens && profile === "native") throw new Error("context compression policy: native target must be below trigger");
|
|
850
903
|
if (policy.freshTargetTokens >= policy.freshTriggerTokens && policy.freshEnabled) throw new Error("context compression policy: fresh target must be below trigger");
|
|
851
904
|
if (policy.aggregateTargetTokens >= policy.aggregateTriggerTokens && policy.freshEnabled) throw new Error("context compression policy: aggregate target must be below trigger");
|
|
905
|
+
if (policy.readInputCapChars !== void 0 && policy.readInputCapChars <= policy.freshTriggerTokens * 4) throw new Error("context compression policy: read input cap would silence the fresh path");
|
|
852
906
|
return deepFreeze(policy);
|
|
853
907
|
}
|
|
854
908
|
function assertTargetBelowTrigger(label, target, trigger) {
|
|
@@ -862,4 +916,274 @@ function assertNonNegativeInteger(name, value) {
|
|
|
862
916
|
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`ToolResultPruneConfig: ${name} (${String(value)}) must be a non-negative safe integer`);
|
|
863
917
|
}
|
|
864
918
|
//#endregion
|
|
865
|
-
|
|
919
|
+
//#region src/runtime/tokenpilot/review-queue.ts
|
|
920
|
+
/** In-memory store: the fail-open fallback when no durable seam is available. */
|
|
921
|
+
var MemoryReviewStore = class {
|
|
922
|
+
sessions = /* @__PURE__ */ new Map();
|
|
923
|
+
load(sessionId) {
|
|
924
|
+
return this.sessions.get(sessionId);
|
|
925
|
+
}
|
|
926
|
+
save(sessionId, record) {
|
|
927
|
+
this.sessions.set(sessionId, record);
|
|
928
|
+
}
|
|
929
|
+
ids() {
|
|
930
|
+
return [...this.sessions.keys()];
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
var ReviewQueue = class {
|
|
934
|
+
store;
|
|
935
|
+
options;
|
|
936
|
+
constructor(store, options) {
|
|
937
|
+
this.store = store;
|
|
938
|
+
this.options = options;
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Fail-open store access: a throwing seam must never break the compression
|
|
942
|
+
* pipeline. Reads degrade to "no stored record"; writes degrade to losing
|
|
943
|
+
* durability for that call (the store itself is expected to warn).
|
|
944
|
+
*/
|
|
945
|
+
safeLoad(sessionId) {
|
|
946
|
+
try {
|
|
947
|
+
return this.store.load(sessionId);
|
|
948
|
+
} catch {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
safeSave(sessionId, record) {
|
|
953
|
+
try {
|
|
954
|
+
this.store.save(sessionId, record);
|
|
955
|
+
} catch {}
|
|
956
|
+
}
|
|
957
|
+
sessionRecord(sessionId) {
|
|
958
|
+
return this.safeLoad(sessionId) ?? {
|
|
959
|
+
version: 1,
|
|
960
|
+
proposals: []
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
/**
|
|
964
|
+
* Queue one classified proposal. A repeated classification of the same
|
|
965
|
+
* content re-does nothing but refresh the patience clock, so re-enqueue
|
|
966
|
+
* cannot duplicate a live proposal.
|
|
967
|
+
* @returns `false` when an identical live proposal already exists.
|
|
968
|
+
*/
|
|
969
|
+
enqueue(sessionId, skeleton, turnIndex) {
|
|
970
|
+
const record = this.sessionRecord(sessionId);
|
|
971
|
+
const existing = record.proposals.find((entry) => entry.id === skeleton.id);
|
|
972
|
+
if (existing !== void 0 && existing.status !== "expired") {
|
|
973
|
+
existing.lastTurnIndex = turnIndex;
|
|
974
|
+
this.safeSave(sessionId, record);
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
const proposal = {
|
|
978
|
+
id: skeleton.id,
|
|
979
|
+
sessionId,
|
|
980
|
+
kind: skeleton.kind,
|
|
981
|
+
items: skeleton.items.map((item) => ({ ...item })),
|
|
982
|
+
benefit: { ...skeleton.benefit },
|
|
983
|
+
status: "pending",
|
|
984
|
+
enqueuedTurn: turnIndex,
|
|
985
|
+
lastTurnIndex: turnIndex
|
|
986
|
+
};
|
|
987
|
+
this.safeSave(sessionId, {
|
|
988
|
+
version: 1,
|
|
989
|
+
proposals: [...record.proposals.filter((entry) => entry.id !== skeleton.id), proposal]
|
|
990
|
+
});
|
|
991
|
+
return true;
|
|
992
|
+
}
|
|
993
|
+
/** Live pending proposals of one session, oldest enqueue first. */
|
|
994
|
+
listPending(sessionId) {
|
|
995
|
+
return this.sessionRecord(sessionId).proposals.filter((entry) => entry.status === "pending").sort((left, right) => left.enqueuedTurn - right.enqueuedTurn);
|
|
996
|
+
}
|
|
997
|
+
/** Approved proposals waiting for the next turn-boundary batch. */
|
|
998
|
+
listApproved(sessionId) {
|
|
999
|
+
return this.sessionRecord(sessionId).proposals.filter((entry) => entry.status === "approved").sort((left, right) => left.enqueuedTurn - right.enqueuedTurn);
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* Transition one pending proposal. Idempotent: deciding an unknown id or a
|
|
1003
|
+
* non-pending proposal changes nothing and reports the miss.
|
|
1004
|
+
*/
|
|
1005
|
+
decide(sessionId, id, decision) {
|
|
1006
|
+
const record = this.sessionRecord(sessionId);
|
|
1007
|
+
const proposal = record.proposals.find((entry) => entry.id === id);
|
|
1008
|
+
if (proposal === void 0) return {
|
|
1009
|
+
ok: false,
|
|
1010
|
+
reason: "unknown-proposal"
|
|
1011
|
+
};
|
|
1012
|
+
if (proposal.status !== "pending") return {
|
|
1013
|
+
ok: false,
|
|
1014
|
+
reason: "not-pending"
|
|
1015
|
+
};
|
|
1016
|
+
proposal.status = decision;
|
|
1017
|
+
this.safeSave(sessionId, record);
|
|
1018
|
+
return { ok: true };
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Expire every pending proposal whose patience has run out at this turn
|
|
1022
|
+
* boundary. Expired proposals are removed from the store (the summary view
|
|
1023
|
+
* aggregates them from the audit log instead).
|
|
1024
|
+
* @returns the expired proposals, for the caller's audit emission.
|
|
1025
|
+
*/
|
|
1026
|
+
expireTurn(sessionId, turnIndex) {
|
|
1027
|
+
const record = this.sessionRecord(sessionId);
|
|
1028
|
+
const keep = [];
|
|
1029
|
+
const expired = [];
|
|
1030
|
+
for (const proposal of record.proposals) {
|
|
1031
|
+
if (proposal.status === "pending" && turnIndex - proposal.lastTurnIndex > this.options.timeoutTurns) {
|
|
1032
|
+
expired.push({
|
|
1033
|
+
...proposal,
|
|
1034
|
+
status: "expired"
|
|
1035
|
+
});
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
keep.push(proposal);
|
|
1039
|
+
}
|
|
1040
|
+
if (expired.length > 0) this.safeSave(sessionId, {
|
|
1041
|
+
version: 1,
|
|
1042
|
+
proposals: keep
|
|
1043
|
+
});
|
|
1044
|
+
return expired;
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Settle an approved proposal with its execution receipt and retire it from
|
|
1048
|
+
* the live store. The caller is responsible for auditing the receipt; the
|
|
1049
|
+
* queue only records which proposal left and why.
|
|
1050
|
+
*/
|
|
1051
|
+
recordReceipt(sessionId, id, receipt) {
|
|
1052
|
+
const record = this.sessionRecord(sessionId);
|
|
1053
|
+
const proposal = record.proposals.find((entry) => entry.id === id);
|
|
1054
|
+
if (proposal === void 0 || proposal.status !== "approved") return void 0;
|
|
1055
|
+
this.safeSave(sessionId, {
|
|
1056
|
+
version: 1,
|
|
1057
|
+
proposals: record.proposals.filter((entry) => entry.id !== id)
|
|
1058
|
+
});
|
|
1059
|
+
return {
|
|
1060
|
+
...proposal,
|
|
1061
|
+
receipt
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
//#endregion
|
|
1066
|
+
//#region src/runtime/tokenpilot/review-registry.ts
|
|
1067
|
+
/**
|
|
1068
|
+
* Scope-independent handle on the live review pipeline.
|
|
1069
|
+
*
|
|
1070
|
+
* The R4 HTTP routes are registered on the plugin's TOP-LEVEL fiber
|
|
1071
|
+
* (`cordis.patch.yml` → `context-compression-improved-estimator-catalog`),
|
|
1072
|
+
* but every `ToolResultPruner` is mounted inside an agent preset's isolated
|
|
1073
|
+
* group — `canonicalCompressionRows()` declares
|
|
1074
|
+
* `isolate: { compaction: true, toolResultPruner: true }` — so
|
|
1075
|
+
* `ctx.get('toolResultPruner')` at the top level is always `undefined` and the
|
|
1076
|
+
* queue route could only ever answer 503 "review pipeline unavailable".
|
|
1077
|
+
*
|
|
1078
|
+
* Ownership, not transport, was in the wrong place: the queue records are
|
|
1079
|
+
* already keyed by session id, so the store belongs to the plugin rather than
|
|
1080
|
+
* to one pruner instance. Every instance shares one store and publishes itself
|
|
1081
|
+
* here, which lets a top-level reader reach whichever instance currently holds
|
|
1082
|
+
* a session's proposals.
|
|
1083
|
+
*
|
|
1084
|
+
* The durable seam already behaves this way — `REVIEW_STORAGE_DOMAIN` /
|
|
1085
|
+
* `REVIEW_STORAGE_TABLE` are constants, so every instance opens the same table.
|
|
1086
|
+
* Only the in-memory fallback was per-instance, and that is what this module
|
|
1087
|
+
* makes shared.
|
|
1088
|
+
*
|
|
1089
|
+
* @module dsh-context-compression-improved/review-registry
|
|
1090
|
+
*/
|
|
1091
|
+
/**
|
|
1092
|
+
* The one in-memory fallback every pruner instance starts from. Session ids are
|
|
1093
|
+
* globally unique and `ReviewSessionRecord` is keyed by them, so a single store
|
|
1094
|
+
* is semantically identical to one store per instance — except that a reader
|
|
1095
|
+
* reaching any instance now observes every session.
|
|
1096
|
+
*/
|
|
1097
|
+
const sharedStore = new MemoryReviewStore();
|
|
1098
|
+
const live = /* @__PURE__ */ new Set();
|
|
1099
|
+
/** The process-wide review queue store shared by every pruner instance. */
|
|
1100
|
+
function sharedReviewStore() {
|
|
1101
|
+
return sharedStore;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Publish one pruner instance for scope-independent readers.
|
|
1105
|
+
* @param pruner - the instance to publish.
|
|
1106
|
+
* @returns the disposer removing it, for `ctx.effect`.
|
|
1107
|
+
*/
|
|
1108
|
+
function registerReviewPruner(pruner) {
|
|
1109
|
+
live.add(pruner);
|
|
1110
|
+
return () => {
|
|
1111
|
+
live.delete(pruner);
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Resolve a live pruner instance for the top-level routes.
|
|
1116
|
+
*
|
|
1117
|
+
* Any instance can serve an aggregate read because the store is shared, and a
|
|
1118
|
+
* session-scoped read is answered from that same store. Instances that have
|
|
1119
|
+
* upgraded to the durable seam read the same table, so the answer does not
|
|
1120
|
+
* depend on which instance this happens to return.
|
|
1121
|
+
*
|
|
1122
|
+
* @returns a live instance, or `undefined` when no preset has been composed yet.
|
|
1123
|
+
*/
|
|
1124
|
+
function resolveReviewPruner() {
|
|
1125
|
+
return live.values().next().value;
|
|
1126
|
+
}
|
|
1127
|
+
const advisorStates = /* @__PURE__ */ new WeakMap();
|
|
1128
|
+
/**
|
|
1129
|
+
* The per-session advisor state, created on first touch.
|
|
1130
|
+
* @param session - the session to key the state on (by object identity).
|
|
1131
|
+
*/
|
|
1132
|
+
function getAdvisorState(session) {
|
|
1133
|
+
let state = advisorStates.get(session);
|
|
1134
|
+
if (state === void 0) {
|
|
1135
|
+
state = {
|
|
1136
|
+
todoVersion: void 0,
|
|
1137
|
+
summary: void 0,
|
|
1138
|
+
lastSummaryTurn: -1,
|
|
1139
|
+
watermarkSeq: 0,
|
|
1140
|
+
scores: /* @__PURE__ */ new Map(),
|
|
1141
|
+
recertified: /* @__PURE__ */ new Map(),
|
|
1142
|
+
failures: void 0,
|
|
1143
|
+
inFlight: false,
|
|
1144
|
+
lastDecay: void 0
|
|
1145
|
+
};
|
|
1146
|
+
advisorStates.set(session, state);
|
|
1147
|
+
}
|
|
1148
|
+
return state;
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Insert or refresh one score with LRU semantics: a re-touched seq moves to
|
|
1152
|
+
* the newest position, and the oldest entry is evicted once the map exceeds
|
|
1153
|
+
* {@link ADVISOR_SCORES_LIMIT}.
|
|
1154
|
+
*/
|
|
1155
|
+
function recordScore(state, seq, entry) {
|
|
1156
|
+
state.scores.delete(seq);
|
|
1157
|
+
state.scores.set(seq, entry);
|
|
1158
|
+
if (state.scores.size > 64) {
|
|
1159
|
+
const oldest = state.scores.keys().next();
|
|
1160
|
+
if (oldest.done !== true) state.scores.delete(oldest.value);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Mark one seq as LLM-recertified low relevance (a suggestion for later
|
|
1165
|
+
* history-aggressiveness decisions, consumed by nothing in this round).
|
|
1166
|
+
* Bounded at {@link ADVISOR_RECERTIFIED_LIMIT} with the same LRU eviction.
|
|
1167
|
+
*/
|
|
1168
|
+
function recordRecertified(state, seq, turn) {
|
|
1169
|
+
state.recertified.delete(seq);
|
|
1170
|
+
state.recertified.set(seq, turn);
|
|
1171
|
+
if (state.recertified.size > 64) {
|
|
1172
|
+
const oldest = state.recertified.keys().next();
|
|
1173
|
+
if (oldest.done !== true) state.recertified.delete(oldest.value);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
/**
|
|
1177
|
+
* Drop every cached artifact that depends on the task semantics: a changed
|
|
1178
|
+
* todo version invalidates the summary and makes all eligible candidates
|
|
1179
|
+
* rescore-worthy (the watermark alone would otherwise hide them).
|
|
1180
|
+
*/
|
|
1181
|
+
function invalidateOnTaskChange(state, todoVersion) {
|
|
1182
|
+
if (state.todoVersion === todoVersion) return false;
|
|
1183
|
+
state.todoVersion = todoVersion;
|
|
1184
|
+
state.summary = void 0;
|
|
1185
|
+
state.lastSummaryTurn = -1;
|
|
1186
|
+
return true;
|
|
1187
|
+
}
|
|
1188
|
+
//#endregion
|
|
1189
|
+
export { DEFAULT_CUSTOM_COMPRESSION_POLICY as C, COMPRESSION_PROFILES as D, deepFreeze as E, CustomCompressionPolicySchema as S, assertNever as T, isCompressionProfile as _, registerReviewPruner as a, resolveConfig as b, ReviewQueue as c, ContextCompressionSettingsSchema as d, DEFAULTS as f, codePointLength as g, charsToTokens as h, recordScore as i, AUTO_COMPACT_THRESHOLD_LIMITS as l, charsForTokens as m, invalidateOnTaskChange as n, resolveReviewPruner as o, PRUNE_MARKER as p, recordRecertified as r, sharedReviewStore as s, getAdvisorState as t, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE as u, isValidAutoCompactThresholdPercent as v, resolveCustomPolicy as w, resolvePolicy as x, parseContextCompressionSettings as y };
|
|
@@ -89,6 +89,13 @@ interface PresetOptionsSettings {
|
|
|
89
89
|
readonly reviewTimeoutTurns?: number;
|
|
90
90
|
readonly cacheHitDiscountAlpha?: number;
|
|
91
91
|
readonly reviewHighImpactTokens?: number;
|
|
92
|
+
/** Advisory advisor channel; `''` (the default) keeps the advisor off. */
|
|
93
|
+
readonly advisorMode?: '' | 'host' | 'direct';
|
|
94
|
+
readonly advisorTimeoutMs?: number;
|
|
95
|
+
readonly advisorRefreshTurns?: number;
|
|
96
|
+
readonly advisorScoreThreshold?: number;
|
|
97
|
+
readonly advisorSampleLimit?: number;
|
|
98
|
+
readonly advisorMinTokens?: number;
|
|
92
99
|
}
|
|
93
100
|
/** Durable settings section owned by this package. */
|
|
94
101
|
interface ContextCompressionSettings {
|
|
@@ -82,7 +82,13 @@ window.__ModuleLoader__.load({
|
|
|
82
82
|
"reviewMode",
|
|
83
83
|
"reviewTimeoutTurns",
|
|
84
84
|
"cacheHitDiscountAlpha",
|
|
85
|
-
"reviewHighImpactTokens"
|
|
85
|
+
"reviewHighImpactTokens",
|
|
86
|
+
"advisorMode",
|
|
87
|
+
"advisorTimeoutMs",
|
|
88
|
+
"advisorRefreshTurns",
|
|
89
|
+
"advisorScoreThreshold",
|
|
90
|
+
"advisorSampleLimit",
|
|
91
|
+
"advisorMinTokens"
|
|
86
92
|
]);
|
|
87
93
|
if (Object.keys(value).some((key) => !allowed.has(key))) return void 0;
|
|
88
94
|
for (const key of [
|
|
@@ -97,6 +103,8 @@ window.__ModuleLoader__.load({
|
|
|
97
103
|
}
|
|
98
104
|
const estimatorMode = value.estimatorMode;
|
|
99
105
|
if (estimatorMode !== void 0 && estimatorMode !== "" && estimatorMode !== "host" && estimatorMode !== "direct") return;
|
|
106
|
+
const advisorMode = value.advisorMode;
|
|
107
|
+
if (advisorMode !== void 0 && advisorMode !== "" && advisorMode !== "host" && advisorMode !== "direct") return;
|
|
100
108
|
for (const key of [
|
|
101
109
|
"estimatorProvider",
|
|
102
110
|
"estimatorModel",
|
|
@@ -114,6 +122,16 @@ window.__ModuleLoader__.load({
|
|
|
114
122
|
if (cacheHitDiscountAlpha !== void 0 && (typeof cacheHitDiscountAlpha !== "number" || !Number.isFinite(cacheHitDiscountAlpha) || cacheHitDiscountAlpha <= 0 || cacheHitDiscountAlpha >= 1)) return;
|
|
115
123
|
const reviewHighImpactTokens = value.reviewHighImpactTokens;
|
|
116
124
|
if (reviewHighImpactTokens !== void 0 && (typeof reviewHighImpactTokens !== "number" || !Number.isSafeInteger(reviewHighImpactTokens) || reviewHighImpactTokens < 0)) return;
|
|
125
|
+
const advisorTimeoutMs = value.advisorTimeoutMs;
|
|
126
|
+
if (advisorTimeoutMs !== void 0 && (typeof advisorTimeoutMs !== "number" || !Number.isSafeInteger(advisorTimeoutMs) || advisorTimeoutMs < 100 || advisorTimeoutMs > 6e4)) return;
|
|
127
|
+
const advisorRefreshTurns = value.advisorRefreshTurns;
|
|
128
|
+
if (advisorRefreshTurns !== void 0 && (typeof advisorRefreshTurns !== "number" || !Number.isSafeInteger(advisorRefreshTurns) || advisorRefreshTurns < 1)) return;
|
|
129
|
+
const advisorScoreThreshold = value.advisorScoreThreshold;
|
|
130
|
+
if (advisorScoreThreshold !== void 0 && (typeof advisorScoreThreshold !== "number" || !Number.isFinite(advisorScoreThreshold) || advisorScoreThreshold <= 0 || advisorScoreThreshold >= 1)) return;
|
|
131
|
+
const advisorSampleLimit = value.advisorSampleLimit;
|
|
132
|
+
if (advisorSampleLimit !== void 0 && (typeof advisorSampleLimit !== "number" || !Number.isSafeInteger(advisorSampleLimit) || advisorSampleLimit < 1 || advisorSampleLimit > 64)) return;
|
|
133
|
+
const advisorMinTokens = value.advisorMinTokens;
|
|
134
|
+
if (advisorMinTokens !== void 0 && (typeof advisorMinTokens !== "number" || !Number.isSafeInteger(advisorMinTokens) || advisorMinTokens < 1)) return;
|
|
117
135
|
const decoded = {};
|
|
118
136
|
if (value.dedupeToolResults !== void 0) decoded.dedupeToolResults = value.dedupeToolResults;
|
|
119
137
|
if (value.summaryLocator !== void 0) decoded.summaryLocator = value.summaryLocator;
|
|
@@ -129,6 +147,12 @@ window.__ModuleLoader__.load({
|
|
|
129
147
|
if (reviewTimeoutTurns !== void 0) decoded.reviewTimeoutTurns = reviewTimeoutTurns;
|
|
130
148
|
if (cacheHitDiscountAlpha !== void 0) decoded.cacheHitDiscountAlpha = cacheHitDiscountAlpha;
|
|
131
149
|
if (reviewHighImpactTokens !== void 0) decoded.reviewHighImpactTokens = reviewHighImpactTokens;
|
|
150
|
+
if (advisorMode !== void 0) decoded.advisorMode = advisorMode;
|
|
151
|
+
if (advisorTimeoutMs !== void 0) decoded.advisorTimeoutMs = advisorTimeoutMs;
|
|
152
|
+
if (advisorRefreshTurns !== void 0) decoded.advisorRefreshTurns = advisorRefreshTurns;
|
|
153
|
+
if (advisorScoreThreshold !== void 0) decoded.advisorScoreThreshold = advisorScoreThreshold;
|
|
154
|
+
if (advisorSampleLimit !== void 0) decoded.advisorSampleLimit = advisorSampleLimit;
|
|
155
|
+
if (advisorMinTokens !== void 0) decoded.advisorMinTokens = advisorMinTokens;
|
|
132
156
|
return decoded;
|
|
133
157
|
}
|
|
134
158
|
/**
|
|
@@ -1544,7 +1568,13 @@ window.__ModuleLoader__.load({
|
|
|
1544
1568
|
"reviewMode",
|
|
1545
1569
|
"reviewTimeoutTurns",
|
|
1546
1570
|
"cacheHitDiscountAlpha",
|
|
1547
|
-
"reviewHighImpactTokens"
|
|
1571
|
+
"reviewHighImpactTokens",
|
|
1572
|
+
"advisorMode",
|
|
1573
|
+
"advisorTimeoutMs",
|
|
1574
|
+
"advisorRefreshTurns",
|
|
1575
|
+
"advisorScoreThreshold",
|
|
1576
|
+
"advisorSampleLimit",
|
|
1577
|
+
"advisorMinTokens"
|
|
1548
1578
|
];
|
|
1549
1579
|
/**
|
|
1550
1580
|
* Plan the path ops one patch needs against the section currently stored.
|
|
@@ -1768,7 +1798,7 @@ window.__ModuleLoader__.load({
|
|
|
1768
1798
|
}
|
|
1769
1799
|
};
|
|
1770
1800
|
pull();
|
|
1771
|
-
let unsubscribe
|
|
1801
|
+
let unsubscribe;
|
|
1772
1802
|
try {
|
|
1773
1803
|
unsubscribe = scope.subscribe(pull);
|
|
1774
1804
|
} catch {
|
|
@@ -24,6 +24,13 @@ interface Config {
|
|
|
24
24
|
* transport and simply never appears.
|
|
25
25
|
*/
|
|
26
26
|
reviewQueueRoute?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Register the advisory advisor's read-only HTTP report route (decay
|
|
29
|
+
* figure, task summary, score distribution). Same Bundle opt-in semantics
|
|
30
|
+
* as `reviewQueueRoute`; the advisor itself stays off until the user turns
|
|
31
|
+
* it on through the `presetOptions.advisor*` settings keys.
|
|
32
|
+
*/
|
|
33
|
+
advisorReportRoute?: boolean;
|
|
27
34
|
}
|
|
28
35
|
/** Loader validation for the standalone Bundle opt-in. */
|
|
29
36
|
declare const Config: z<Config>;
|
|
@@ -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";
|
|
@@ -459,9 +459,20 @@ const CONTEXT_COMPRESSION_NAMESPACE = CONTEXT_COMPRESSION_SETTINGS_NAMESPACE;
|
|
|
459
459
|
const ESTIMATOR_CATALOG_ROUTES = ["/endpoint/dsh-context-compression-improved/estimator-catalog", "/api/dsh-context-compression-improved/estimator-catalog"];
|
|
460
460
|
const REVIEW_QUEUE_ROUTES = ["/endpoint/dsh-context-compression-improved/review-queue", "/api/dsh-context-compression-improved/review-queue"];
|
|
461
461
|
const REVIEW_DECIDE_ROUTES = ["/endpoint/dsh-context-compression-improved/review-decide", "/api/dsh-context-compression-improved/review-decide"];
|
|
462
|
+
const ADVISOR_REPORT_ROUTES = ["/endpoint/dsh-context-compression-improved/advisor-report", "/api/dsh-context-compression-improved/advisor-report"];
|
|
463
|
+
/**
|
|
464
|
+
* Resolve the review pipeline for the top-level routes.
|
|
465
|
+
*
|
|
466
|
+
* A top-level `toolResultPruner` service wins when a deployment actually mounts
|
|
467
|
+
* one, but in production every pruner lives inside an agent preset's isolated
|
|
468
|
+
* group, so the registry is the path that resolves. Without the fallback the
|
|
469
|
+
* queue route answered 503 "review pipeline unavailable" on every request while
|
|
470
|
+
* the review pipeline itself was running normally.
|
|
471
|
+
*/
|
|
462
472
|
function reviewPrunerOf(readService) {
|
|
463
473
|
const candidate = readService("toolResultPruner");
|
|
464
|
-
|
|
474
|
+
if (typeof candidate?.listReviewProposals === "function" && typeof candidate?.decideReviewProposal === "function") return candidate;
|
|
475
|
+
return resolveReviewPruner();
|
|
465
476
|
}
|
|
466
477
|
function sessionFor(readService, sessionId) {
|
|
467
478
|
const agents = readService("agents");
|
|
@@ -684,6 +695,102 @@ function registerReviewQueueRoutes(ctx) {
|
|
|
684
695
|
log("warn", "context-compression webServer not active yet — review routes pending: %s", REVIEW_QUEUE_ROUTES.join(", "));
|
|
685
696
|
}
|
|
686
697
|
/**
|
|
698
|
+
* Serve the advisory advisor's read-only report route (same registration
|
|
699
|
+
* skeleton as the review routes):
|
|
700
|
+
*
|
|
701
|
+
* `GET .../advisor-report?sessionId=…` → the session's prefix-decay figure,
|
|
702
|
+
* the todolist-bound task summary, and the score distribution. Content-free
|
|
703
|
+
* by construction: task semantics are LLM-derived summaries, never message
|
|
704
|
+
* text, and no score reason or candidate preview is ever returned.
|
|
705
|
+
* Unknown session → 404; no agents service → 503. A session whose advisor
|
|
706
|
+
* never ran reports nulls and empty arrays, not an error.
|
|
707
|
+
*/
|
|
708
|
+
function registerAdvisorReportRoute(ctx) {
|
|
709
|
+
const readService = (name) => {
|
|
710
|
+
try {
|
|
711
|
+
return ctx.get(name);
|
|
712
|
+
} catch {
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
const log = (level, message, ...args) => {
|
|
717
|
+
console[level](message, ...args);
|
|
718
|
+
};
|
|
719
|
+
const getHandler = (req, res) => {
|
|
720
|
+
if (typeof readService("agents")?.get !== "function") {
|
|
721
|
+
reviewJson(res, 503, {
|
|
722
|
+
ok: false,
|
|
723
|
+
error: "advisor report unavailable"
|
|
724
|
+
});
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
let sessionId = "";
|
|
728
|
+
try {
|
|
729
|
+
sessionId = new URL(String(req.url ?? ""), "http://localhost").searchParams.get("sessionId") ?? "";
|
|
730
|
+
} catch {}
|
|
731
|
+
if (sessionId === "") {
|
|
732
|
+
reviewJson(res, 400, {
|
|
733
|
+
ok: false,
|
|
734
|
+
error: "sessionId is required"
|
|
735
|
+
});
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
const session = sessionFor(readService, sessionId);
|
|
739
|
+
if (session === void 0) {
|
|
740
|
+
reviewJson(res, 404, {
|
|
741
|
+
ok: false,
|
|
742
|
+
error: "unknown session"
|
|
743
|
+
});
|
|
744
|
+
return;
|
|
745
|
+
}
|
|
746
|
+
const state = getAdvisorState(session);
|
|
747
|
+
reviewJson(res, 200, {
|
|
748
|
+
ok: true,
|
|
749
|
+
sessionId,
|
|
750
|
+
advisor: {
|
|
751
|
+
summary: state.summary ?? null,
|
|
752
|
+
decay: state.lastDecay?.decay ?? null,
|
|
753
|
+
weightedChars: state.lastDecay?.weightedChars ?? null,
|
|
754
|
+
decayTurn: state.lastDecay?.turn ?? null,
|
|
755
|
+
scores: [...state.scores].map(([seq, entry]) => ({
|
|
756
|
+
seq,
|
|
757
|
+
score: entry.score,
|
|
758
|
+
turn: entry.turn
|
|
759
|
+
})),
|
|
760
|
+
lowRelevanceSeqs: [...state.recertified.keys()]
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
};
|
|
764
|
+
const register = (webServer) => {
|
|
765
|
+
const disposers = [...ADVISOR_REPORT_ROUTES].map((path) => ({
|
|
766
|
+
path,
|
|
767
|
+
handler: getHandler
|
|
768
|
+
})).map((entry) => webServer.register({
|
|
769
|
+
kind: "exact",
|
|
770
|
+
path: entry.path,
|
|
771
|
+
handler: entry.handler
|
|
772
|
+
})).filter((off) => typeof off === "function");
|
|
773
|
+
ctx.effect(() => () => {
|
|
774
|
+
for (const off of disposers) off();
|
|
775
|
+
}, "contextCompressionSelector.advisor report route");
|
|
776
|
+
log("info", "context-compression advisor report route registered: %s", ADVISOR_REPORT_ROUTES.join(", "));
|
|
777
|
+
};
|
|
778
|
+
const active = asWebServer(readService("webServer"));
|
|
779
|
+
if (active !== void 0) {
|
|
780
|
+
register(active);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
ctx.inject(["webServer"], (injected) => {
|
|
784
|
+
const webServer = asWebServer(injected.webServer);
|
|
785
|
+
if (webServer === void 0) {
|
|
786
|
+
log("warn", "context-compression webServer exposes no register() — advisor report route not registered");
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
register(webServer);
|
|
790
|
+
});
|
|
791
|
+
log("warn", "context-compression webServer not active yet — advisor report route pending: %s", ADVISOR_REPORT_ROUTES.join(", "));
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
687
794
|
* The one service the catalog route actually needs. `llm` and
|
|
688
795
|
* `agentDefaultModel` are payload enrichment the handler resolves per request,
|
|
689
796
|
* never reasons to withhold the route.
|
|
@@ -787,7 +894,8 @@ const SHARED_SETTINGS = Symbol.for("dsh-context-compression-improved/settings-re
|
|
|
787
894
|
const Config = z.object({
|
|
788
895
|
presetOverlay: z.boolean().default(false),
|
|
789
896
|
estimatorCatalogRoute: z.boolean().default(false),
|
|
790
|
-
reviewQueueRoute: z.boolean().default(false)
|
|
897
|
+
reviewQueueRoute: z.boolean().default(false),
|
|
898
|
+
advisorReportRoute: z.boolean().default(false)
|
|
791
899
|
});
|
|
792
900
|
/** Register the persisted default read by the currently mounted root pruner. */
|
|
793
901
|
function apply(ctx, config = {}) {
|
|
@@ -797,6 +905,7 @@ function apply(ctx, config = {}) {
|
|
|
797
905
|
});
|
|
798
906
|
if (config.estimatorCatalogRoute === true) registerEstimatorCatalogRoute(ctx);
|
|
799
907
|
if (config.reviewQueueRoute === true) registerReviewQueueRoutes(ctx);
|
|
908
|
+
if (config.advisorReportRoute === true) registerAdvisorReportRoute(ctx);
|
|
800
909
|
if (config.presetOverlay !== true) return;
|
|
801
910
|
ctx.inject(["agentPresets"], (presetsCtx) => {
|
|
802
911
|
const installation = decorateAgentPresets(presetsCtx.agentPresets, {
|