dsh-compact-relay 0.4.0 → 0.4.2

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,44 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2(2026-10-26 · 交接线保命补丁)
4
+
5
+ ### 修复:交接提醒可能晚于 DSH 原生压缩(0.4.1 的三个洞)
6
+
7
+ - **现象**:① 服务端保存校验只看配置阈值(80%),手改设置文件能把交接线设到 70%,而 UI 按实际触发线只允许 66%;② 交接线 60% 是死值——换到实际触发线更低的模型(如 55%)时,指示灯/菜单照旧按 60% 提醒,DSH 会先自己压历史;③ 已保存的交接线高于当前模型实际线时,除设置页一行 warning 外没有可见提示。
8
+ - **修复(P0-1 运行时判定)**:新增共享纯函数 `lib/core/handoff-guard.js`(`HANDOFF_MARGIN_PP=5`、`handoffCapOf()`、`handoffGuard()`):**交接线硬上限 = 实际触发线 −5pp,向下取整到整数百分位**(deepseek-flash 1M:67.8% → 62%)。违规时**只告警、不静默改**:指示灯悬停卡 + 环上交接刻度旁小警示、`/handoff` 菜单项、设置页三处;设置页提供「按实际线重算」一键(二次确认,目标 = 实际线 −5pp),目标 ≤ 压缩线时拒绝并提示先降压缩线。
9
+ - **修复(P0-2 服务端同口径)**:`save(patch, { nativeRatio })` 接受客户端传入的当前模型实际触发线,按实际线 −5pp 校验;未带(旧客户端 / 无会话)退回配置阈值 −1pp 防呆,行为同 0.4.1。超限一律 400 + `field=thresholds.handoffRatio` + 「最高可设 X%」,不夹紧、不落盘。实际触发线算不出(`native.unavailable`)时不告警、不阻断,保存退回配置阈值;DSH 原生自动压缩关闭时不校验。
10
+ - **修复(P1 换模型跟随)**:客户端状态刷新即重新判定;观察到路由模型变化且首次发现违规时弹一次 toast(localStorage 记上次路由,同路由不重复打扰)。指示灯环上只加警示节点,不改分段/断点逻辑。
11
+ - **兼容性**:`nativeRatio` 为可选参数,旧客户端不带 → 0.4.1 行为;旧设置文件原样加载,不静默改写。兜底上限从「< 配置阈值」明确为「≤ 配置阈值 −1pp」(与 0.4.1 提示文案「最高 79%」一致;0.4.1 代码实际接受 79.5%,0.4.2 起按文案执行,仅影响亚百分点手改值)。
12
+ - **文档(P2)**:README / README.en / TUTORIAL 写清三条线 **40% 压缩线 / 60% 交接线 / 67.8% 原生实际触发线**,删掉「原生线 −1%」旧口径;CHANGELOG / ROADMAP 同步。
13
+ - **测试**:新增 `test/handoff-guard.mjs`(纯函数 12 组边界 + 服务端保存校验 8 组:带/不带 nativeRatio、0.62/0.63 边界、非法参数、旧设置文件、压缩线冲突、原生关闭;客户端产物接线断言);`test/plugin-load.mjs` 增加 HTTP 集成(临时 profile:`nativeRatio=0.678` 保存 65% → 400 含「最高可设 62%」、62% → 200、不带 → 200 兼容、85% → 400 仍按 62% 而非 79%)。`npm test` 全绿。
14
+ - **验证边界**:设置页/悬停卡/菜单/换模型 toast 的真机点验需在真实 DSH 会话里确认(本仓库测试环境无 Chrome 自动化);服务端与纯函数、构建一致性已自动化覆盖。
15
+
16
+ ### 修复:设置页保存条 + 交接线输入被静默改写(真机反馈)
17
+
18
+ - **保存条(上一提交)**:设置页很长,保存按钮原在最底部;改成粘性底部工具条(`position:sticky; bottom:0`),含保存 / 恢复默认 / 未保存指示 / 保存结果,保存失败文案同时出现在条内与交接线行内。
19
+ - **现象(真机)**:「输入 80 他直接改成 99 然后报错」。交接线输入框里打 80,输入框自己变成 99(=0.99),点保存再报错。
20
+ - **根因(客户端三处叠加,`main@2b4d10b` 的 `lib/client/50-settings.js`)**:① `:549` 每次按键都 `Math.min(0.99, Math.max(0.05, value))`——夹紧上限写死 99%,和真实上限 62% 无关;② `:542`/`:408` 输入框 value 是派生值 `Math.round(ratio*100)`、**没有本地草稿**,所以中间态(60 后面打 8 → 608)当场被夹成 0.99 并渲染成「99」,用户输入消失;③ `:540` `max` 是 62 只作浏览器提示,`type=number` 打字不受约束,代码却按 99 夹。同族问题:最小线/压缩线也是「按键即夹紧」,清空输入框会被 `Number('')=0` 夹成 5%,无法清空重打。
21
+ - **修复**:百分比输入加本地草稿(按键只记草稿 + 原样写入 `value/100`,空串/非数字不改表单;失焦/加载/保存成功后回落表单派生值);`max` 用当前 cap(62%,算不出时 100)并新增「最高 62%」行内徽标;三个百分比输入聚焦/点击即全选(避免在旧值后面追加);超限仍由服务端 400 +「最高可设 X%」拒绝,**不夹紧、不落盘**。
22
+ - **视觉打磨**:交接线行重排(控制行 / 体量估算 / 上限说明 / amber 告警 / 红色失败 / 右对齐「按实际线重算」动作行);告警与失败块改淡色底(7–8%)+ 同色描边;保存条按钮与状态文字对齐、状态独立一行胶囊(红/绿)、高度 47px,深浅主题都用现有 token(探针:条底 rgb(35,35,36)/rgb(255,255,255),错误文字均为红色主导)。
23
+ - **测试**:`test/settings-ux.mjs` 新增「输入 80 输入框仍是 80(不是 99)→ 保存 400 → 条内+行内双处报错 → 改 62 保存成功」;`npm test` **197 PASS / 0 FAIL**;`test/e2e/settings-ux.mjs`(隔离 home + 无头 Chrome)真键盘输入 80/62 全流程 PASS,截图见 `default-workspace/2026-10-25-收尾/`。
24
+
25
+ ## 0.4.1(2026-10-25)
26
+
27
+ ### 修复:原生压缩线显示口径不一致(设置页仍写 80%,指示灯写实际触发 67.8%)
28
+
29
+ - **现象**:0.4.0 把指示灯/悬停卡改为「实际触发线」(DSH 按 `min(窗口×thresholdRatio, 窗口−输出预留−headroomTokens)` 触发),但设置页的「作用 / 调整后 / 原生阈值」三处和「指示灯怎么看」教学卡仍按 `thresholdRatio` 显示 80% —— 同一插件两处数字互相矛盾,而且设置页那句「80%:DSH 会自己压历史」本身是错的(实际 67.8% 就压)。
30
+ - **修复**:客户端抽出纯函数 `nativeLineInfo()`(实际线优先;给出配置阈值、实际触发点与差异原因);设置页三处 + 教学卡刻度/文案 + 指示灯悬停卡统一为「实际触发 X%(配置阈值 Y%)」,并说明「模型输出预留 + DSH 自身余量」使实际触发更早;交接提醒线上限也改用实际触发线(原按配置 80% 算,允许把交接线设在实际触发点之后、永远走不到)。
31
+ - **不改**:宿主计算、触发条件、压缩/交接行为;纯显示文案与校验上限。
32
+ - **验证**:Mac 真机(0.4.1 客户端):设置页与悬停卡同显 **67.8%**(678.5K / 1.00M,deepseek-flash),配置阈值 80% 作为注释保留;交接线上限随实际线收紧。
33
+
34
+ ### 显示层补强(dev-god 独立审查后,同属 0.4.1)
35
+
36
+ - **关闭态空数字**:`autoEnabled=false` 时原生线信息原本连「实际会触发多少」一起丢,设置页会出现「原生阈值 %」、确认弹层出现「容量到 %」。现在关闭态显示「配置 80%(已关闭;开启后实际触发 67.8%)」,恢复确认也用实际值。
37
+ - **差异归因**:`why` 只在「预算分支确实更小」时才归因为输出预留/余量;`modelPolicies` 路由覆盖改说「该模型单独配了阈值」;实际触发晚于配置阈值时不再套用「理论上限」说法(原实现会自相矛盾)。
38
+ - **交接线遗留值**:设置页对「已保存交接线 > 实际触发线」给出显式提示(不静默改写);交接线输入上限与保存口径统一(按实际触发线向下取整)。服务端保存校验仍以配置阈值做上限(防呆不阻断),本版仅在 UI 收紧并提示如何调整。
39
+ - **文档口径**:README badge / README.en 同步到 0.4.1 与实际触发线说明;ROADMAP 标注已失效参考仓库。
40
+ - 测试:`test/native-line.mjs` 新增 `nativeLineInfo` 行为回归(正常/关闭/算不出/路由覆盖/实际晚于配置五组输入,从客户端源码切出函数真跑)。
41
+
3
42
  ## 0.4.0(2026-10-24)
4
43
 
5
44
  ### 修复:压缩/交接后「压缩判断记录」从未失效(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,12 +61,22 @@ 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
 
68
68
  ![Crossing the native auto-compaction line](assets/native-alert.jpg)
69
69
 
70
+ ## The three lines (deepseek-flash, 1M window defaults)
71
+
72
+ | Line | Default | Meaning | How to change |
73
+ | --- | --- | --- | --- |
74
+ | Compaction line | 40% | The ring turns amber and the menu suggests "compact old session" | Settings → Compact Relay |
75
+ | Handoff line | 60% | The inner dot turns red and the menu suggests "hand off to a new session" | Settings → Compact Relay; **hard cap = actual trigger −5pp** (62% for deepseek-flash) |
76
+ | Native auto-compaction line | 67.8% | Where DSH actually auto-compacts, computed from its token budget (the configured 80% is only a theoretical ceiling; output reservation + DSH headroom pull it down to 67.8% for deepseek-flash) | `thresholdRatio` of `compaction-basic` in the profile (default 80%) |
77
+
78
+ **Why the handoff line keeps a 5-point margin**: the handoff reminder must fire before DSH compacts on its own, otherwise there is no time to generate a handoff document and open the new session. Since 0.4.2 the handoff line is hard-capped at **actual trigger −5pp** (floored to a whole percent; 62% for deepseek-flash on a 1M window). Going over **rejects the save and shows the target value** — values are never silently clamped. Existing higher values are not rewritten behind your back, but the settings page keeps warning and offers a one-click "recalculate from actual line". When the actual trigger line is unavailable (no session / host did not report it), nothing is warned or blocked and save validation falls back to the configured threshold guard (same as 0.4.1).
79
+
70
80
  ## Installation
71
81
 
72
82
  **Requirements**: DSH Desktop or `dsh web` 0.2.x (tested on 0.2.1-rc.2), Node.js 22+ (the plugin uses the runtime bundled with DSH). Running the source test suite requires Node **22.15+** (the harness uses `node:module.registerHooks`). The suite is verified on macOS and Windows 11 (Node 24.22).
@@ -143,7 +153,7 @@ The first time a session is opened, the ring plays a brief highlight animation w
143
153
 
144
154
  ## Version and license
145
155
 
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.
156
+ - **Current version**: 0.4.2 (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; 0.4.2 is the handoff-line safety patch: hard cap = actual trigger −5pp with the same rule in the UI and on the server, over-limit saves are rejected with the target value, warnings in three places plus a one-click recalculation, a one-off notice after switching models, and documentation of the 40/60/67.8 lines). 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
157
  - **License**: [MIT](LICENSE). Feedback and Pull Requests are welcome via [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues).
148
158
 
149
159
  ## 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%)。当上下文占用越过原生自动压缩线时,整环呈红色闪烁告警。交接提醒线晚于当前模型的实际触发线(−5pp)时,环上交接刻度旁会出现红色小警示,悬停卡与 `/handoff` 菜单也会同步标出「交接线已晚」。
34
34
 
35
35
  上下文进入「可压缩区间」(最小压缩线到压缩线之间)时,插件会在系统提示里主动告诉模型当前容量与区间,模型据此判断「刚完成一个自然节点」时是否记录压缩节点(0.3.4 起;此前模型看不到容量,判断条件不可执行)。
36
36
 
@@ -61,12 +61,22 @@
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
 
68
68
  ![越过原生自动压缩线](assets/native-alert.jpg)
69
69
 
70
+ ## 三条线(deepseek-flash 1M 默认)
71
+
72
+ | 线 | 默认值 | 含义 | 怎么调 |
73
+ | --- | --- | --- | --- |
74
+ | 压缩提醒线 | 40% | 到线变琥珀色,菜单建议「压缩旧会话」 | 设置 → 压缩接力 |
75
+ | 交接提醒线 | 60% | 到线内圆亮红点,菜单建议「交接新会话」 | 设置 → 压缩接力;**硬上限 = 实际触发线 −5pp**(deepseek-flash 下 62%) |
76
+ | 原生自动压缩线 | 67.8% | DSH 按 token 预算真正触发自动压缩的点(配置阈值 80% 只是理论上限,deepseek-flash 的输出预留 + DSH 余量把它压到 67.8%) | profile 里 `compaction-basic` 的 `thresholdRatio`(默认 80%) |
77
+
78
+ **交接线为什么要留 5 个百分点**:交接提醒必须早于 DSH 自己压历史,才有时间生成交接单、开新会话。所以 0.4.2 起交接线有硬上限 **实际触发线 −5pp**(取整到整数百分位;deepseek-flash 1M 窗口下是 62%):超过时**拒绝保存并提示目标值**,不静默夹紧;已存的高值不会被偷偷改写,但设置页会常驻告警,并可点「按实际线重算」一键改成上限。实际触发线算不出(无会话 / 宿主没给)时不告警、不阻断,保存校验退回配置阈值防呆(与 0.4.1 一致)。
79
+
70
80
  ## 安装
71
81
 
72
82
  **环境要求**:DSH 桌面版或 `dsh web` 0.2.x(已于 0.2.1-rc.2 测试通过),Node.js 22+(插件跟随 DSH 自带运行时)。若要在源码里跑测试,需要 Node **22.15+**(测试脚手架用到 `node:module.registerHooks`)。测试已在 macOS 与 Windows 11(Node 24.22)实测全部通过。
@@ -89,7 +99,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
89
99
 
90
100
  - **压缩提示**:配置最小压缩线、压缩提醒线与交接提醒线,控制告警触发时机。
91
101
  - **压缩行为**:配置摘要保留份数上限、近期原文保留 Token 数及交接文档存储目录。
92
- - **DSH 原生自动压缩**:读取宿主当前阈值,支持开启或关闭 DSH 原生自动压缩(修改 profile 配置前自动生成备份,重启后生效)。
102
+ - **DSH 原生自动压缩**:读取宿主阈值并换算实际触发线,支持开启或关闭 DSH 原生自动压缩(修改 profile 配置前自动生成备份,重启后生效)。
93
103
  - **颜色与外观**:配置五项基准色,系统将根据浅色/深色主题自动派生适配色阶。
94
104
  - **帮助**:提供指示环图例、使用说明、问题反馈入口、「再看一次初次引导」按钮,以及「诊断信息」(只在本机生成、可预览/复制,见下节)。
95
105
  - **关于与危险操作**:查看版本号、配置文件路径,支持恢复出厂默认设置。
@@ -143,7 +153,7 @@ dsh plugin --profile desktop add github:cfanzx/dsh-compact-relay
143
153
 
144
154
  ## 版本状态与开源许可
145
155
 
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)。
156
+ - **当前版本**:0.4.2(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** 统一原生压缩线显示口径:指示灯 / 设置页 / 教学卡都显示实际触发点,并注明配置阈值与输出预留;**0.4.2** 交接线保命补丁:硬上限 = 实际触发线 −5pp(UI 与后台同口径,超限拒绝保存并给目标值)、三处告警 + 一键重算、换模型后一次性提示,文档写清 40/60/67.8 三条线)。已在 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
157
  - **开源许可**:采用 [MIT](LICENSE) 许可证,欢迎通过 [GitHub Issues](https://github.com/cfanzx/dsh-compact-relay/issues) 与 Pull Requests 提交反馈。
148
158
 
149
159
  ## 参考与致谢
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.2(2026-10-25)|状态:0.3 已发布(npm + GitHub),0.3.6 已发布,0.4.0 已发布(npm latest + GitHub tag/Release),0.4.1 已发布;**0.4.2 交接线保命补丁已实现、待验收与发布**
4
4
  > 维护方式:每次发布前更新"当前状态"与勾选进度;方向变化必须同时改本文件和 memory 项目页。
5
5
 
6
6
  ## 一、最终目标(North Star)
@@ -54,11 +54,22 @@
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`。
61
61
 
62
+ **0.4.2 交接线保命补丁(已实现、待 Lead 验收;发布须用户点头)**
63
+
64
+ > 范围定稿:`dcr-0.4.2-范围定稿-2026-10-25.md`|实施规划:`docs/0.4.2-implementation-plan.md`
65
+
66
+ - ✅ **P0-1 交接线硬规矩**:硬上限 = 实际触发线 −5pp(整数百分位;deepseek-flash 1M 下 62%),共享纯函数 `core/handoff-guard.js`;违规只告警不静默改(指示灯悬停卡 + 环上交接刻度警示、`/handoff` 菜单、设置页三处);设置页「按实际线重算」一键 + 二次确认,目标 ≤ 压缩线时拒绝并提示先降压缩线。
67
+ - ✅ **P0-2 服务端同口径**:`save()` 接受可选 `nativeRatio`,按实际线 −5pp 校验;未带退回配置阈值 −1pp 防呆(兼容 0.4.1 客户端);超限 400 + `field` + 目标值,不落盘。
68
+ - ✅ **P1 换模型跟随**:状态刷新即重判;观察路由变化后首次违规弹一次 toast;算不出实际线不告警、不阻断。
69
+ - ✅ **P2 文档三线**:README / README.en / TUTORIAL 写清 40% / 60% / 67.8%,删掉「原生线 −1%」旧文案;CHANGELOG 0.4.2 段同步。
70
+ - ⏳ 待真机点验:设置页 max=62 与提示、环上警示、菜单 ⚠、换模型 toast、截图存档(本仓库自动化无 Chrome/CDP)。
71
+ - 明确不做(0.4.2 边界):历史前缀 / 接力条(0.5.0)、自动触发 / 自动归档(1.0)、改宿主触发逻辑、环上 A/B/C 画法。
72
+
62
73
  1. ✅ **交接单六段 → 九段**(0.3 已提前交付):总体目标 / 约束 / 已完成 / 当前进度 / 改过的文件 / 关键决策 / 已知问题 / 未完成 / 下一步。
63
74
  - 文件改动、关键决策由程序收集(`core/handoff-facts.js`),模型只写 7 段、编不了。
64
75
  2. ✅ **交接就绪检查**(0.3 已提前交付):九段齐全/占位阻断,路径核对只警告;预览卡里先看再确认。
@@ -134,7 +145,7 @@
134
145
  | [dsh-command-context-trim](https://www.npmjs.com/package/dsh-command-context-trim) | 0.6.6 / 10-03 | 无模型 `/trim`、小窗口切换 | 切掉 |
135
146
  | [context-vista](https://www.npmjs.com/package/context-vista) | 0.1.0 / 08-14 | 环图、成本估算 | 仪表盘不做 |
136
147
  | [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 + 高保真摘要 | 智能保留不自研 |
148
+ | dsh-context-guard(原 https://github.com/wood20100715-collab/dsh-context-guard 已失效,2026-10-25 核查 404) | 仓库 | 内容类型压缩 + CCR + 高保真摘要 | 智能保留不自研 |
138
149
  | [dsh-session-handoff](https://github.com/snow-The/dsh-session-handoff) | GitHub | 结构化交接文档 + 官方 compaction API | 交接同赛道;我们做"检查 + 链 + 记忆" |
139
150
 
140
151
  ## 六、已定决策
@@ -149,9 +160,9 @@
149
160
  - ✅ 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
161
  - ✅ 0.3.4 判断机制修复(BUG-010:`mark_compress_point` + systemPrompt 可压缩区间提示 + 状态缓存;427 会话数据佐证旧工具 0 调用)+ BUG-005/BUG-006 小 bug;npm latest 待本次发布。
151
162
  - ✅ 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 开发版);发布动作待办。
163
+ - ✅ 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
164
  - ✅ 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**。
165
+ - ✅ 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
166
  - ✅ 真实压缩 e2e:`test/e2e/real-compact.mjs`(真实模型 + 真实压缩事务 + `maxSummaries=1` 合并旧摘要分支)6 项断言通过。
156
167
  - ✅ P3-01~14(13 修/1 确认无需修)+ BUG-004 + P2-T1 已收口。
157
168
  - ⏳ 市场收录 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
 
@@ -42,10 +42,11 @@
42
42
  - **指示灯怎么看**:设置页「帮助」区顶部的教学卡片,5 个示意环(未满灰 / 走满蓝 / 压缩区琥珀 / 交接区红 / 原生线),随你的配色和阈值实时变
43
43
  - **再看一次初次引导**:「帮助」区的按钮,点了关掉设置页就会重播引导(手动重播时长更长)
44
44
  - **最小压缩线**:低于它压缩不划算(默认 5%,会随保留量和窗口自动抬高)
45
- - **压缩提醒线 / 交接提醒线**:按上下文窗口的百分比算(1M 窗口默认 40% / 60%),换小窗口模型自动缩放
45
+ - **压缩提醒线 / 交接提醒线**:按上下文窗口的百分比算(1M 窗口默认 40% / 60%),换小窗口模型自动缩放;三条线的关系见下一条
46
46
  - **摘要份数上限**:压到几份摘要后建议交接(默认 6)
47
47
  - **保留近期原文**:压缩时这段 token 内的近期消息保持原文(默认 40K)
48
- - **DSH 原生自动压缩**:状态 + 一键关闭/恢复(会先确认,改 profile 配置前备份,重启生效);原生开着时交接线最高只能设到「原生线 −1%」
48
+ - **DSH 原生自动压缩**:状态 + 一键关闭/恢复(会先确认,改 profile 配置前备份,重启生效);原生开着时交接线硬上限 = **实际触发线 −5pp**(0.4.2 起;deepseek-flash 1M 下是 62%),超限保存会被拒绝并提示目标值,设置页有「按实际线重算」一键改成上限
49
+ - **三条线(deepseek-flash 1M 默认)**:40% 压缩提醒线 → 60% 交接提醒线 → 67.8% 原生实际触发线(配置阈值 80% 只是理论上限,输出预留 + DSH 余量把真实触发点压到 67.8%)。交接线晚于实际触发线时,环上交接刻度、悬停卡、`/handoff` 菜单三处都会告警
49
50
  - **交接单目录**:九段交接单保存位置
50
51
  - **颜色**:选 5 个基准色(默认/填满/调整/上限/锁定),浅色/深色两套自动派生;每行都有「作用」和「调整后」两行说明
51
52
 
@@ -56,7 +57,8 @@
56
57
  - **指示灯显示「上下文未知」**:会话刚打开,等半秒到 1.5 秒会自己出数。
57
58
  - **没有「可压」内容**:会话太短,或近期内容需要保留原文,或锁定把范围挡住了(先 `/unpin`)。
58
59
  - **压缩到一半取消了**:历史不会变、锁会释放;已发出的模型请求可能仍计费。
59
- - **换模型后阈值变了**:阈值按窗口比例算,小窗口模型会自动变小,这是预期。
60
+ - **换模型后阈值变了**:阈值按窗口比例算,小窗口模型会自动变小,这是预期;交接线还有一条硬上限「当前模型实际触发线 −5pp」,换到实际线更低的模型后旧交接线会变成「已晚」。
61
+ - **提示「交接线已晚」/ 保存被拒**:说明当前交接线晚于 DSH 原生压缩线,DSH 会先自己压历史。三种处理:① 点设置页「按实际线重算」(一键改成实际线 −5pp,最省事);② 手动把交接线调到提示的目标值;③ 关掉「DSH 原生自动压缩」(不再有截止线)。实际触发线算不出时不会告警,也不会因此拒绝保存。
60
62
  - **设置保存了但没变**:设置栏保存即时生效;改代码或 profile 配置才需要重启 DSH。
61
63
  - **在 headless / QQ 桥上发 `/bc` 没反应**:指示灯和 `/bc`、`/handoff` 依附桌面输入框与斜杠命令,headless 会话和 QQ / 微信桥接链路不会加载它们,`/bc` 会被当成普通消息。插件本身不报错、也不额外烧模型调用;要在电脑上开 DSH 桌面版才能用。
62
64
 
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { resolveConfig } from '../config.js';
14
14
  import { paletteForBothSchemes } from '../core/colors.js';
15
+ import { handoffGuard } from '../core/handoff-guard.js';
15
16
  import { isPlainObject } from '../core/object.js';
16
17
  import { readNativeCompaction } from '../dsh/native-compaction.js';
17
18
  import { filterSettingsPatch, publicConfigOf } from '../core/config-schema.js';
@@ -125,9 +126,12 @@ export function createSettingsStore({ config = {}, file = defaultSettingsPath()
125
126
  /**
126
127
  * 合并保存一组设置;先校验再写盘,最后热替换当前配置。
127
128
  * @param {object} patch
129
+ * @param {{nativeRatio?:number|null}} [options]
130
+ * 0.4.2:客户端传入的「当前模型实际触发线」(state.native.ratio)。
131
+ * 带了就按「实际线 −5pp」校验交接线;没带 / 非法(旧客户端、无会话)退回配置阈值防呆。
128
132
  * @returns {object} 最新 view
129
133
  */
130
- save(patch) {
134
+ save(patch, { nativeRatio = null } = {}) {
131
135
  const filtered = filterSettingsPatch(patch);
132
136
  if (Object.keys(filtered).length === 0) throw new Error('没有可保存的设置项');
133
137
  const nextUser = deepMerge(user, filtered);
@@ -140,18 +144,22 @@ export function createSettingsStore({ config = {}, file = defaultSettingsPath()
140
144
  wrapped.field = error !== null && typeof error === 'object' ? error.field : undefined;
141
145
  throw wrapped;
142
146
  }
143
- // 0.3.1:原生自动压缩开着时,交接线不能 >= 原生阈值(否则原生会先动手、我们的提醒失效)。
147
+ // 0.4.2(P0-2):交接线硬上限与 UI 同口径 —— 实际触发线 −5pp(整数百分位)。
148
+ // 实际线算不出(未带 nativeRatio / 无会话)时退回配置阈值 −1pp 防呆,与 0.4.1 一致;
149
+ // 原生自动压缩关闭时不校验(没有「DSH 先压」的截止线)。一律拒绝 + 给目标值,不静默夹紧。
144
150
  const nativeCfg = readNativeCompaction();
145
- if (nativeCfg.autoEnabled) {
146
- const limit = nativeCfg.thresholdRatio - 0.01;
147
- if (next.thresholds.handoffRatio >= nativeCfg.thresholdRatio - 1e-9) {
148
- const wrapped = new Error(
149
- `「交接提醒线」必须低于 DSH 原生自动压缩线(${(nativeCfg.thresholdRatio * 100).toFixed(0)}%),否则原生压缩会先动手;` +
150
- `最高只能设到 ${(limit * 100).toFixed(0)}%。想设更高请先在设置里关闭「DSH 原生自动压缩」。`,
151
- );
152
- wrapped.field = 'thresholds.handoffRatio';
153
- throw wrapped;
154
- }
151
+ const guard = handoffGuard({
152
+ actualRatio: nativeRatio,
153
+ configuredRatio: nativeCfg.thresholdRatio,
154
+ handoffRatio: next.thresholds.handoffRatio,
155
+ compactRatio: next.thresholds.compactRatio,
156
+ autoEnabled: nativeCfg.autoEnabled,
157
+ fallback: 'configured',
158
+ });
159
+ if (!guard.ok) {
160
+ const wrapped = new Error(guard.message);
161
+ wrapped.field = 'thresholds.handoffRatio';
162
+ throw wrapped;
155
163
  }
156
164
  // A4:信封装盘;旧扁平文件在这时自动升级,不丢用户设置。
157
165
  writeSettingsFile(file, nextUser);
@@ -294,7 +294,9 @@ export function registerHttp(webCtx, deps) {
294
294
  deps.hub?.refreshAll?.();
295
295
  return send(res, 200, { ok: true, ...settingsPayloadOf(deps) });
296
296
  }
297
- deps.settings.save(parsed);
297
+ // 0.4.2:客户端可选带 nativeRatio(当前模型实际触发线),服务端按实际线 −5pp
298
+ // 校验交接线(与 UI 同口径);不带则退回配置阈值防呆(兼容 0.4.1 客户端)。
299
+ deps.settings.save(parsed, { nativeRatio: parsed.nativeRatio });
298
300
  deps.hub?.refreshAll?.();
299
301
  return send(res, 200, { ok: true, ...settingsPayloadOf(deps) });
300
302
  } catch (error) {