dsh-context-compression-improved 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.gitattributes +1 -0
  2. package/CHANGELOG.ja.md +144 -83
  3. package/CHANGELOG.ko.md +143 -82
  4. package/CHANGELOG.md +278 -212
  5. package/CHANGELOG.zh.md +131 -77
  6. package/docs/installation.md +103 -103
  7. package/docs/installation.zh.md +100 -100
  8. package/package.json +1 -1
  9. package/packages/selector/cordis.patch.yml +5 -6
  10. package/packages/selector/lib/advisor-state.js +4 -231
  11. package/packages/selector/lib/client.d.ts +0 -24
  12. package/packages/selector/lib/client.js +6 -501
  13. package/packages/selector/lib/index.d.ts +4 -10
  14. package/packages/selector/lib/index.js +16 -234
  15. package/packages/selector/lib/pruner.d.ts +13 -248
  16. package/packages/selector/lib/pruner.js +148 -552
  17. package/packages/selector/src/client/EstimatorControls.tsx +0 -101
  18. package/packages/selector/src/client/index.ts +0 -17
  19. package/packages/selector/src/client/locales.ts +0 -38
  20. package/packages/selector/src/client/preset-options.ts +3 -2
  21. package/packages/selector/src/client/settings-section.tsx +8 -17
  22. package/packages/selector/src/index.ts +24 -271
  23. package/packages/selector/src/profiles.ts +4 -27
  24. package/packages/selector/src/pruner/state.ts +2 -25
  25. package/packages/selector/src/pruner.ts +75 -403
  26. package/packages/selector/src/runtime/audit.ts +27 -21
  27. package/packages/selector/src/runtime/config.ts +6 -32
  28. package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -188
  29. package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +149 -133
  30. package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -419
  31. package/packages/selector/src/runtime/tokenpilot/benefit.ts +200 -0
  32. package/packages/selector/src/runtime/types.ts +0 -17
  33. package/packages/selector/tests/advisor-report.host.spec.ts +223 -223
  34. package/packages/selector/tests/preset-options-write.client.spec.ts +7 -23
  35. package/packages/selector/tests/public/package-contract.client.spec.ts +20 -0
  36. package/packages/selector/tests/runtime/advice-never-withholds.host.spec.ts +232 -0
  37. package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -272
  38. package/packages/selector/tests/runtime/advisor.spec.ts +226 -226
  39. package/packages/selector/tests/runtime/audit.spec.ts +35 -21
  40. package/packages/selector/tests/runtime/char-basis.spec.ts +30 -30
  41. package/packages/selector/tests/runtime/deprecated-preset-options.spec.ts +96 -0
  42. package/packages/selector/tests/runtime/tokenpilot/benefit.spec.ts +217 -0
  43. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +4 -5
  44. package/packages/selector/tests/settings-seat.client.spec.ts +45 -14
  45. package/scripts/toolclass-corpus-replay.mjs +281 -281
  46. package/packages/selector/src/client/ReviewOverlay.tsx +0 -320
  47. package/packages/selector/src/client/review-scope.ts +0 -16
  48. package/packages/selector/src/runtime/tokenpilot/proposal.ts +0 -267
  49. package/packages/selector/src/runtime/tokenpilot/review-queue.ts +0 -231
  50. package/packages/selector/src/runtime/tokenpilot/review-registry.ts +0 -117
  51. package/packages/selector/src/runtime/tokenpilot/review-storage.ts +0 -122
  52. package/packages/selector/tests/review-overlay.client.spec.tsx +0 -118
  53. package/packages/selector/tests/review-routes-registry.host.spec.ts +0 -142
  54. package/packages/selector/tests/review-routes.host.spec.ts +0 -290
  55. package/packages/selector/tests/runtime/tokenpilot/proposal.spec.ts +0 -393
  56. package/packages/selector/tests/runtime/tokenpilot/pruner-review.spec.ts +0 -382
  57. package/packages/selector/tests/runtime/tokenpilot/review-queue.spec.ts +0 -168
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-compression-improved",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
@@ -14,15 +14,14 @@
14
14
  # announcing a pending route on a webServer-less profile. The two-channel
15
15
  # registration inside the plugin covers both arrival orders on its own.
16
16
  #
17
- # `reviewQueueRoute` belongs here and not on the bundle row: it gates the
18
- # R4 human-review endpoints (`/review-queue`, `/review-decide`) and was
19
- # never turned on, so the review overlay polled two 404s and the panel
20
- # could not open. Both flags are set on exactly one row, so each route is
21
- # registered exactly once.
17
+ # `estimatorCatalogRoute` is the only flag this row sets. The retired
18
+ # review gate's `reviewQueueRoute` used to live here; that key no longer
19
+ # exists in the plugin's Config schema (a reduction must never block
20
+ # automatic processing), so setting it would advertise a route that can
21
+ # never register.
22
22
  - id: context-compression-improved-estimator-catalog
23
23
  name: 'dsh-context-compression-improved'
24
24
  inject:
25
25
  - webServer
26
26
  config:
27
27
  estimatorCatalogRoute: true
28
- reviewQueueRoute: true
@@ -408,8 +408,7 @@ function parsePresetOptionsSettings(value) {
408
408
  "dedupeToolResults",
409
409
  "summaryLocator",
410
410
  "prefixStabilizer",
411
- "readState",
412
- "reviewMode"
411
+ "readState"
413
412
  ]) {
414
413
  const entry = value[key];
415
414
  if (entry !== void 0 && typeof entry !== "boolean") throw new TypeError(`Context-compression presetOptions.${key} must be a boolean`);
@@ -430,12 +429,6 @@ function parsePresetOptionsSettings(value) {
430
429
  if (advisorMinTokens !== void 0 && (typeof advisorMinTokens !== "number" || !Number.isSafeInteger(advisorMinTokens) || advisorMinTokens < 1)) throw new TypeError("Context-compression presetOptions.advisorMinTokens must be a positive integer");
431
430
  const estimatorTimeoutMs = value.estimatorTimeoutMs;
432
431
  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");
433
- const reviewTimeoutTurns = value.reviewTimeoutTurns;
434
- if (reviewTimeoutTurns !== void 0 && (typeof reviewTimeoutTurns !== "number" || !Number.isSafeInteger(reviewTimeoutTurns) || reviewTimeoutTurns < 1)) throw new TypeError("Context-compression presetOptions.reviewTimeoutTurns must be an integer of at least 1");
435
- const cacheHitDiscountAlpha = value.cacheHitDiscountAlpha;
436
- if (cacheHitDiscountAlpha !== void 0 && (typeof cacheHitDiscountAlpha !== "number" || !Number.isFinite(cacheHitDiscountAlpha) || cacheHitDiscountAlpha <= 0 || cacheHitDiscountAlpha >= 1)) throw new TypeError("Context-compression presetOptions.cacheHitDiscountAlpha must be a number strictly between 0 and 1");
437
- const reviewHighImpactTokens = value.reviewHighImpactTokens;
438
- if (reviewHighImpactTokens !== void 0 && (typeof reviewHighImpactTokens !== "number" || !Number.isSafeInteger(reviewHighImpactTokens) || reviewHighImpactTokens < 0)) throw new TypeError("Context-compression presetOptions.reviewHighImpactTokens must be a non-negative integer");
439
432
  for (const key of [
440
433
  "estimatorProvider",
441
434
  "estimatorModel",
@@ -456,10 +449,6 @@ function parsePresetOptionsSettings(value) {
456
449
  if (value.estimatorBaseUrl !== void 0) result.estimatorBaseUrl = value.estimatorBaseUrl;
457
450
  if (value.estimatorApiKey !== void 0) result.estimatorApiKey = value.estimatorApiKey;
458
451
  if (estimatorTimeoutMs !== void 0) result.estimatorTimeoutMs = estimatorTimeoutMs;
459
- if (value.reviewMode !== void 0) result.reviewMode = value.reviewMode;
460
- if (reviewTimeoutTurns !== void 0) result.reviewTimeoutTurns = reviewTimeoutTurns;
461
- if (cacheHitDiscountAlpha !== void 0) result.cacheHitDiscountAlpha = cacheHitDiscountAlpha;
462
- if (reviewHighImpactTokens !== void 0) result.reviewHighImpactTokens = reviewHighImpactTokens;
463
452
  if (advisorMode !== void 0) result.advisorMode = advisorMode;
464
453
  if (advisorTimeoutMs !== void 0) result.advisorTimeoutMs = advisorTimeoutMs;
465
454
  if (advisorRefreshTurns !== void 0) result.advisorRefreshTurns = advisorRefreshTurns;
@@ -681,10 +670,6 @@ const PRESET_OPTION_DEFAULTS = deepFreeze({
681
670
  prefixStabilizer: true,
682
671
  readState: true,
683
672
  estimator: { mode: "" },
684
- reviewMode: false,
685
- reviewTimeoutTurns: 6,
686
- cacheHitDiscountAlpha: .1,
687
- reviewHighImpactTokens: 4e3,
688
673
  advisor: {
689
674
  mode: "",
690
675
  timeoutMs: 8e3,
@@ -709,10 +694,6 @@ function mergePresetOptions(overrides) {
709
694
  prefixStabilizer: overrides.prefixStabilizer ?? PRESET_OPTION_DEFAULTS.prefixStabilizer,
710
695
  readState: overrides.readState ?? PRESET_OPTION_DEFAULTS.readState,
711
696
  estimator: { mode: overrides.estimatorMode ?? PRESET_OPTION_DEFAULTS.estimator.mode },
712
- reviewMode: overrides.reviewMode ?? PRESET_OPTION_DEFAULTS.reviewMode,
713
- reviewTimeoutTurns: overrides.reviewTimeoutTurns ?? PRESET_OPTION_DEFAULTS.reviewTimeoutTurns,
714
- cacheHitDiscountAlpha: overrides.cacheHitDiscountAlpha ?? PRESET_OPTION_DEFAULTS.cacheHitDiscountAlpha,
715
- reviewHighImpactTokens: overrides.reviewHighImpactTokens ?? PRESET_OPTION_DEFAULTS.reviewHighImpactTokens,
716
697
  advisor: {
717
698
  mode: overrides.advisorMode ?? PRESET_OPTION_DEFAULTS.advisor.mode,
718
699
  timeoutMs: overrides.advisorTimeoutMs ?? PRESET_OPTION_DEFAULTS.advisor.timeoutMs,
@@ -915,215 +896,6 @@ function assertPositiveInteger(name, value) {
915
896
  function assertNonNegativeInteger(name, value) {
916
897
  if (!Number.isSafeInteger(value) || value < 0) throw new Error(`ToolResultPruneConfig: ${name} (${String(value)}) must be a non-negative safe integer`);
917
898
  }
918
- //#endregion
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
899
  const advisorStates = /* @__PURE__ */ new WeakMap();
1128
900
  /**
1129
901
  * The per-session advisor state, created on first touch.
@@ -1141,7 +913,8 @@ function getAdvisorState(session) {
1141
913
  recertified: /* @__PURE__ */ new Map(),
1142
914
  failures: void 0,
1143
915
  inFlight: false,
1144
- lastDecay: void 0
916
+ lastDecay: void 0,
917
+ lastAdvice: void 0
1145
918
  };
1146
919
  advisorStates.set(session, state);
1147
920
  }
@@ -1186,4 +959,4 @@ function invalidateOnTaskChange(state, todoVersion) {
1186
959
  return true;
1187
960
  }
1188
961
  //#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 };
962
+ export { COMPRESSION_PROFILES as C, deepFreeze as S, resolvePolicy as _, AUTO_COMPACT_THRESHOLD_LIMITS as a, resolveCustomPolicy as b, DEFAULTS as c, charsToTokens as d, codePointLength as f, resolveConfig as g, parseContextCompressionSettings as h, recordScore as i, PRUNE_MARKER as l, isValidAutoCompactThresholdPercent as m, invalidateOnTaskChange as n, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE as o, isCompressionProfile as p, recordRecertified as r, ContextCompressionSettingsSchema as s, getAdvisorState as t, charsForTokens as u, CustomCompressionPolicySchema as v, assertNever as x, DEFAULT_CUSTOM_COMPRESSION_POLICY as y };
@@ -84,11 +84,6 @@ interface PresetOptionsSettings {
84
84
  readonly estimatorBaseUrl?: string;
85
85
  readonly estimatorApiKey?: string;
86
86
  readonly estimatorTimeoutMs?: number;
87
- /** Review-mode overrides (beta); mirrors the runtime PresetOptions.reviewMode. */
88
- readonly reviewMode?: boolean;
89
- readonly reviewTimeoutTurns?: number;
90
- readonly cacheHitDiscountAlpha?: number;
91
- readonly reviewHighImpactTokens?: number;
92
87
  /** Advisory advisor channel; `''` (the default) keeps the advisor off. */
93
88
  readonly advisorMode?: '' | 'host' | 'direct';
94
89
  readonly advisorTimeoutMs?: number;
@@ -153,25 +148,6 @@ declare const zh: {
153
148
  'estimator.apiKey.set': string;
154
149
  'estimator.apiKey.clear': string;
155
150
  'estimator.apiKey.overwrite': string;
156
- 'review.title': string;
157
- 'review.description': string;
158
- 'review.enabled': string;
159
- 'review.enabled.on': string;
160
- 'review.enabled.off': string;
161
- 'review.timeoutTurns': string;
162
- 'review.alpha': string;
163
- 'review.highImpact': string;
164
- 'review.badge': string;
165
- 'review.summary.autoApplied': string;
166
- 'review.summary.reviewApplied': string;
167
- 'review.summary.expired': string;
168
- 'review.summary.voided': string;
169
- 'review.row.payback': string;
170
- 'review.row.expectedSaving': string;
171
- 'review.row.estimated': string;
172
- 'review.action.approve': string;
173
- 'review.action.reject': string;
174
- 'review.action.ignore': string;
175
151
  'detail.tokenpilot-inspired': string;
176
152
  'profile.custom': string;
177
153
  'profile.native': string;