dsh-compact-relay 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1(2026-10-25)
4
+
5
+ ### 修复:原生压缩线显示口径不一致(设置页仍写 80%,指示灯写实际触发 67.8%)
6
+
7
+ - **现象**:0.4.0 把指示灯/悬停卡改为「实际触发线」(DSH 按 `min(窗口×thresholdRatio, 窗口−输出预留−headroomTokens)` 触发),但设置页的「作用 / 调整后 / 原生阈值」三处和「指示灯怎么看」教学卡仍按 `thresholdRatio` 显示 80% —— 同一插件两处数字互相矛盾,而且设置页那句「80%:DSH 会自己压历史」本身是错的(实际 67.8% 就压)。
8
+ - **修复**:客户端抽出纯函数 `nativeLineInfo()`(实际线优先;给出配置阈值、实际触发点与差异原因);设置页三处 + 教学卡刻度/文案 + 指示灯悬停卡统一为「实际触发 X%(配置阈值 Y%)」,并说明「模型输出预留 + DSH 自身余量」使实际触发更早;交接提醒线上限也改用实际触发线(原按配置 80% 算,允许把交接线设在实际触发点之后、永远走不到)。
9
+ - **不改**:宿主计算、触发条件、压缩/交接行为;纯显示文案与校验上限。
10
+ - **验证**:Mac 真机(0.4.1 客户端):设置页与悬停卡同显 **67.8%**(678.5K / 1.00M,deepseek-flash),配置阈值 80% 作为注释保留;交接线上限随实际线收紧。
11
+
12
+ ### 显示层补强(dev-god 独立审查后,同属 0.4.1)
13
+
14
+ - **关闭态空数字**:`autoEnabled=false` 时原生线信息原本连「实际会触发多少」一起丢,设置页会出现「原生阈值 %」、确认弹层出现「容量到 %」。现在关闭态显示「配置 80%(已关闭;开启后实际触发 67.8%)」,恢复确认也用实际值。
15
+ - **差异归因**:`why` 只在「预算分支确实更小」时才归因为输出预留/余量;`modelPolicies` 路由覆盖改说「该模型单独配了阈值」;实际触发晚于配置阈值时不再套用「理论上限」说法(原实现会自相矛盾)。
16
+ - **交接线遗留值**:设置页对「已保存交接线 > 实际触发线」给出显式提示(不静默改写);交接线输入上限与保存口径统一(按实际触发线向下取整)。服务端保存校验仍以配置阈值做上限(防呆不阻断),本版仅在 UI 收紧并提示如何调整。
17
+ - **文档口径**:README badge / README.en 同步到 0.4.1 与实际触发线说明;ROADMAP 标注已失效参考仓库。
18
+ - 测试:`test/native-line.mjs` 新增 `nativeLineInfo` 行为回归(正常/关闭/算不出/路由覆盖/实际晚于配置五组输入,从客户端源码切出函数真跑)。
19
+
3
20
  ## 0.4.0(2026-10-24)
4
21
 
5
22
  ### 修复:压缩/交接后「压缩判断记录」从未失效(BUG-013,0.3.1 起存在)
package/README.en.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![npm](https://img.shields.io/npm/v/dsh-compact-relay?label=npm&color=cb3837)](https://www.npmjs.com/package/dsh-compact-relay)
10
10
  [![DSH](https://img.shields.io/badge/DSH-0.2.x-4d6bfe)](https://www.deepseek.com/harness/)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
12
- [![version](https://img.shields.io/badge/version-0.3.4-blue)](CHANGELOG.md)
12
+ [![version](https://img.shields.io/badge/version-0.4.1-blue)](CHANGELOG.md)
13
13
 
14
14
  </div>
15
15
 
@@ -30,7 +30,7 @@ The plugin mounts beside the existing composer. Default parameters are tuned for
30
30
 
31
31
  ### 1. Occupancy visualization
32
32
 
33
- The ring is colored in segments according to the compression-assessment logic: the suggested compression point determined by the model or configured threshold, the positions of pinned messages, and the DSH native auto-compaction line (80% by default). When occupancy crosses the native auto-compaction line, the ring flashes red.
33
+ The ring is colored in segments according to the compression-assessment logic: the suggested compression point determined by the model or configured threshold, the positions of pinned messages, and the DSH native auto-compaction line (the actual trigger point computed from the host token budget: ~67.8% for deepseek-flash, while the configured threshold defaults to 80%). When occupancy crosses the actual trigger point, the ring flashes red.
34
34
 
35
35
  When occupancy enters the compressible band (between the minimum compression line and the compression line), the plugin proactively tells the model the current occupancy and band through a dynamic system-prompt context, so it can decide whether to record a compression node after finishing a natural milestone (since 0.3.4; before that the model could not see occupancy, so the condition was not actionable).
36
36
 
@@ -61,7 +61,7 @@ Running `/pin` marks messages that must be preserved during compaction. Pinned m
61
61
  | Amber arc | The compression point has been passed; compaction is suitable |
62
62
  | Red arc | The handoff threshold has been passed; switching to a new session is recommended |
63
63
  | Ring notch | Suggested compression point (the system proactively prompts the model to assess inside the compressible band; shown after the model records one or the compression line is reached) |
64
- | Violet tick | DSH native auto-compaction line (reads `compaction-basic.thresholdRatio`, 80% by default) |
64
+ | Violet tick | DSH native auto-compaction line: the actual trigger point computed as `min(window × thresholdRatio, window − reserved output − headroomTokens)` (~67.8% for deepseek-flash; the configured `thresholdRatio` defaults to 80% and is only a theoretical ceiling) |
65
65
  | Magenta blocks | Distribution of pinned messages in the history |
66
66
  | Full-ring red flash | The native auto-compaction line has been crossed; DSH native auto-compaction is imminent |
67
67
 
@@ -143,7 +143,7 @@ The first time a session is opened, the ring plays a brief highlight animation w
143
143
 
144
144
  ## Version and license
145
145
 
146
- - **Current version**: 0.3.4 (0.3.1 was the first public release; 0.3.2 fixed settings links and contact details; 0.3.3 added Windows support; 0.3.4 fixes the compression-point judgment mechanism and adds handoff Markdown rendering plus list load-more). Tested on macOS (DSH 0.2.1-rc.2) and Windows 11 (Node 24.22), using only public DSH exports. See [CHANGELOG.md](CHANGELOG.md) for changes, [ROADMAP.md](ROADMAP.md) for plans and known limitations, and [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md) for build and release workflows.
146
+ - **Current version**: 0.4.1 (0.3.1 was the first public release; 0.3.2 fixed settings links and contact details; 0.3.3 added Windows support; 0.3.4 fixed the compression-point judgment mechanism; 0.3.5 fixed the disabled compress button; 0.3.6 fixed the native-line parsing scope; 0.4.0 hardened the judgment mechanism, added diagnostics, pin retention, ledger, handoff metadata and the real native trigger line; 0.4.1 unified the native-line display: the ring, the settings page and the teaching card all show the actual trigger point with the configured threshold annotated). Tested on macOS (DSH 0.2.1-rc.2) and Windows 11 (Node 24.22), using only public DSH exports. See [CHANGELOG.md](CHANGELOG.md) for changes, [ROADMAP.md](ROADMAP.md) for plans and known limitations, and [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md) for build and release workflows.
147
147
  - **License**: [MIT](LICENSE). Feedback and Pull Requests are welcome via [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues).
148
148
 
149
149
  ## Credits
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![npm](https://img.shields.io/npm/v/dsh-compact-relay?label=npm&color=cb3837)](https://www.npmjs.com/package/dsh-compact-relay)
10
10
  [![DSH](https://img.shields.io/badge/DSH-0.2.x-4d6bfe)](https://www.deepseek.com/harness/)
11
11
  [![license](https://img.shields.io/badge/license-MIT-yellow)](LICENSE)
12
- [![version](https://img.shields.io/badge/version-0.4.0-blue)](CHANGELOG.md)
12
+ [![version](https://img.shields.io/badge/version-0.4.1-blue)](CHANGELOG.md)
13
13
 
14
14
  </div>
15
15
 
@@ -30,7 +30,7 @@
30
30
 
31
31
  ### 1. 状态可视化监控
32
32
 
33
- 指示环按压缩评估逻辑分段显色,分别标识模型或阈值判定的建议压缩点、已锁定消息位置,以及 DSH 原生自动压缩线(默认 80%)。当上下文占用越过原生自动压缩线时,整环呈红色闪烁告警。
33
+ 指示环按压缩评估逻辑分段显色,分别标识模型或阈值判定的建议压缩点、已锁定消息位置,以及 DSH 原生自动压缩线(按宿主实际 token 预算算出的触发点:deepseek-flash 约 67.8%,配置阈值默认 80%)。当上下文占用越过原生自动压缩线时,整环呈红色闪烁告警。
34
34
 
35
35
  上下文进入「可压缩区间」(最小压缩线到压缩线之间)时,插件会在系统提示里主动告诉模型当前容量与区间,模型据此判断「刚完成一个自然节点」时是否记录压缩节点(0.3.4 起;此前模型看不到容量,判断条件不可执行)。
36
36
 
@@ -61,7 +61,7 @@
61
61
  | 琥珀色弧 | 已越过压缩判断点,适合执行压缩 |
62
62
  | 红色弧 | 已越过交接阈值,建议切换新会话 |
63
63
  | 环形缺口 | 建议压缩点(系统在可压缩区间主动提示模型判断;模型记录或到达压缩线时自动出现) |
64
- | 紫色刻度 | DSH 原生自动压缩线(读取 `compaction-basic.thresholdRatio`,默认 80%) |
64
+ | 紫色刻度 | DSH 原生自动压缩线:按 `min(窗口 × thresholdRatio, 窗口 − 输出预留 − headroomTokens)` 算出的**实际触发点**(deepseek-flash 约 67.8%;`thresholdRatio` 配置默认 80%,只是理论上限) |
65
65
  | 品红色块 | 已锁定消息在历史记录中的分布位置 |
66
66
  | 整环红色闪烁 | 已越过原生自动压缩线,即将触发 DSH 原生自动压缩 |
67
67
 
@@ -89,7 +89,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
89
89
 
90
90
  - **压缩提示**:配置最小压缩线、压缩提醒线与交接提醒线,控制告警触发时机。
91
91
  - **压缩行为**:配置摘要保留份数上限、近期原文保留 Token 数及交接文档存储目录。
92
- - **DSH 原生自动压缩**:读取宿主当前阈值,支持开启或关闭 DSH 原生自动压缩(修改 profile 配置前自动生成备份,重启后生效)。
92
+ - **DSH 原生自动压缩**:读取宿主阈值并换算实际触发线,支持开启或关闭 DSH 原生自动压缩(修改 profile 配置前自动生成备份,重启后生效)。
93
93
  - **颜色与外观**:配置五项基准色,系统将根据浅色/深色主题自动派生适配色阶。
94
94
  - **帮助**:提供指示环图例、使用说明、问题反馈入口、「再看一次初次引导」按钮,以及「诊断信息」(只在本机生成、可预览/复制,见下节)。
95
95
  - **关于与危险操作**:查看版本号、配置文件路径,支持恢复出厂默认设置。
@@ -143,7 +143,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
143
143
 
144
144
  ## 版本状态与开源许可
145
145
 
146
- - **当前版本**:0.4.0(0.3.1 为首个公开版本;0.3.2 修正设置页链接与联系方式;0.3.3 完成 Windows 适配;0.3.4 修复判断机制并补交接单渲染;0.3.5 修无活 agent 时压缩按钮误禁;0.3.6 修原生压缩线解析作用域(BUG-012);**0.4.0** 判断机制加强(cue 文案 v2 + 注入门控 + 计量)、诊断包(脱敏诊断信息,一键复制/预览)、压缩预览建议锁定、pin 保留契约、轻账本累计、交接预览两段可编辑、接力链元数据、原生自动压缩真实触发线)。已在 macOS(DSH 0.2.1-rc.2)与 Windows 11(Node 24.22)完成测试,仅调用 DSH 公开导出接口。变更记录详见 [CHANGELOG.md](CHANGELOG.md),后续规划与已知限制详见 [ROADMAP.md](ROADMAP.md),构建与发布流程详见 [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md)。
146
+ - **当前版本**:0.4.0(0.3.1 为首个公开版本;0.3.2 修正设置页链接与联系方式;0.3.3 完成 Windows 适配;0.3.4 修复判断机制并补交接单渲染;0.3.5 修无活 agent 时压缩按钮误禁;0.3.6 修原生压缩线解析作用域(BUG-012);**0.4.0** 判断机制加强(cue 文案 v2 + 注入门控 + 计量)、诊断包(脱敏诊断信息,一键复制/预览)、压缩预览建议锁定、pin 保留契约、轻账本累计、交接预览两段可编辑、接力链元数据、原生自动压缩真实触发线;**0.4.1** 统一原生压缩线显示口径:指示灯 / 设置页 / 教学卡都显示实际触发点,并注明配置阈值与输出预留)。已在 macOS(DSH 0.2.1-rc.2)与 Windows 11(Node 24.22)完成测试,仅调用 DSH 公开导出接口。变更记录详见 [CHANGELOG.md](CHANGELOG.md),后续规划与已知限制详见 [ROADMAP.md](ROADMAP.md),构建与发布流程详见 [DEVELOPMENT.md](https://github.com/cfanzx/dsh-compact-relay/blob/main/DEVELOPMENT.md)。
147
147
  - **开源许可**:采用 [MIT](LICENSE) 许可证,欢迎通过 [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues) 与 Pull Requests 提交反馈。
148
148
 
149
149
  ## 参考与致谢
package/ROADMAP.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 压缩接力 · 目标与路线图
2
2
 
3
- > 版本基准:0.4.0(2026-10-24)|状态:0.3 已发布(npm + GitHub),0.3.6 已发布,0.4.0 已准备(只差上传)
3
+ > 版本基准:0.4.0(2026-10-25)|状态:0.3 已发布(npm + GitHub),0.3.6 已发布,0.4.0 已发布(npm latest + GitHub tag/Release)
4
4
  > 维护方式:每次发布前更新"当前状态"与勾选进度;方向变化必须同时改本文件和 memory 项目页。
5
5
 
6
6
  ## 一、最终目标(North Star)
@@ -54,7 +54,7 @@
54
54
 
55
55
  **这是我们的差异化,不碰压缩算法。**
56
56
 
57
- **0.4.0 未发布在途(2026-10-24)**
57
+ **0.4.0 已发布(2026-10-25)**
58
58
 
59
59
  - ✅ **P0-2 诊断包最小版**:本机生成、白名单 + 脱敏的诊断信息(设置页「生成并预览 / 复制 / 打开问题报告页」+ 只读 `GET /api/compact-relay-diagnostics`),并接入压缩/交接/锁定计数与耗时(commit `41699c8`;`npm test` exit 0,含 T1–T9 与真机 e2e 断言)。
60
60
  - 🚧 **P0-1 判断机制加强**(代码已落地,待真实任务验收):cue 文案 v2(4 类完成信号 + 被拒无副作用)、注入门控(10%–40%、距上次注入 ≥3 轮、按会话不落盘)、工具描述 v2(用户/模型都可调用、被拒不需要致歉、典型调用时机)、cue/mark 计量(`cueInjected` / `markCalls` / `markAccepted` / `markRejected` 直接进诊断包 counters)。验收口径不变(标准 B):3 次真实长任务里 ≥2 次模型自发调用 `mark_compress_point`。
@@ -134,7 +134,7 @@
134
134
  | [dsh-command-context-trim](https://www.npmjs.com/package/dsh-command-context-trim) | 0.6.6 / 10-03 | 无模型 `/trim`、小窗口切换 | 切掉 |
135
135
  | [context-vista](https://www.npmjs.com/package/context-vista) | 0.1.0 / 08-14 | 环图、成本估算 | 仪表盘不做 |
136
136
  | [dsh-compressor](https://www.npmjs.com/package/dsh-compressor) | 0.1.0 / 08-15 | 工具结果压缩 + retrieve | 压缩不做 |
137
- | [dsh-context-guard](https://github.com/wood20100715-collab/dsh-context-guard) | 仓库 | 内容类型压缩 + CCR + 高保真摘要 | 智能保留不自研 |
137
+ | dsh-context-guard(原 https://github.com/wood20100715-collab/dsh-context-guard 已失效,2026-10-25 核查 404) | 仓库 | 内容类型压缩 + CCR + 高保真摘要 | 智能保留不自研 |
138
138
  | [dsh-session-handoff](https://github.com/snow-The/dsh-session-handoff) | GitHub | 结构化交接文档 + 官方 compaction API | 交接同赛道;我们做"检查 + 链 + 记忆" |
139
139
 
140
140
  ## 六、已定决策
@@ -149,9 +149,9 @@
149
149
  - ✅ 0.3.1 已发布:npm `dsh-compact-relay@0.3.1` + GitHub tag/Release;0.3.2 补设置页链接与联系方式;0.3.3 完成 Windows 适配(Win11 + Node 24.22 全测试通过)。
150
150
  - ✅ 0.3.4 判断机制修复(BUG-010:`mark_compress_point` + systemPrompt 可压缩区间提示 + 状态缓存;427 会话数据佐证旧工具 0 调用)+ BUG-005/BUG-006 小 bug;npm latest 待本次发布。
151
151
  - ✅ 0.3.5 补丁:修复「无活 agent 时压缩按钮误禁 + 文案误导」(state 增加 `compactRangeReason='no_agent'`;客户端提示「会话未激活:发一条消息唤醒 agent 后可压 N 条」)。
152
- - 🟡 0.4.0 已准备、只差上传(2026-10-24):P0-1 判断机制加强(cue 文案 v2 + 10%–40% 注入门控 + cue/mark 计量)、P0-2 诊断包(脱敏诊断信息预览/复制/问题报告页,零网络)、P1-1 压缩预览建议锁定、P1-2 pin 保留契约、P1-3 轻账本累计、P2-1 交接预览两段可编辑、P2-2 接力链元数据、P2-3 原生自动压缩真实触发线(按 route 算)。真机验证已全绿(Mac 0.3.6→0.4.0 开发版);发布动作待办。
152
+ - ✅ 0.4.0 已发布(2026-10-25,npm latest + GitHub tag/Release):P0-1 判断机制加强(cue 文案 v2 + 10%–40% 注入门控 + cue/mark 计量)、P0-2 诊断包(脱敏诊断信息预览/复制/问题报告页,零网络)、P1-1 压缩预览建议锁定、P1-2 pin 保留契约、P1-3 轻账本累计、P2-1 交接预览两段可编辑、P2-2 接力链元数据、P2-3 原生自动压缩真实触发线(按 route 算)。真机验证全绿(Mac 0.3.6→0.4.0);Mac/Win 均已装 0.4.0 并核对版本。
153
153
  - ✅ 0.3.6 已发布(BUG-012):原生压缩线解析只认 `config:` 直接子键(按缩进判断),修复 `modelPolicies` 嵌套 `thresholdRatio` 被误报为全局线(Windows 真实会话误显示 50%);真实触发线按模型 route 计算留 0.4。
154
- - 🚧 0.4.0 未发布(进行中):P0-2 诊断包最小版已交付(commit `41699c8`,`npm test` 全绿 + 隔离 e2e 真机验证);P0-1 判断机制加强(cue 文案 v2 + 注入门控 10%–40%/≥3 轮 + 工具描述 v2 + cue/mark 计量)代码已落地、测试全绿,**等 3 次真实长任务数据判定标准 B 后再决定是否发布 0.4.0**。
154
+ - ✅ 0.4.0 判断机制与诊断包(2026-10-24 完成):P0-2 诊断包最小版已交付(commit `41699c8`,`npm test` 全绿 + 隔离 e2e 真机验证);P0-1 判断机制加强(cue 文案 v2 + 注入门控 10%–40%/≥3 轮 + 工具描述 v2 + cue/mark 计量)代码已落地、测试全绿,标准 B 已达标(3/3)并随 0.4.0 发布。
155
155
  - ✅ 真实压缩 e2e:`test/e2e/real-compact.mjs`(真实模型 + 真实压缩事务 + `maxSummaries=1` 合并旧摘要分支)6 项断言通过。
156
156
  - ✅ P3-01~14(13 修/1 确认无需修)+ BUG-004 + P2-T1 已收口。
157
157
  - ⏳ 市场收录 PR:仓库满 1 天(2026-10-21 19:09 后)提,yml 与说明已备。
package/TUTORIAL.md CHANGED
@@ -14,12 +14,12 @@
14
14
  | 蓝(填满色) | 正常区走满了(到了断点1) | 这里是「该压缩」的位置 |
15
15
  | 琥珀(调整色) | 压缩区(断点1 → 交接线) | 点指示灯 →「压缩旧会话」,或输入 `/bc` |
16
16
  | 红(上限色 / 内圆红点) | 交接区(交接线之后),或摘要太多 | 点指示灯 →「交接新会话」,或输入 `/handoff` |
17
- | 警示红(原生线) | 80% 处的 DSH 原生自动压缩线(紫色径向刻度);越过 80% 后整环红色闪烁 | 再往前 DSH 会自己压历史,先压缩或交接 |
17
+ | 警示红(原生线) | DSH 原生自动压缩线的**实际触发点**(紫色径向刻度;deepseek-flash 约 67.8%,配置阈值 80%);越过实际触发点后整环红色闪烁 | 再往前 DSH 会自己压历史,先压缩或交接 |
18
18
  | 品红(锁定色) | 这里锁了一条重要消息(按它在对话里的位置显示,可多个) | 知道「我锁的东西在哪」 |
19
19
 
20
20
  **断点**(环上的小缺口)=「我判断这里适合压缩」的位置:上下文进入可压缩区间(最小压缩线到压缩线之间)时,系统会主动在提示里告诉模型当前容量,模型判断「刚完成一个自然节点」时**可能**会记录一个压缩节点;到了压缩线也会自动出现。压缩后环变短,断点自然消失。
21
21
 
22
- 把鼠标放到指示灯上,会显示具体数字:`会话容量已用 xx.x%(xxxK / 1.00M),已压缩 n 次|建议压缩 / 建议交接 / 正常|压缩判断点 x%|原生自动压缩线 80%|已锁定 n 条`。
22
+ 把鼠标放到指示灯上,会显示具体数字:`会话容量已用 xx.x%(xxxK / 1.00M),已压缩 n 次|建议压缩 / 建议交接 / 正常|压缩判断点 x%|原生自动压缩线 67.8%(配置阈值 80%)|已锁定 n 条`。
23
23
 
24
24
  **第一次用**会看到一条提示:点指示灯能压缩旧会话、**锁定重要消息**、交接新会话;会顺带告诉你 DSH 原生自动压缩线是多少,以及环上的小缺口什么意思。重要的结论/路径先锁定,压缩时才不会被压掉。
25
25
 
package/lib/client.js CHANGED
@@ -742,6 +742,59 @@ window.__ModuleLoader__.load({
742
742
  );
743
743
  };
744
744
 
745
+ /**
746
+ * 0.4.1:原生自动压缩线「配置阈值 vs 实际触发线」。
747
+ * DSH 按 token 预算触发:min(窗口×配置阈值, 窗口−输出预留−余量),
748
+ * 所以实际触发点常比配置比例更早(deepseek-flash 配置 80%、实际 67.85%)。
749
+ * autoEnabled=false 时 effective/percentText 为空(未开启),但 actualText 仍给出
750
+ * 「开启后实际会触发多少」,供关闭态文案使用。
751
+ * @param {object|null} native readState 下发的 native 字段
752
+ * @returns {{configured:number|null,configuredText:string|null,actual:number|null,actualText:string,effective:number|null,percentText:string,differs:boolean,autoOff:boolean,why:string}}
753
+ */
754
+ const nativeLineInfo = (native) => {
755
+ const num = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : null);
756
+ const ratioOf = (value) => {
757
+ const n = num(value);
758
+ return n !== null && n > 0 && n < 1 ? n : null;
759
+ };
760
+ const src = native === null || native === undefined ? null : native;
761
+ const configured = ratioOf(src === null ? null : src.thresholdRatio);
762
+ const actual = ratioOf(src === null ? null : src.ratio);
763
+ const autoOff = src !== null && src.autoEnabled === false;
764
+ const effective = autoOff ? null : actual !== null ? actual : configured;
765
+ const pctText = (value) => {
766
+ const p = value * 100;
767
+ return Math.abs(p - Math.round(p)) < 0.05 ? String(Math.round(p)) : p.toFixed(1);
768
+ };
769
+ const configuredText = configured === null ? null : pctText(configured);
770
+ const actualText = actual === null ? '' : pctText(actual);
771
+ const percentText = effective === null ? '' : pctText(effective);
772
+ const differs = configured !== null && effective !== null && Math.abs(effective - configured) >= 0.005;
773
+ let why = '';
774
+ if (differs) {
775
+ const overridden = src.overridden === true;
776
+ const windowTokens = num(src.contextWindow);
777
+ const reserved = num(src.reservedCompletionTokens);
778
+ const headroom = num(src.headroomTokens);
779
+ // 只有「预算分支确实更小」时,才能把差异归因为输出预留/余量。
780
+ const budgetTokens = windowTokens !== null ? windowTokens - (reserved ?? 0) - (headroom ?? 0) : null;
781
+ const budgetBinding = budgetTokens !== null && configured !== null && budgetTokens < windowTokens * configured;
782
+ const parts = [];
783
+ if (reserved !== null && reserved > 0) parts.push(`模型输出要预留 ${fmt(reserved)}`);
784
+ if (headroom !== null && headroom > 0) parts.push(`DSH 自己还要留 ${fmt(headroom)} 余量`);
785
+ if (overridden) {
786
+ why = `这个模型单独配了阈值(按路由覆盖),实际触发是 ${actualText}%(全局配置 ${configuredText}%)。`;
787
+ } else if (budgetBinding && parts.length > 0) {
788
+ why = `配置阈值 ${configuredText}% 只是理论上限;${parts.join('、')},所以实际打到 ${percentText}% 就会自动压。`;
789
+ } else if (actual !== null && configured !== null && actual > configured) {
790
+ why = `这个路由的输出预留更小,实际触发晚于配置阈值 ${configuredText}%(实际 ${actualText}%)。`;
791
+ } else {
792
+ why = `实际触发 ${actualText}% 与配置阈值 ${configuredText}% 不同,按宿主 token 预算算的。`;
793
+ }
794
+ }
795
+ return { configured, configuredText, actual, actualText, effective, percentText, differs, autoOff, why };
796
+ };
797
+
745
798
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
746
799
 
747
800
  /**
@@ -1033,6 +1086,11 @@ window.__ModuleLoader__.load({
1033
1086
  typeof data.native.headroomTokens === 'number' && Number.isFinite(data.native.headroomTokens)
1034
1087
  ? data.native.headroomTokens
1035
1088
  : null,
1089
+ // 0.4.1:悬停卡解释「为什么实际触发比配置阈值早」要用到输出预留。
1090
+ reservedCompletionTokens:
1091
+ typeof data.native.reservedCompletionTokens === 'number' && Number.isFinite(data.native.reservedCompletionTokens)
1092
+ ? data.native.reservedCompletionTokens
1093
+ : null,
1036
1094
  overridden: data.native.overridden === true,
1037
1095
  route:
1038
1096
  data.native.route && typeof data.native.route === 'object' && typeof data.native.route.model === 'string'
@@ -1764,11 +1822,12 @@ window.__ModuleLoader__.load({
1764
1822
  state.native.route !== null ? `,${state.native.route.provider}/${state.native.route.model}` : ''
1765
1823
  }${state.native.overridden ? ',按路由覆盖' : ''})`
1766
1824
  : '';
1825
+ const nativeInfo = nativeLineInfo(state.native);
1767
1826
  const nativeLineText =
1768
1827
  nativeEffective !== null
1769
1828
  ? `原生自动压缩线 ${(nativeEffective * 100).toFixed(1)}%${nativeDetail}${
1770
- ratio > nativeEffective ? '(已越过:整环红色闪烁)' : ''
1771
- }`
1829
+ nativeInfo.differs ? `;${nativeInfo.why}` : ''
1830
+ }${ratio > nativeEffective ? '(已越过:整环红色闪烁)' : ''}`
1772
1831
  : 'DSH 原生自动压缩已关闭';
1773
1832
  const pressureKnown = pressureValue !== null;
1774
1833
  const title =
@@ -3044,22 +3103,51 @@ window.__ModuleLoader__.load({
3044
3103
  [],
3045
3104
  );
3046
3105
 
3106
+ /** 0.4.1:设置接口只给「配置阈值」,实际触发线要从 state 接口补(同一套 readState 数据)。 */
3107
+ const nativeLineExtra = useRef(null);
3108
+ const mergeNative = useCallback((view) => {
3109
+ if (view === null || view === undefined) return null;
3110
+ const extra = nativeLineExtra.current;
3111
+ return extra === null ? view : { ...view, ...extra };
3112
+ }, []);
3113
+
3047
3114
  /** 把服务端返回的 view 落到表单:同时更新「已保存」快照和设置文件错误。 */
3048
3115
  const applyLoaded = useCallback((data) => {
3049
3116
  setForm(data.config);
3050
3117
  setSavedSnapshot(JSON.stringify(data.config));
3051
3118
  setSettingsError(data.settingsError ?? null);
3052
- setNative(data.native && typeof data.native === 'object' ? data.native : null);
3119
+ setNative(mergeNative(data.native && typeof data.native === 'object' ? data.native : null));
3053
3120
  if (typeof data.version === 'string') setVersion(data.version);
3054
3121
  if (typeof data.path === 'string') setSettingsPath(data.path);
3055
3122
  setLedger(data.ledger !== null && typeof data.ledger === 'object' ? data.ledger : null);
3056
- }, []);
3123
+ }, [mergeNative]);
3057
3124
 
3058
3125
  const load = useCallback(async () => {
3059
3126
  try {
3060
- const res = await fetch('/api/compact-relay-settings', { cache: 'no-store' });
3061
- const data = await res.json().catch(() => ({}));
3062
- if (!res.ok || data.ok === false) throw new Error(data && data.message ? String(data.message) : '读取设置失败');
3127
+ const [settingsRes, stateRes] = await Promise.all([
3128
+ fetch('/api/compact-relay-settings', { cache: 'no-store' }),
3129
+ fetch('/api/compact-relay-state', { cache: 'no-store' }).catch(() => null),
3130
+ ]);
3131
+ const data = await settingsRes.json().catch(() => ({}));
3132
+ if (!settingsRes.ok || data.ok === false) throw new Error(data && data.message ? String(data.message) : '读取设置失败');
3133
+ // 0.4.1:补上实际触发线相关字段(配置接口没有这些,只有 state 接口有)。
3134
+ let extra = null;
3135
+ if (stateRes !== null && stateRes.ok) {
3136
+ const stateData = await stateRes.json().catch(() => ({}));
3137
+ const sn = stateData !== null && stateData.native !== null && typeof stateData.native === 'object' ? stateData.native : null;
3138
+ if (sn !== null) {
3139
+ extra = {
3140
+ ratio: typeof sn.ratio === 'number' ? sn.ratio : null,
3141
+ thresholdTokens: typeof sn.thresholdTokens === 'number' ? sn.thresholdTokens : null,
3142
+ contextWindow: typeof sn.contextWindow === 'number' ? sn.contextWindow : null,
3143
+ reservedCompletionTokens: typeof sn.reservedCompletionTokens === 'number' ? sn.reservedCompletionTokens : null,
3144
+ headroomTokens: typeof sn.headroomTokens === 'number' ? sn.headroomTokens : null,
3145
+ overridden: sn.overridden === true,
3146
+ route: sn.route !== null && typeof sn.route === 'object' ? sn.route : null,
3147
+ };
3148
+ }
3149
+ }
3150
+ nativeLineExtra.current = extra;
3063
3151
  applyLoaded(data);
3064
3152
  setStatus(null);
3065
3153
  } catch (error) {
@@ -3250,7 +3338,16 @@ window.__ModuleLoader__.load({
3250
3338
  })();
3251
3339
 
3252
3340
  const autoOnNative = native !== null && native.autoEnabled !== false;
3253
- const nativePercent = native !== null && typeof native.thresholdRatio === 'number' ? Math.round(native.thresholdRatio * 100) : 80;
3341
+ // 0.4.1:配置阈值(thresholdRatio)只是理论上限;实际触发线由宿主按 token 预算算。
3342
+ const nativeInfo = nativeLineInfo(native);
3343
+ const nativePercent = nativeInfo.configured !== null ? Math.round(nativeInfo.configured * 100) : 80;
3344
+ const nativeCapRatio = nativeInfo.effective !== null ? nativeInfo.effective : nativeInfo.configured !== null ? nativeInfo.configured : 0.8;
3345
+ // 交接线可设上限:按实际触发线向下取整,输入框 max 与 onChange 用同一个数。
3346
+ const nativeCapPercent = Math.max(5, Math.floor(nativeCapRatio * 100 - 1));
3347
+ // 原生压缩已关闭时 effective/percentText 为空:回落到「开启后实际会触发多少」,再回落配置阈值。
3348
+ const nativeActualText = nativeInfo.percentText !== '' ? nativeInfo.percentText : nativeInfo.actualText !== '' ? nativeInfo.actualText : nativeInfo.configuredText !== null ? nativeInfo.configuredText : String(nativePercent);
3349
+ const handoffOverNative = nativeInfo.effective !== null && form !== null && typeof form.thresholds === 'object' && typeof form.thresholds.handoffRatio === 'number' && form.thresholds.handoffRatio > nativeInfo.effective + 1e-9;
3350
+ const handoffNativeWarning = handoffOverNative ? `当前交接线 ${Math.round(form.thresholds.handoffRatio * 100)}% 高于原生实际触发线 ${nativeInfo.percentText}%:DSH 会先自己压历史,这条提醒可能来不及。建议调到 ${nativeInfo.percentText}% 以下(改完立即生效)。` : '';
3254
3351
  const previewLight = paletteForScheme(form.colors, 'light');
3255
3352
  const previewDark = paletteForScheme(form.colors, 'dark');
3256
3353
  // 0.4 P0-2:诊断预览弹层复用指示灯那套浮层材质(15-shared.panelChrome)。
@@ -3322,19 +3419,19 @@ window.__ModuleLoader__.load({
3322
3419
  settingsRow(
3323
3420
  '交接提醒线',
3324
3421
  '到这条线内圆亮红点,菜单建议「交接新会话」。',
3325
- '调高 → 更晚换会话、单会话更久;调低 → 更早换会话、单会话更短。必须大于压缩线。',
3422
+ `调高 → 更晚换会话、单会话更久;调低 → 更早换会话、单会话更短。必须大于压缩线。${handoffNativeWarning}`,
3326
3423
  h('div', { style: { display: 'flex', alignItems: 'center', gap: 8 } },
3327
3424
  h('input', {
3328
3425
  type: 'number',
3329
3426
  min: 5,
3330
- max: native !== null && native.autoEnabled !== false ? Math.max(5, Math.round(native.thresholdRatio * 100) - 1) : 99,
3427
+ max: native !== null && native.autoEnabled !== false ? nativeCapPercent : 99,
3331
3428
  step: 1,
3332
3429
  value: handoffPercent,
3333
3430
  'aria-label': '交接提醒线(百分比)',
3334
3431
  style: { ...settingsInputStyle, width: 90 },
3335
3432
  onChange: (event) => {
3336
3433
  const raw = Math.max(0.05, Number(event.target.value) / 100);
3337
- const cap = native !== null && native.autoEnabled !== false ? Math.max(0.05, native.thresholdRatio - 0.01) : 0.99;
3434
+ const cap = native !== null && native.autoEnabled !== false ? nativeCapPercent / 100 : 0.99;
3338
3435
  patch(['thresholds', 'handoffRatio'], Math.min(cap, raw));
3339
3436
  },
3340
3437
  }),
@@ -3387,10 +3484,10 @@ window.__ModuleLoader__.load({
3387
3484
  native === null
3388
3485
  ? '正在读取 DSH 配置…'
3389
3486
  : autoOnNative
3390
- ? `当前开启:容量到 ${nativePercent}% 时,DSH 会自己把历史压成摘要(不读本插件的锁定)。`
3487
+ ? `当前开启:容量到 ${nativeActualText}% 时,DSH 会自己把历史压成摘要(不读本插件的锁定)。`
3391
3488
  : '当前已关闭:DSH 不再自动压历史,只有你主动压缩/交接才会动历史。',
3392
3489
  autoOnNative
3393
- ? `开启时:容量到 ${nativePercent}% DSH 会自己压历史(不读锁定);关闭后:只在你主动压缩/交接时才动历史。`
3490
+ ? `开启时:容量到 ${nativeActualText}% DSH 会自己压历史(不读锁定);关闭后:只在你主动压缩/交接时才动历史。${nativeInfo.differs ? `(${nativeInfo.why})` : ''}`
3394
3491
  : '开启后:DSH 会在容量到原生阈值时自动压历史,可能压掉锁定的消息;本插件的提醒照常。',
3395
3492
  h(
3396
3493
  'div',
@@ -3437,7 +3534,13 @@ window.__ModuleLoader__.load({
3437
3534
  }),
3438
3535
  ),
3439
3536
  h('span', { style: { fontSize: 12, fontWeight: 600, minWidth: 44 } }, native === null ? '读取中…' : nativeBusy ? '处理中…' : autoOnNative ? '已开启' : '已关闭'),
3440
- h('span', { style: settingsHintStyle }, native !== null ? `原生阈值 ${nativePercent}%` : ''),
3537
+ h('span', { style: settingsHintStyle }, native !== null
3538
+ ? (nativeInfo.autoOff
3539
+ ? (nativeInfo.actualText !== ''
3540
+ ? `原生阈值 配置 ${nativeInfo.configuredText ?? nativePercent}%(已关闭;开启后实际触发 ${nativeInfo.actualText}%)`
3541
+ : `原生阈值 配置 ${nativeInfo.configuredText ?? nativePercent}%(已关闭)`)
3542
+ : (nativeInfo.differs ? `原生阈值 配置 ${nativeInfo.configuredText}%(实际触发 ${nativeInfo.percentText}%)` : `原生阈值 ${nativeActualText}%`))
3543
+ : ''),
3441
3544
  nativeStatus === null
3442
3545
  ? null
3443
3546
  : h(
@@ -3474,9 +3577,9 @@ window.__ModuleLoader__.load({
3474
3577
  { style: { ...settingsHintStyle, marginBottom: 8 } },
3475
3578
  confirmNative === 'disable'
3476
3579
  ? `操作内容:在 DSH profile 的 cordis.patch.yml 里把 compaction-basic 的 auto 改为 false(写前备份原文件,可回滚)。` +
3477
- `改变:DSH 不再在容量到 ${nativePercent}% 时自动压缩历史;锁定的消息不会被压掉;本插件的提醒和手动压缩照常。需要重启 DSH 生效。`
3580
+ `改变:DSH 不再在容量到 ${nativeActualText}% 时自动压缩历史;锁定的消息不会被压掉;本插件的提醒和手动压缩照常。需要重启 DSH 生效。`
3478
3581
  : `操作内容:在 DSH profile 的 cordis.patch.yml 里把 compaction-basic 的 auto 改回 true(写前备份,可回滚)。` +
3479
- `改变:DSH 重新在容量到 ${nativePercent}% 时自动压历史,可能压掉锁定的消息。需要重启 DSH 生效。`,
3582
+ `改变:DSH 重新在容量到 ${nativeActualText}% 时自动压历史,可能压掉锁定的消息。需要重启 DSH 生效。`,
3480
3583
  ),
3481
3584
  h(
3482
3585
  'div',
@@ -3664,13 +3767,15 @@ window.__ModuleLoader__.load({
3664
3767
  { ratio: compactRatio + 0.08, hint: null, title: '进入压缩区', text: '琥珀:建议压缩旧会话' },
3665
3768
  { ratio: handoffRatio + 0.15, hint: null, title: '越过交接线', text: '红:建议交接新会话' },
3666
3769
  ];
3667
- if (native !== null && native.autoEnabled !== false) {
3770
+ if (native !== null && native.autoEnabled !== false && nativeInfo.effective !== null) {
3668
3771
  legend.push({
3669
- ratio: native.thresholdRatio + 0.06,
3772
+ ratio: Math.min(0.95, nativeInfo.effective + 0.06),
3670
3773
  hint: null,
3671
- nativeAt: native.thresholdRatio,
3774
+ nativeAt: nativeInfo.effective,
3672
3775
  title: '原生自动压缩线',
3673
- text: `${Math.round(native.thresholdRatio * 100)}%:DSH 会自己压历史(越过整环红色闪烁)`,
3776
+ text: nativeInfo.differs
3777
+ ? `实际 ${nativeInfo.percentText}%:DSH 会自己压历史(配置阈值 ${nativeInfo.configuredText}%,越过整环红色闪烁)`
3778
+ : `${nativeInfo.percentText}%:DSH 会自己压历史(越过整环红色闪烁)`,
3674
3779
  });
3675
3780
  }
3676
3781
  return h(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-compact-relay",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "DeepSeek Harness 上下文指示器插件:提供容量监控、压缩预览与确认、九段式交接单生成及消息锁定功能。 · Context indicator for DSH: preview-then-confirm compaction, 9-section session handoff, and pinned messages.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",