dsh-context-compression-improved 0.4.0 → 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.
Files changed (31) hide show
  1. package/CHANGELOG.ja.md +19 -0
  2. package/CHANGELOG.ko.md +19 -0
  3. package/CHANGELOG.md +21 -0
  4. package/CHANGELOG.zh.md +17 -0
  5. package/docs/installation.md +25 -1
  6. package/docs/installation.zh.md +24 -1
  7. package/package.json +1 -1
  8. package/packages/selector/lib/{review-registry.js → advisor-state.js} +105 -4
  9. package/packages/selector/lib/client.d.ts +7 -0
  10. package/packages/selector/lib/client.js +32 -2
  11. package/packages/selector/lib/index.d.ts +7 -0
  12. package/packages/selector/lib/index.js +101 -2
  13. package/packages/selector/lib/pruner.d.ts +82 -0
  14. package/packages/selector/lib/pruner.js +545 -11
  15. package/packages/selector/src/client/preset-options.ts +2 -0
  16. package/packages/selector/src/index.ts +108 -0
  17. package/packages/selector/src/profiles.ts +48 -0
  18. package/packages/selector/src/pruner/state.ts +3 -0
  19. package/packages/selector/src/pruner.ts +113 -0
  20. package/packages/selector/src/runtime/audit.ts +22 -0
  21. package/packages/selector/src/runtime/config.ts +58 -0
  22. package/packages/selector/src/runtime/tokenpilot/advisor-prompt.ts +188 -0
  23. package/packages/selector/src/runtime/tokenpilot/advisor-state.ts +133 -0
  24. package/packages/selector/src/runtime/tokenpilot/advisor.ts +419 -0
  25. package/packages/selector/src/runtime/tokenpilot/sidechannel.ts +24 -9
  26. package/packages/selector/src/runtime/types.ts +21 -0
  27. package/packages/selector/tests/advisor-report.host.spec.ts +223 -0
  28. package/packages/selector/tests/runtime/advisor-invariant.spec.ts +272 -0
  29. package/packages/selector/tests/runtime/advisor.spec.ts +226 -0
  30. package/packages/selector/tests/runtime/audit.spec.ts +44 -0
  31. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +12 -0
package/CHANGELOG.ja.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  > 完全な履歴(アップストリーム 0.1.0 以前を含む)は [CHANGELOG.md](CHANGELOG.md) を参照。このファイルはフォークの追加エントリーのみを翻訳したものです。 · [English](CHANGELOG.md) · [中文](CHANGELOG.zh.md) · [한국어](CHANGELOG.ko.md)
4
4
 
5
+ ## 0.5.0 - 2026-09-20
6
+
7
+ ### Added(追加)
8
+
9
+ - アドバイザリー関連度アドバイザー(統計と提案のみ、デフォルト無効):各ターン境界で
10
+ fire-and-forget のパスを実行し、最新の `todo/write` イベントからセッションの末端タスク
11
+ 意味を要約し(todolist が無い場合は直近のユーザーテキストへフォールバック)、履歴
12
+ tool-result 候選の「内容+コメント意味 ↔ 現在タスク」の関連度を増分スコアリングし、
13
+ prefix-decay(characterPressure 重み付けの関連度平均の逆数)を算出します。関連度の低い
14
+ 古いセグメントは `recertified` として記録されますが、これは将来の history 積極度判断への
15
+ 提案入力に過ぎず——今回それを消費する経路は存在せず、アドバイザーの出力が着地すべき
16
+ reduction を抑制・遅延・書き換えすることは決してありません(専用の不変テストで固定)。
17
+ `presetOptions.advisor*` 設定キーで設定(`advisorMode` `''|'host'|'direct'`、既定 `''`;
18
+ direct チャネルは estimator のエンドポイントを再利用;`SideChannel` に任意の overrides
19
+ 引数を追加し、設定を共有せずトランスポートを共有)。可観測性:新しい `advisor-outcome`
20
+ 監査レコード(content-free、summary / scoring / decay の各フェーズごとに 1 件)と、読み
21
+ 取り専用 HTTP ルート `GET .../advisor-report?sessionId=`(デプロイ opt-in フラグ
22
+ `advisorReportRoute`、review ルートと同じ骨格)。クライアント UI は今回意図的に未提供。
23
+
5
24
  ## 0.4.0 - 2026-09-20
6
25
 
7
26
  ### Fixed(修正)
package/CHANGELOG.ko.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  > 전체 히스토리(업스트림 0.1.0 이전 포함)는 [CHANGELOG.md](CHANGELOG.md)를 참고하세요. 이 파일은 포크의 추가 항목만 번역한 것입니다. · [English](CHANGELOG.md) · [中文](CHANGELOG.zh.md) · [日本語](CHANGELOG.ja.md)
4
4
 
5
+ ## 0.5.0 - 2026-09-20
6
+
7
+ ### Added(추가)
8
+
9
+ - 어드바이저리 관련도 어드바이저(통계와 제안 전용, 기본 꺼짐): 모든 턴 경계에서
10
+ fire-and-forget 패스를 실행하여, 최신 `todo/write` 이벤트에서 세션의 후미 작업 의미를
11
+ 요약하고(todolist 가 없으면 최근 사용자 텍스트로 폴백), 역사적 tool-result 후보의
12
+ "내용+주석 의미 ↔ 현재 작업" 관련도를 증분 스코어링하며, prefix-decay 수치(characterPressure
13
+ 가중 관련도 평균의 역)를 계산합니다. 관련도가 낮은 오래된 세그먼트는 `recertified` 로
14
+ 표시되지만 이는 향후 history 공격성 판단을 위한 제안 입력일 뿐——이번 라운드에서 이를
15
+ 소비하는 경로는 없으며, 어드바이저 출력이 착지해야 할 reduction 을 억제·지연·재작성하는
16
+ 일은 결코 없습니다(전용 불변 테스트로 고정). `presetOptions.advisor*` 설정 키로 구성
17
+ (`advisorMode` `''|'host'|'direct'`, 기본 `''`; direct 채널은 estimator 엔드포인트 재사용;
18
+ `SideChannel` 에 선택적 overrides 매개변수 추가로 설정 공유 없이 전송 계층 공유).
19
+ 관측성: 새로운 `advisor-outcome` 감사 레코드(content-free, summary / scoring / decay 각
20
+ 페이즈별 1개)와 읽기 전용 HTTP 라우트 `GET .../advisor-report?sessionId=`(배포 opt-in
21
+ 플래그 `advisorReportRoute`, review 라우트와 동일한 뼈대). 클라이언트 UI 는 이번 라운드에서
22
+ 의도적으로 제공하지 않습니다.
23
+
5
24
  ## 0.4.0 - 2026-09-20
6
25
 
7
26
  ### Fixed(수정)
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes use this file. The project follows semantic versioning after `0.1.0`.
4
4
 
5
+ ## 0.5.0 - 2026-09-20
6
+
7
+ ### Added
8
+
9
+ - Advisory relevance advisor (statistics and suggestions only, default off): at every turn
10
+ boundary a fire-and-forget pass summarizes the session's tail-task semantics from the
11
+ most recent `todo/write` event (falling back to recent user text), incrementally scores
12
+ historical tool-result candidates for content-and-comment relevance against the current
13
+ task, and computes a prefix-decay figure (character-pressure-weighted mean relevance).
14
+ Low-relevance old segments are marked `recertified` as suggestions for later
15
+ history-aggressiveness decisions — nothing this round consumes them, and advisor output
16
+ can never suppress, delay, or rewrite any reduction that would land (pinned by a
17
+ dedicated invariant test). Configure through the `presetOptions.advisor*` settings keys
18
+ (`advisorMode` `''|'host'|'direct'`, default `''`; the direct channel reuses the
19
+ estimator endpoint; `SideChannel` gained an optional overrides parameter so the
20
+ estimator's transport is shared without sharing its configuration). Observability: new
21
+ `advisor-outcome` audit records (content-free, one per phase: summary / scoring / decay)
22
+ and a read-only `GET .../advisor-report?sessionId=` HTTP route (opt-in deployment flag
23
+ `advisorReportRoute`, mirroring the review routes). Client UI is intentionally absent
24
+ this round.
25
+
5
26
  ## 0.4.0 - 2026-09-20
6
27
 
7
28
  ### Fixed
package/CHANGELOG.zh.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  > 完整历史(含上游 0.1.0 及更早版本)见 [CHANGELOG.md](CHANGELOG.md)。本文件只翻译本 fork 的新增条目。 · [English](CHANGELOG.md) · [日本語](CHANGELOG.ja.md) · [한국어](CHANGELOG.ko.md)
4
4
 
5
+ ## 0.5.0 - 2026-09-20
6
+
7
+ ### Added
8
+
9
+ - 建议型相关度 advisor(仅统计与建议,默认关闭):每个 turn 边界以 fire-and-forget 方式
10
+ 运行一次 pass——从最近的 `todo/write` 事件总结尾部任务语义(无 todolist 时回退到最近
11
+ 用户文本)、对历史 tool-result 候选做"内容+注释语义 ↔ 当前任务"的增量相关度打分、并
12
+ 计算前缀腐化度(prefix-decay,按 characterPressure 加权的相关度均值取反)。低相关的
13
+ 旧段标记为 `recertified`,仅作为后续 history 激进化的建议输入——本轮没有任何决策路径
14
+ 消费它,且 advisor 输出绝不抑制、延迟或改写任何本应落地的 reduction(有专项不变量测试
15
+ 钉死)。经 `presetOptions.advisor*` settings 键配置(`advisorMode` `''|'host'|'direct'`,
16
+ 默认 `''`;direct 通道复用 estimator 端点;`SideChannel` 新增可选 overrides 参数,
17
+ 共享传输层而不共享配置)。可观测性:新增 `advisor-outcome` 审计记录(content-free,
18
+ 每阶段一条:summary / scoring / decay)与只读 HTTP 路由
19
+ `GET .../advisor-report?sessionId=`(部署级 opt-in 开关 `advisorReportRoute`,与 review
20
+ 路由同骨架)。本轮刻意不提供客户端 UI。
21
+
5
22
  ## 0.4.0 - 2026-09-20
6
23
 
7
24
  ### Fixed
@@ -59,7 +59,31 @@ Open DeepSeek Harness settings → **Context compression selector**:
59
59
 
60
60
  Like all selector settings, the value is frozen when a session first observes it — the gate affects newly observed sessions, never a task that is already running.
61
61
 
62
- ## 5. Update or remove
62
+ ## 5. Optional: the advisory relevance advisor
63
+
64
+ The plugin can keep statistics about how relevant the session's history still is — advisory only, it never decides or blocks anything. It is off by default; turn it on by editing the `presetOptions` section of the context-compression settings (settings JSON, no UI card this round):
65
+
66
+ ```json
67
+ "presetOptions": {
68
+ "advisorMode": "host",
69
+ "advisorRefreshTurns": 8,
70
+ "advisorScoreThreshold": 0.35,
71
+ "advisorSampleLimit": 16,
72
+ "advisorMinTokens": 250,
73
+ "advisorTimeoutMs": 8000
74
+ }
75
+ ```
76
+
77
+ `advisorMode: "host"` calls the harness `llm` service; `"direct"` reuses the estimator's
78
+ `estimatorBaseUrl` / `estimatorApiKey` / `estimatorModel` endpoint. At every turn boundary
79
+ the advisor (1) summarizes the current task from the most recent `todo/write` event,
80
+ (2) incrementally scores historical tool results for content-and-comment relevance, and
81
+ (3) records a prefix-decay figure. Results surface as `advisor-outcome` audit records and,
82
+ with the deployment flag `advisorReportRoute: true`, a read-only
83
+ `GET .../advisor-report?sessionId=` HTTP route. What the advisor reports can never
84
+ suppress, delay, or rewrite any reduction that would land.
85
+
86
+ ## 6. Update or remove
63
87
 
64
88
  ```sh
65
89
  # update: pull, rebuild, repack, and add the new tarball again
@@ -57,7 +57,30 @@ dsh --profile web --dump-config
57
57
 
58
58
  与所有选择器设置一致,取值在会话首次观察时冻结——这道门只影响新观察的会话,不会改变正在运行的任务。
59
59
 
60
- ## 5. 更新或卸载
60
+ ## 5. 可选:建议型相关度 advisor
61
+
62
+ 插件可以对"会话历史还有多相关"做统计——仅建议性质,不做任何决策、也不阻断任何流程。默认关闭;本轮没有设置卡片,请直接编辑上下文压缩设置中的 `presetOptions` 段(settings JSON):
63
+
64
+ ```json
65
+ "presetOptions": {
66
+ "advisorMode": "host",
67
+ "advisorRefreshTurns": 8,
68
+ "advisorScoreThreshold": 0.35,
69
+ "advisorSampleLimit": 16,
70
+ "advisorMinTokens": 250,
71
+ "advisorTimeoutMs": 8000
72
+ }
73
+ ```
74
+
75
+ `advisorMode: "host"` 走 harness `llm` 服务;`"direct"` 复用 estimator 的
76
+ `estimatorBaseUrl` / `estimatorApiKey` / `estimatorModel` 端点。每个 turn 边界,advisor
77
+ 会:(1) 从最近的 `todo/write` 事件总结当前任务语义;(2) 对历史 tool result 做"内容+注释
78
+ 语义 ↔ 当前任务"的增量相关度打分;(3) 记录前缀腐化度(prefix-decay)。结果以
79
+ `advisor-outcome` 审计记录呈现;部署配置打开 `advisorReportRoute: true` 后,还可经只读
80
+ HTTP 路由 `GET .../advisor-report?sessionId=` 读取。advisor 报告的任何内容都不会抑制、
81
+ 延迟或改写任何本应落地的 reduction。
82
+
83
+ ## 6. 更新或卸载
61
84
 
62
85
  ```sh
63
86
  # 更新:拉取、重建、重新打包、再次添加新 tarball
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context-compression-improved",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
@@ -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. */
@@ -660,7 +684,15 @@ const PRESET_OPTION_DEFAULTS = deepFreeze({
660
684
  reviewMode: false,
661
685
  reviewTimeoutTurns: 6,
662
686
  cacheHitDiscountAlpha: .1,
663
- 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
+ }
664
696
  });
665
697
  /**
666
698
  * Merge persisted presetOptions overrides over the tokenpilot-inspired
@@ -680,7 +712,15 @@ function mergePresetOptions(overrides) {
680
712
  reviewMode: overrides.reviewMode ?? PRESET_OPTION_DEFAULTS.reviewMode,
681
713
  reviewTimeoutTurns: overrides.reviewTimeoutTurns ?? PRESET_OPTION_DEFAULTS.reviewTimeoutTurns,
682
714
  cacheHitDiscountAlpha: overrides.cacheHitDiscountAlpha ?? PRESET_OPTION_DEFAULTS.cacheHitDiscountAlpha,
683
- 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
+ }
684
724
  });
685
725
  }
686
726
  /**
@@ -1084,5 +1124,66 @@ function registerReviewPruner(pruner) {
1084
1124
  function resolveReviewPruner() {
1085
1125
  return live.values().next().value;
1086
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
+ }
1087
1188
  //#endregion
1088
- 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, ReviewQueue as i, PRUNE_MARKER as l, isValidAutoCompactThresholdPercent as m, resolveReviewPruner as n, CONTEXT_COMPRESSION_SETTINGS_NAMESPACE as o, isCompressionProfile as p, sharedReviewStore as r, ContextCompressionSettingsSchema as s, registerReviewPruner as t, charsForTokens as u, CustomCompressionPolicySchema as v, assertNever as x, DEFAULT_CUSTOM_COMPRESSION_POLICY as y };
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.
@@ -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 { n as resolveReviewPruner, o as CONTEXT_COMPRESSION_SETTINGS_NAMESPACE, s as ContextCompressionSettingsSchema } from "./review-registry.js";
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,6 +459,7 @@ 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"];
462
463
  /**
463
464
  * Resolve the review pipeline for the top-level routes.
464
465
  *
@@ -694,6 +695,102 @@ function registerReviewQueueRoutes(ctx) {
694
695
  log("warn", "context-compression webServer not active yet — review routes pending: %s", REVIEW_QUEUE_ROUTES.join(", "));
695
696
  }
696
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
+ /**
697
794
  * The one service the catalog route actually needs. `llm` and
698
795
  * `agentDefaultModel` are payload enrichment the handler resolves per request,
699
796
  * never reasons to withhold the route.
@@ -797,7 +894,8 @@ const SHARED_SETTINGS = Symbol.for("dsh-context-compression-improved/settings-re
797
894
  const Config = z.object({
798
895
  presetOverlay: z.boolean().default(false),
799
896
  estimatorCatalogRoute: z.boolean().default(false),
800
- reviewQueueRoute: z.boolean().default(false)
897
+ reviewQueueRoute: z.boolean().default(false),
898
+ advisorReportRoute: z.boolean().default(false)
801
899
  });
802
900
  /** Register the persisted default read by the currently mounted root pruner. */
803
901
  function apply(ctx, config = {}) {
@@ -807,6 +905,7 @@ function apply(ctx, config = {}) {
807
905
  });
808
906
  if (config.estimatorCatalogRoute === true) registerEstimatorCatalogRoute(ctx);
809
907
  if (config.reviewQueueRoute === true) registerReviewQueueRoutes(ctx);
908
+ if (config.advisorReportRoute === true) registerAdvisorReportRoute(ctx);
810
909
  if (config.presetOverlay !== true) return;
811
910
  ctx.inject(["agentPresets"], (presetsCtx) => {
812
911
  const installation = decorateAgentPresets(presetsCtx.agentPresets, {