dsh-context-pilot
DSH 上下文智能压缩 + 智能思考:每轮注入用量、模型自主决定压缩并自动续跑;可选让模型按任务难度自主调整思考强度档位
- Stars
- 2
- Language
- JavaScript
- Created
- Oct 6, 2026
- Updated
- Oct 16, 2026
Introduction
dsh-context-pilot — DSH 上下文智能压缩 + 智能思考插件
DSH(DeepSeek Harness)宿主侧插件 dsh-context-pilot(面板名「上下文智能压缩」,曾用名「上下文领航」):
让长会话的上下文压力可见、可控,并把「何时压缩」的决策权交给模型——用工具调用触发压缩,
压缩在你的下一个步骤开始前落地,本轮不中断、不注入任何消息、不伪造任何用户发言。
另含可选的智能思考:把当前模型的思考强度档位告知 Agent,由它按任务难度自主换档(同样是工具)。
安装
# 从 GitHub 仓库安装(本项目**不分发 npm 包**,这是推荐路径;pnpm 规格)
dsh plugin --profile web add github:Shuffle-1992/dsh-context-pilot
分发方式(2026-10-10 决定):不发布 npm。安装一律走上面的 GitHub 仓库规格;
dsh plugin --profile web add dsh-context-pilot这条 npm 形式在本项目不可用(无 npm 包)。
安装后重启 DSH 生效(bundle 资格由本包 package.json 的 dsh.bundle.patch 声明,
patch 文件:plugin/cordis.patch.yml)。
本地开发挂载(本机已实测的路径)
profile package.json 两处要改(以本机 desktop profile 为例):
{
"dependencies": { "dsh-context-pilot": "link:F:/My Code/dsh-context-pilot/plugin" },
"dsh": { "profile": { "bundles": [ /* … 其它 bundle …, */ "dsh-context-pilot" ] } }
}
再手工 Junction(可免 pnpm install):node_modules/dsh-context-pilot → <仓库>/plugin。
⚠️ 插件
package.json必须有dsh.bundle段,否则 plugin-manager 报not-bundle并静默跳过 (重启几次都不生效、host 无日志)——详见docs/setup.md§2 坑 P1。 包名统一为dsh-context-pilot(2026-10-10 起去掉@local/前缀);旧挂载名@local/dsh-context-pilot仍可用plugin/cordis.patch.local.yml兼容,但新装请用正式名。
仓库结构
| 路径 | 作用 |
|---|---|
package.json(根) | 可分发包清单(name/version/license/repository/dsh.bundle.patch/meta)——市场按仓库根识别 |
plugin/ | 插件本体(entry.mjs / client.js / 域模块 / icon.svg / 两个 patch 文件)——也是本机 link: 挂载目标 |
test/ | 四套断言(contract / static / report / boot) |
docs/ | 设计与交接文档(含 docs/setup.md 部署矩阵、docs/hub-publishing.md 上架清单) |
交接说明:本文是入口,读完 §1–§3 即可上手;深入内容在
docs/。最后更新 2026-10-15。
当前版本 v0.1.8 · 逐版本历史与实证记录见 docs/milestones.md。
最近更新(v0.1.6 → v0.1.35)
| 版本 | 更新 | 为什么值得知道 |
|---|---|---|
| v0.1.35 | 面板行后缀也带「预计稳定后」(与回执同源同数) | R45 只改了回执;用户同意后,三条面板路径共用的 occupancyActSuffix 也带上预计值:「压后占用 19.0%(provider 待刷新,预计稳定后 ≈10%)」。第三实参就是回执用的那个 settledRatio(pre-step 用同一局部量 ⇒ 两处永远一致);无样本保持原话「下次请求后会回落」(不编数字);非 stale 不加后缀。顺手修掉 R38 单源守卫的脆弱判据(原用 200 字符窗口,函数体一变长就误报) |
| v0.1.34 | 回执给「稳定后预计」——不再把 provider 未刷新的中间值当结果 | 用户实测:压缩后回执显示 19%,稳定后回落到 10%(行为没问题,只是回执报的是中间值)。修法:压缩完成时挂「稳定观察」⇒ 下一次非 stale 实测里量出真实回落量(tokens,中位数,≤8 样本)⇒ 回执改写成「压缩后占用 ≈19%(192k)(provider 未刷新)⇒ 稳定后预计 ≈10%(100k)」;无样本时如实说「本会话暂无实测回落值」(不编数字);stale 实测不得记回落(否则量的是假回落) |
| v0.1.33 | 真机走查锚点压缩 + 回执补「下限保护」 | 按用户要求实跑 keepFrom:{recentUser:1}:回执「锚点之后原样保留 ~40k(启发式;表盘 ≈ 62k(×1.56))…压缩后占用 ≈19%(192k)」,账本 anchorProbe = {via:'recent-user', seq:7783, tokens:1431, margin:8000, budget:40000, clamped:true}。四条硬证据:①锚点命中;②retainBudgetHeur = retainBudget = 40000 ⇒ 锚点确实没被折算(因子 1.56 只用于落点预估);③两个数分别=「该段 62k」与「压后总占用 192k」;④新发现:锚点段只有 1.4k,被 MIN 下限抬到 40k(clamped)⇒ 回执原先会被读成"那段有 40k"⇒ 现补「下限保护:锚点段实际只有 ~1.4k,为防丢新需求抬到 ~40k」 |
| v0.1.32 | 决策卡写清「两种模式」与优先级(全中文) | 用户要求「卡里要说清优先用 keepFrom 还是档位」。新增独立一条:「两种模式(二选一,锚点优先):①按比例(默认)tier 浅压24% ⇒ 表盘 ≈ 档位名 + 约 3%;②按位置 keepFrom:{recentUser:1}(或 {text:'开头十来字'})⇒ 该处起原样保留、不折算(表盘由位置定,回执给落点)。锚点未命中自动退回档位。」 并删掉原句尾重复的锚点提示、压缩冗词 ⇒ 教学预算 2998/3000。新增全中文守卫(剥掉代码标识符后不得残留拉丁字母;真机抓出过漏网的英文「3pp」) |
| v0.1.31 | 锚点语义定案:绝不折算(保持 R21 位置语义) | 用户明确「keepFrom 不用强制符合这个计算逻辑,应准确保留之前的逻辑」⇒ 确认现有实现即如此:scaleRetainBudget 只作用于档位预算,锚点预算原样进入范围选择;锚点侧(resolveAnchorBudget + R21「范围绝不越过锚点」护栏)未改动,唯一变的是回执文案(只读)。新增契约守卫:源码里不得出现 scaleRetainBudget(anchorBudget;变异 2/2 CAUGHT(把折算套到锚点上 ⇒ 各红 2 条) |
| v0.1.30 | 锚点回执给出「真实口径落点」——可按表盘挑锚点 | 用户追问「锚点能否也像档位那样折算成 26%?」⇒ 不能也不该:档位是预算承诺(能折算),锚点是位置承诺("之后一律原样保留",折算就是把锚点之后的内容丢掉)。但用户要的"可预期"能做到:锚点回执现在直接算给你看——「锚点之后原样保留 ~95k(启发式口径;表盘 ≈ 143k(×1.5))」。等价规则:想让表盘 ≈24%,锚点要选在启发式 ≈16% 的位置(即比过去更靠后一点) |
| v0.1.29 | 锚点(keepFrom)与真实口径的关系 + 修一处回归 | 锚点路径有意不折算:它是"位置承诺"(「从这条消息起之后一律原样保留」),按真实口径缩它就会越过锚点 ⇒ 违背 R21 护栏。所以档位=真实口径(降到 X%)、锚点=位置口径(这段不能丢),表盘落点分别 ≈ 档位名+固定开销 / ≈1.5×该段启发式量。同时修回归:R39 曾无条件报 tierBudget ⇒ 锚点生效时误报「保留预算 ~240k」;现按来源切换,回执也按来源标口径(锚点写「锚点之后原样保留 ~95k(启发式口径;表盘 ≈ ×1.5)」) |
| v0.1.28 | 修 R39:折算没生效(读错了"三行组成"的来源) | 首次实测:回执没带 ×因子、只省 51.7K、压后 42% ⇒ 折算失效。账本证据 retainBudgetHeur: 240000 / retainFactor: 1(新代码在跑,因子恒 1)。根因:R39 把整个 measure 传给 summarizeBreakdown,而按契约它要的是 svc('sessionProjections').stateOf(session,'contextBreakdown') 的结果 ⇒ 拿不到组成。现改用正确来源 + 三行齐全守卫(不齐就不折算,否则"只有 system 行"会把 factor 顶到上限 2 ⇒ 压过头)。测试教训:原测试把 summarizeBreakdown 整体桩成"返回三行"⇒ 太配合、放过了这个 bug;现改为只在服务边界打桩 + 用真函数,该 bug 已有变异覆盖 |
| v0.1.27 | 保留预算改「真实口径」——让档位名副其实 | 用户实测:DSH 原生 /compact 能压到 5%(messages 只剩 3.9K),我们的浅压24% 压完仍 40%。量化 6 个会话:表盘真实 / 三行启发式 ≈ 1.31–1.51(典型 1.4) ⇒ 我们按启发式保留 240K,实际保留 ≈340K ⇒ 档位名与表盘对不上。现按实测因子折算:预算 / factor(夹在 [1,2],只减不增;因子只用非 stale 实测、pre-step 每轮刷新)⇒ 浅压24% 压完应落在 24–28%。回执会写「保留预算 ~240k(真实口径 ×1.4)」 |
| v0.1.26 | 面板「压后占用」后缀单源 + 自选范围那条也报占用;实测确认「缺口是黏的」 | 现场压缩实测(60%→40.1%):回执 455k ⇔ 账本 455,337 逐位吻合,面板 40% ⇔ 插件 40.1% 完全一致 ✅。⚠️ 我预测"会落到 26%"错了——压后真值 ≈ 保留 240k + 系统/工具 18k + 黏性缺口 ~136K ≈ 40%(那份"provider 计入而三行不计"的开销不随压缩消失,本会话有大量截图)。现把「压后占用」后缀抽成单源 occupancyActSuffix,三条面板路径(引擎/手动、自选范围、idle)共用,自选范围那条也从"只报省 XK"升级为"报占用 + provider 待刷新提示" |
| v0.1.25 | 压缩后「压后占用」带「provider 待刷新」提示(13%→5% 之谜) | 真机:手动 /compact 后面板写「压后占用 12.6%」,13 分钟后表盘变成 5%。原因:表盘锚在 provider 实报上、滞后一次请求——刚压完是「旧实报 387,168 + 新 surface 8,194 − 上报时 surface 269,183 = 126,179」的过渡值;下一次请求完成后 provider 实报真实值(~53.6K)⇒ 落到 5%。现面板行在 stale(surface 缩水 >50%)时追加「(provider 待刷新,下次请求后会回落)」,正常轮次不附 |
| v0.1.24 | 手动切档后不再写「保持 X」(措辞失真修正) | 真机:用户 06:25:57 手动把档位从 max 切到 high,插件 06:26:01 已注入「用户刚把档位从 最高(max) 调到 高(high)」(检测正常 ✓),但模型 06:26:20 写成「档位决策:保持 高(high)」——照抄了卡片模板里的"保持" ⇒ 用户以为它没感知到切换。现切档那一轮后缀显式补「这是用户改的,不是"保持":请写「用户刚调到 X」」,教学同步加一句。另:手动纠正的效果已复测确认(t108–t112 连续 5 轮都只有 1 条决策行) |
| v0.1.23 | 持续重复 ⇒ 上报面板(含可复制纠正文案);备注上限 4→1 | 备注 4 条/轮已被证明压不住模型(实测照写 18–22 条/轮),压缩清先例也无效 ⇒ 不再跟模型较劲,改为把异常上报给用户:同轮决策行 ≥6 条时,面板 acts 行出现「20:xx · 决策行重复 12 次/轮 · 建议手动纠正;可直接复制:请停止在每个步骤重复写决策行…」——后半段可直接粘贴给 Agent(无需改客户端/刷新页面);同轮只上报一次。备注上限降到 1(留提示位、省 token) |
| v0.1.22 | 「动作背书」的决策行不算重复(修 R31 的误报) | 用户追问「被纠正后就不会注入了吧」时复查判据,发现:R27 允许「中途真的改了决定再补一行」,而 R31 按"同轮 ≥2 条 = 重复"⇒ 合法补行被误判。现统计前先看该条消息里有没有指向压缩/换档工具的 tool-call(工具名从 api 取,不硬编码);有 ⇒ 不计入。其它 tool-call(如 pwsh)不豁免。完整语义:守规矩 ⇒ 永不注入;停手 ⇒ 同轮也不再注入;下轮重置;复发才再注入 |
| v0.1.21 | 反重复备注改为「检测驱动」:监测到重复才注入 | 用户要求「只有当这种被强化后反复写决策的时候,监测到才注入」。R30 的固定节拍会打扰守规矩的会话。现改为读会话事件检测:assistant/message 自带 data.turn/step,只数正文 text 块(reasoning 里提到"压缩决策:"不算);同轮 ≥2 条才认定重复并投一条,只写 1 条完全不打扰;投后再写满 3 条才再提醒,每轮上限 4 条,新轮重置。state.m2.decisionRepeat / repeatNudges 进报告可核对 |
| v0.1.20 | 反重复备注:决策行每步重复是"模型模式",用结构性信号压 | 会话日志取证(97 轮/3032 步):插件注入 108 次(≈1.1 次/轮 ⇒ 卡只在轮首一次,不是每步),而模型写的决策行 t86 2 条 → t92 25 条 → t95 27 条 ⇒ 自我强化,纯文案压不住。现于重复发生处投极短备注:本轮发过卡 + step≥2 + 每 3 步一次(回执优先),文本为「本轮决策行只需写一次,后续步骤不必重复」。⚠️ plugin/*.mjs 只需 toggle(无需重启 DSH);重启也解决不了这个问题 |
| v0.1.19 | 手动压缩不误报「引擎抢先」+ 压缩后投影偏高会标注 | 真机手动 /compact 成功后:① 记录被写成「手动压缩(/compact) · 引擎抢先…」——用户主动压的,没有"抢先"(preempted 没判 kind,已修);② 表盘 13.1% 而当前 surface 只剩 6,185(真实约 2.4%)——投影公式里旧 provider 报的"非 surface 开销"(125K)压缩后没同步缩小,要等 provider 实报才回落。方向偏高=保守,故只标注:surface 缩水 >50% 时用量行附「provider 待刷新」。另附真机四口径前后对比(消息 221,980 → 4,062) |
| v0.1.18 | 非插件压缩的失败原因可见(/compact 失败不再只说"失败") | 真机:同一会话连试 3 次 /compact 全失败(DSH 码 summary),我们记到了 3 条但只有「失败」两个字——原因只存内存、且这条路径不触发报告快照。现把 DSH 失败码翻成中文短语写进面板行(失败(摘要无法生成))与记录(error + reason),并在记录后立刻落一次报告快照;认不出码则截断原文照抄(不编造)。⚠️ 我们自己的压缩用同一个官方摘要后端 ⇒ summary 失败也可能打到我们,但会明确回报「未执行 + 原因」 |
| v0.1.17 | 决策行节奏:每轮开头一次 + 中途变更再一次 | 长任务里同一轮的每个步骤都出现 档位决策:… + 压缩决策:不压|占用 ~57%|理由…(模型把每步都当成"阶段变化")。现两处文案写明一轮只写一次,中途真的改了决定(决定压缩 / 升或降档)再补一行,其余步骤不要重复。⚠️ 我第一版把卡改成稀疏(首次/涨5pp/隔20轮),与用户口径不符已撤销——卡照旧每轮一张(它是每轮决策的提醒位),改的只是决策行作用域 |
| v0.1.16 | 压缩回执显式消歧:档位名里的百分比 ≠ 占用 | 回执原先虽已报「压缩后占用 ≈51%(510k)」,但同句仍有「浅压24% 档」「保留 ~240k」两个含 24 的数字 ⇒ 模型仍可能挑错那个数(真机先例:写出「回到 ~24%」)。现明写「档位名里的百分比是保留预算比例、不是占用」,「保留 ~240k」改为「保留预算 ~240k」,并预告「provider 下次实报后可能略降」(该数是压缩那一刻的 provider 锚定投影,真机 51% → 实报后 40%);每轮用量行的 used 始终与表盘同口径 |
| v0.1.15 | 报告新增「占用账本」+ 口径公式单源 | 每个会话记 contextLedger:表盘口径(provider 实报 / 采样 surface / 计算投影 / 来源标注)+ 启发式口径(日志总量 / surface / 三行分项 system·tools·messages)+ 两口径之差 ⇒ 「表盘 408K 而三行 272K,差的 136K 是什么」直接从 m1-report.json 读(真机样例:748806+247859−487049=509616,三行 2161+200000+58256=260417,差 249,199)。公式实现唯一一份在 core.mjs(命名导出给测试 + 注入 M1/M3),m3 里出现第二份即被守卫断言抓红 |
| v0.1.14 | R24.1:自算 DSH 投影公式(修我上一版的真缺陷) | v0.1.13 直接读 projectedTokens,但它只存在于 DSH 发给客户端的 view,插件读投影 state 拿到 undefined ⇒ 退回过期的 provider 实报值(真机压缩后仍是 748,806/74.9%,而表盘已是 51%→40%,比改之前更糟)。现照抄 DSH 源码自算:projected = max(0, pressureTokens + surfaceTokens − sampledSurfaceTokens),真机配对验证 509,616(51%) ✓ |
| v0.1.13 | 占用口径对齐 DSH 表盘 | 真机反馈「压缩时 DSH 显示 75%、压完 40%,插件却说 ~24%」——查下来 DSH 有三套口径(表盘 = provider 锚定的投影;弹窗三行 = 启发式组成,含被取代的提示且不含图片/文件路由定价;measure().totalTokens = 又一码事)。现将 used 对齐表盘并从 v0.1.14 起自算投影、标明来源,回执直接念「压缩后占用 ≈41%(408k)」 |
| v0.1.12 | 锚点来源中文描述 + 思考强度「低(low)」 | 修两处显示:① 压缩回执把内部 token recent-user/text 直接给模型 ⇒ 改为「语义锚点(从最近第 1 条你的消息起)」/「(按文本「…」定位)」;② 思考强度的用量行后缀、一次性教学、错误文案、HUD tooltip 全是裸英文 id ⇒ 统一为 低(low) / 高(high) / 最高(max),且入参兼容回填(照抄显示名也能生效)。⚠️ 本次含 client.js 变更 ⇒ 除 toggle 外需刷新页面 |
| v0.1.11 | 档位说法全中文(英文 id 清零) | 审计出 3 条运行期泄漏:工具返回值 {"tier":"heavy"}、回执与 HUD 行的异常兜底会退回英文 id;另有 1 个隐患——canonicalTier 不认卡片上的「浅压24%」⇒ 模型照卡片回填会静默退回标准16%。现对外一律中文标签、内部与持久化仍用 id(兼容不变),并新增「模型可见文本扫描」断言(含"范围域未加载"的兜底路径) |
| v0.1.10 | R21.2 真机测试发现并修掉的两处 | ① 不传 tier 会重置档位(卡片/文档都写"沿用",代码却写回 标准16% 并落盘)⇒ 改成真沿用;② 锚点未命中时回执不再丢降级痕迹("无需压缩"与"官方兜底"两条 return 路径补齐 retainSource/anchorFailReason)。两处都是真机跑出来的,各带行为级回归断言与变异验证 |
| v0.1.9 | R21.1 紧急修复:工具静默不注册 | v0.1.8 的 keepFrom 是 type:'object' 却没写 additionalProperties ⇒ defineTool 拒收 ⇒ 整个 compact_context 不注册(模型调不了,教学还在喊它;强制线自动压缩不受影响)。已修 + 新增「工具 spec 结构化自检」断言(桩绕不过校验层) |
| v0.1.8 | 语义锚点保留 keepFrom | 压缩不再只能"按比例留":可以让模型指位置——keepFrom:{recentUser:1}(从最近第 1 条你的消息起)或 {text:'开头十来字'},插件据此算预算,并保证锚点之后一律原样保留。典型收益:600k 里新需求只占 6% 时,从"留 240k(白留旧内容)"变成"留 ~44k" |
| v0.1.7 | 决策卡「对称化」+ 越线反向建议 | 卡片原先只给"多留的好处" ⇒ 等于在劝模型选浅压24%。现两个方向都给数字(丢多少 / 压后占用 / 距线轮数),越线时明确建议选深压8% 或 标准16%(越线选浅压会"压了等于没压") |
| v0.1.6 | 档位说法改中文 | 三档统一为 深压8% / 标准16% / 浅压24%(百分比由常量推导,不写死);英文 id 退为内部/兼容值,tier 中英都能传,老会话与落盘档位零迁移 |
更早的更新(R18 单档模型零注入、R17 改档轮首知情、R16 档位与教学去重、R5 保留范围自选…)见 docs/milestones.md。
1. 它做什么
插件只做三件事:把读数讲清(窗口 / 已用 / 占比 + 达标时的决策卡)、把选项给足(三档 + 语义锚点)、 把执行做对(按选定预算自选保留范围,边界交引擎校验)。"何时压、留多少"由模型决定——它才看得到任务形态。
每轮任务前 → 读上下文用量(tokenMeter.measure)→ 注入「窗口 / 已用 / 占比」到 prompt
| 档位(R16):深压8% / 标准16%(默认)/ 浅压24%;锚点(R21):keepFrom(更精确的"指位置")
→ 占用达「智能压缩线」时附决策卡(当前档位 + 两个方向的数字 + 趋势)
→ 模型【先在正文说一句理由,再】调用 compact_context(可选 tier / keepFrom)
→ 下一步的 pre-step 执行压缩:按锚点或档位算预算 → 自选范围 → compactRegion
→ 之后所有步骤跑在压缩后的上下文上(本轮不中断、不注入消息、不伪造发言)
→ 占用达「强制压缩线」时插件在 pre-step 无条件发起压缩(同一条预算/范围路径)
可选(智能思考开关):读当前模型档位 → 注入给模型 → 模型【先说理由,再】调 set_reasoning_effort
1.1 兜底链:当前实现的全部降级路径
总原则——失败极性统一:凡「信息不足 / 能力不可用」一律保守:宁可多留、多花 token、少做动作; 且绝不静默——每一跳都写进报告字段与「压缩自检」回执(
anchorProbe/rangeProbe/rangeSource/effortGate…)。
| 环节 | 主路径 | 一级兜底 | 二级兜底 | 最后兜底 |
|---|---|---|---|---|
| 保留预算(R21+R16) | keepFrom:{text:'…'}(前缀→包含定位) | keepFrom:{recentUser:N}(最近第 N 条你的消息) | tier(本次传入 / 会话已登记) | 标准16%(从未登记);每跳记 tried/reason |
| 保留范围(R5) | selectRange 按预算从尾部回退(+keepFromSeq 护栏:绝不越过锚点) | 末端边界不合法 ⇒ prevEnd 逐节点回退(≤64 次) | 仍不合法 ⇒ 官方 compactIfNeeded('context-overflow') | 判定「没什么可压」且未越线 ⇒ 什么都不压(source:none) |
| 压完仍越线(R16) | 自算压完即收工 | 仍 ≥ 强制线 ⇒ 追加官方 overflow 收口(rangeSource=own+official) | — | 记录 officialWhy(如实标注是官方手段) |
| 档位/路由读数(R1/R17) | 投影 pending(用户刚改的模型/档位) | requestHeader(上次请求的已生效值) | 两者都没有 ⇒ 取模型默认档并标 adapterDefault | 读不到 ⇒ 不注入(不编造档位),记 effortDiag.error |
| 换档请求(R1/R3) | 合法 ⇒ 写期望档,下一步生效 | 不在可选集 ⇒ 工具拒绝并回可选集 | 他人已显式指定 ⇒ 让位(不覆盖别人的选择) | 内部异常 ⇒ 吞成结构化结果(绝不抛错、不中断本轮) |
| 工具存在性(R18) | 全局注册 + 按 agent 可见 | 单档模型 ⇒ restrict 对该会话隐藏 | restrict 不可用 / 拿不到回滚句柄 ⇒ 不隐藏(宁可多花 ~417 字符/请求) | 开关关闭 ⇒ 撤回注册(模型清单里没有它) |
| 可切性判据/收敛(R18) | 每步算 canSwitch,按路由变化收敛 | 读不到路由/可选集 ⇒ 不抑制(保守:继续注入 + 保留工具) | 路由键未变 ⇒ 走快路径跳过重算(零异步开销) | 异常 ⇒ 保持现状 + 留痕(effortReconcileError) |
| 模块加载(R10/M3) | 域模块懒加载 + 空值降级 | 范围模块未就绪 ⇒ 退官方路径 | M3 未就绪 ⇒ 本轮跳过(m3-module-missing 留痕) | defineTool 未就绪 ⇒ 工具不注册,下一步重试 |
| 档位/锚点持久化(R16.16/R21) | 与档位一起落盘 compact-tier.json | 写失败 ⇒ 内存仍生效 + 日志 | 热换/重启 ⇒ 落盘恢复(tierState=restored) | 文件损坏 ⇒ 按未登记处理(标准16%) |
| 开关关闭(R18.2) | 「智能思考」关 ⇒ 不注入、不注册 | 已注册过 ⇒ 撤回(成功支) | 撤回不可用(无 disposer)⇒ 留痕 + per-agent 限制兜底 | 「智能压缩」关 ⇒ 回到原生 DSH(不注入、不压缩、不注册) |
| 跨热换残留(R18) | 限制的 disposer 存进程级注册表 | 激活时清扫(released≥1) | 开关翻转时再清扫 | 拿不到回滚句柄 ⇒ 当初就不隐藏(失败极性) |
1.2 关键机制:各自解决什么问题
| 改进 | 内容 | 为什么重要 |
|---|---|---|
| 压缩改「工具触发」(R4) | 模型调用 compact_context ⇒ 登记意图 ⇒ 下一步 pre-step 执行 ⇒ 本轮无缝继续 | 彻底删掉「伪造用户消息拉起会话」那套(maybeResumeAfterMarker / sessionController.prompt / agent.followup 在代码里零出现)。压缩与「叫醒」解耦:工具调用本身就保证有下一步,不需要叫醒 |
| 保留范围「自己算」(R5+R16) | 保留预算由会话档位决定(R16 三档,见下一行)⇒ 调公开方法 compactRegion;边界不合法则逐节点回退;失败或压完仍越线才沿用官方 overflow 兜底 | 引擎 context-overflow 写死 retainTokens = 0:真机实测在只有 53% 占用时把 surface 从 1117 节点 / 367,794 token 砍到 4 节点 / 8,603 token(影子化 97.7%)。现在近端细节留得住,且整段没超预算时什么都不会压 |
| 语义锚点保留(R21) | keepFrom:{recentUser:1|2|3}(从最近第 N 条你的消息起)或 {text:'开头十来字'}(按文本定位)。模型指位置、插件算预算;预算 = 锚点→末尾 token + 余量,且 keepFromSeq 护栏保证范围绝不越过锚点 | 三档只能按比例留,而常见形态是「旧需求占地方、新需求只在最后几个百分点」⇒ 白留旧内容(浪费 + 降智源)。降级链 text → recentUser → 三档 → 标准16%,每跳都留痕 |
| 压缩档位(R16) | compact_context 可选参数 tier:深压8% / 标准16%(默认)/ 浅压24%;会话级持续生效(含强制线,未选 = 标准16% 兜底);范围计算/边界保护三档完全一致,夹取 [40k, 窗口×50%] | 把「不可靠的自由度」换成「有界的选择」:模型只在语义档位里挑,不接触会「切坏 tool 对」的底层参数;选档理由照旧写在正文(先说再调用);回执带档位与保留预算,Agent 能确认选档真的生效 |
| 换档改「工具」(R2/R3) | 模型调用 set_reasoning_effort ⇒ 下一步请求即用新档位 | 替代「写文本标记、等用户再发一条消息才生效」——旧方案要求用户参与才能生效 |
| 「先说,再调用」(R6) | 两个工具的 description + 一次性说明 + 决策卡都要求:先在回复正文里用一句话说明理由,再调用工具 | 工具调用对用户是静默的(只显示「调用了某工具」)。理由写进正文,用户才看得出为什么压、为什么换档 |
| 强制线恒低于引擎 5pp | 生效值 = min(配置, 引擎阈值 − 5pp)(内置 80% ⇒ 75%) | 两线相等时谁先命中取决于各自测量时机,插件可能「什么都没做、占用却降了」⇒ HUD 记录失真 |
| 全程留痕 | 报告记录 rangeProbe(保留预算/起止/影子 token)/ rangeSource / compactTool / preStepErrors / lastPreStepError … | 链路任一环静默失败都能定位——已靠它当场抓出「工具从未注册成功」「面板整卡崩掉」等真 bug |
| 退役零残留(R7) | marker 触发通道 + M5.5「挂起-自动恢复」通道整体清零(host/client/schema/wire/tests 五层成对删除);6 个「静默降级」真缺陷修复;无界状态有界化;CardBoundary 改为真正的 React 错误边界 | 那条通道在 R6 之后已永远不会命中(三处教学都改工具版),却仍在四层之间成对存在;而四个真缺陷的共同形态是「看起来成功了,其实什么都没发生」(工具回成功但意图丢了 / 冷却扣了但没压 / 字段算了但没人读 / 教学 catch 了但没日志)。审查报告:docs/r7-review-and-hardening.md |
要解决的问题:长会话上下文压力不可见;原生自动压缩(80%)只看占用、不看任务—— 重任务需要全上下文,轻任务其实可以先压缩腾空间。这个判断只有模型能做,插件的职责是 把读数、选项、执行三件事做好。
智能思考解决的问题:同一个模型的不同思考档位(off/low/high/max…)成本与质量差异显著, 但用户很难预判每个任务该用哪档;而模型自己看得到任务复杂度。插件把「当前档位 + 可选档」 告知模型,由它自己决定升档/降档——简单任务省钱、难题给足推理预算。
产出:弹窗 HUD(最近压缩记录 + 阈值速览 + 两个开关)、输入框档位 chip、 配置面板(两线阈值 + 成本计算器)、报告的完整取证字段。
2. 关键概念(读这一节就够)
2.1 两条压缩线
| 面板名 | 配置键 | 含义 |
|---|---|---|
| 智能压缩线 | markerMinRatio | 占用达此值时注入决策卡,模型可自行决定压缩(含选档,见 R16)并自动续跑 |
| 强制压缩线 | criticalRatio | 占用达此值无条件发起压缩(pre-step / idle 兜底;执行路径同上——先按会话档位自算保留范围,官方 overflow 只作兜底/收口;档位由 Agent 事先选定,未选 = 标准16%)。生效值 = min(配置, DSH 引擎阈值 − 5pp),即内置 80% ⇒ 上限 75% |
推荐值(按计费口径,计算器可按实价实时推导):
| DeepSeek API(命中:未命中 = 1:50) | 高缓存价档(1:4 ~ 1:8) | |
|---|---|---|
| 智能压缩线 | 0.30 | 0.15–0.20 |
| 强制压缩线 | 0.85 | 0.80 |
核心原则:不要把阈值调得过低去追命中率——过度压缩导致模型重读文件/重跑命令的返工成本, 远超省下的 token。高缓存价档(命中仍计费)压早更划算;DeepSeek 命中近免费,压晚保信息更好。
2.2 自动压缩:工具触发(零输入、零消息、本轮不中断)
- 模型判断该压缩时,先在回复正文里用一句话说明理由(R6 教学要求),再调用工具
compact_context(可选reason) - 插件登记「下一步压缩」意图并立即返回 → 工具调用让本轮继续到下一步
- 下一步的
agent/pre-step先执行轮内压缩(R5 起:自选保留范围,失败才沿用官方),再组装本步请求 - 之后的步骤都在压缩后的上下文上继续 —— 本轮不中断、不注入任何消息
用户全程零操作,且不存在任何伪造用户发言(旧「标记 + 自动拉起」机制已于 R4 整体删除)。
为什么工具能做到:工具调用 ⇒ 必须回灌 tool result ⇒ 下一步必然存在, 而 pre-step 天然在下一步请求之前。压缩与「叫醒」因此解耦——不需要叫醒。 详见
docs/r4-compaction-tool-design.md。占用达强制压缩线(生效值 = min(配置, DSH 引擎阈值 − 5pp);内置 80% ⇒ 75%)时, 仍由既有 pre-step / idle 安全网自动发起压缩——执行路径与工具触发完全相同 (不是切到 DSH 自带压缩);保留档位同样由 Agent 事先选定的 tier 决定 (R16:会话级持续生效,未选 = 标准16% 兜底);详见下一条。
保留多少近端内容(R5+R16):无论哪条触发,都先按会话档位自算范围 → 只压这一段。 三档:深压8% = 窗口 × 8%(1M ⇒ ~80k)/ 标准16% = 16%(~160k,默认)/ 浅压24% = 24%(~240k);预算被夹取在
[40k, 窗口 × 50%]。 边界不合法则逐节点回退;仍不行或压完还在强制线之上,才沿用官方 overflow 兜底; 整段对话未超预算时什么都不会压。这条规则已教给 Agent(R6/R16), 所以它知道压缩后还剩什么,不必靠猜。 详见docs/r5-retention-range-design.md与docs/milestones.md的 R16 节。「先说,再调用」(R6,用户要求):工具调用对用户静默(只显示「调用了某工具」), 所以两个工具——
compact_context与set_reasoning_effort——的描述、一次性说明、决策卡 都要求先在回复正文里说一句理由,再调用。工具 description 每轮随请求下发 ⇒ 这条提醒是零额外 token 的每轮提醒。
2.2b 智能思考(可选,默认关闭)
开关语义(一个开关管两件事):开 = 注入思考强度信息 + 允许 Agent 换档;关 = 两者都不做。
| 项 | 内容 |
|---|---|
| 注入 | 一次性教学(教工具用法)+ 每轮用量行后缀「| 思考强度 high」;用户手动改档时后缀直接点明「用户刚把档位从 high 调到 max,本步骤即生效」(R17,见下) |
| 触发 | 工具 set_reasoning_effort(参数 effort=<档位>)—— 见下「为什么是工具不是标记」 |
| 执行 seam | agent/request waterfall + prepend: true(见下「为什么必须 prepend」) |
| 不中断 | 只改请求头一个字段:会话历史不动、不插消息、工具链不断 |
| 显示 | 输入框模型选择器左侧 chip「智能思考档位 high · 冷却 23s」——响应式投影,零轮询 |
| 冷却 | 30s(防频繁换档反复打断前缀缓存);chip 上显示剩余秒数 |
为什么是工具而不是文本标记(2026-10-08 用户指出后重构):
原方案是模型在回复末尾写 [cp:effort high] 文本标记,但有两个致命问题:
- 需要用户再发消息——标记写在回复末尾时本轮已结束,档位只在下一轮(用户下次说话)生效
- 若仿压缩"自动拉起"则是伪造用户输入——那是往
inbox.next-turn塞一条假用户消息
工具为什么能解决(dsh-agent-loop L1152-1155 源码实证):
const toolCalls = message.content.filter((b) => b.type === "tool-call");
if (toolCalls.length === 0) return { kind: "completed" }; // 无工具 ⇒ 本轮结束
return concluded ? { kind: "completed" } : null; // 有工具 ⇒ null = 继续下一步
⇒ 工具调用让本轮继续到下一步,而每步都重走 agent/request ⇒ 下一步的请求即带新档位。
全程零用户输入、零伪造消息、同轮内生效。 顺带消除了文本标记的「散文提及误触发」风险
(工具参数结构化,无正则误判面)。
为什么必须 prepend: true(2026-10-08 实测踩坑):
官方 installModelSelection 挂在同一个 agent/request 上且在外层,
它 await next() 后会 delete reasoningEffort 再套回持久化 header 的值
⇒ 内层写入必被剥掉。症状极隐蔽:applied: true 却完全没生效。
故本插件必须 prepend 成为最外层,且 pending 需持久(每轮覆盖回去,不能一次用完就删)。
为什么用 agent/request 而不是 sessionController.selectModel:
后者内部还会 agentDefaultModel.saveSelection()——写全局默认模型(实测会重写 profile)。
Agent 自改档不应改掉新会话的默认档。
档位必须动态取:同一个 model id 在不同 provider 档位不同
(实测 trae/deepseek-v4.1-flash = low/high/xhigh,workbuddy 同名模型 = off/low/high/max),
绝不硬编码档位表。应用前必须校验合法性,否则 dsh-llm 会抛
UNSUPPORTED_REASONING_EFFORT 中断该次请求。
工具侧不抛错——非法档/冷却中返回结构化错误让模型自行纠正(抛错会中断本轮);
agent/request 侧则必须校验并静默忽略(那是请求能否发出的最后一道闸)。
让位他人选择:若当前请求的档位既不是本插件上次写的、也不是本次想写的
(用户手动选档 / subagent 自带 reasoning_effort)⇒ 不覆盖。
用户手动改档,模型怎么知道(R17,2026-10-12 用户实测反馈):
用户改档(UI 选择器 / /model 命令)在 DSH 里只落成 model/selection 事件 ⇒
modelSelection 投影的 pending;官方要到本步骤的 system-prompt/assemble → agent/request
才把它落到请求上,而插件的注入在 agent/pre-step(更早)⇒
那一刻 requestHeader() 还是上一次请求的档位。
故 read() 改为 pending 优先(与 client chip 的 sel.pending || sel.lastUsed 同源),
后缀随之变成:
思考强度 max(**用户刚把档位从 high 调到 max**,本步骤的请求即用它;可选 …)——
模型不用等下一轮就知道用户调了档、调到了哪一档。无 pending 时文案与旧版逐字一致(零额外 token)。
单档模型下完全静默(R18,2026-10-13 用户要求):当本模型排除 Default 后只剩一档(例如只有 high),
或模型明确不支持思考档时,插件不注入任何换档文本(后缀 / 一次性教学 / 换档提醒 / 压缩自检回执里的换档提示
四处全静默),并把换档工具从该会话的模型工具清单里收起(agent 作用域 tools.restrict({deny}))
⇒ 省约 417 字符/请求(工具 schema,最大那笔)+ 60 字符/轮(后缀)。
- 判据是单一真相(
canSwitch):工具可见性与四处文本永远同源,绝不出现「工具已隐藏、文本却还在教你调用它」。 - 未知 ≠ 没有(R7 结论):可选项取不到 / 空数组 / 读取异常时一律不抑制——最坏多花 token,绝不锁死能力。
- 切到多档模型当轮恢复:判据读投影
pending(与 R17 同源),工具可见性在 pre-step 收敛(早于system-prompt/assemble);一次性教学因「只有真注入才标记已讲」而自动重讲一次。 - 稳定性:限制的 disposer 存进进程级注册表(跨插件热换存活)+ 激活/开关翻转时清扫残留; 失败极性铁律——拿不到可回滚句柄就不隐藏(宁可多花 token,绝不把能力锁死)。
- 开关联动(R18 修掉的真实缺口):关闭「智能思考」⇒ 换档工具立刻从模型清单撤回(连 schema 都不再下发);
重新打开 ⇒ 自动重新注册;「智能压缩」同理(关 ⇒
compact_context退出清单)。 此前只停了行为(不注入、执行被拒),工具却仍每请求下发、白付 schema。
真机 E2E(2026-10-13 实测,带数值):
| 场景 | 实测 |
|---|---|
单档 workbuddy/hy4-preview-f | 可见工具 73、清单里无 set_reasoning_effort;用量行后缀与一次性教学均不注入 |
切回多档 deepseek-account/deepseek-flash | 可见工具 74;后缀 + 教学当轮恢复 |
| 单档下 toggle 插件 | 激活期清扫 released=1 ⇒ 跨热换不留残(杜绝「切多档后工具永远不见」) |
| 关闭「智能思考」 | Inspect 清单里该工具 0 命中(compact_context 仍在 ⇒ 精确收敛,非一刀切) |
成本:一次性教学 ≈120 token + 每轮后缀 ≈7 token。
⚠️ 换档可能使前缀缓存失效(call-config.js 注明 effort 属 cache-affecting 状态)——
这是换档的真实代价,故设 30s 冷却并建议一次任务 1–2 次(已写进注入的教学文本)。
已实测(2026-10-08):工具注册成功(effortTool.ok=true, via=tools.register)+ 注入改写为工具版本。
调查与设计全文:
docs/r1-reasoning-effort-investigation.md|docs/r1-effort-design.md
恢复投递走三通道,当前实际生效顺序(2026-10-07 实证):
| 通道 | 方式 | 状态 |
|---|---|---|
| A | sessionController.prompt(request, AbortSignal.timeout(30s)) | ✅ 当前首选({"accepted":true},一次命中) |
| B | ctx.remote.session.prompt 代理 | ❌ 不可用(插件 ctx 无 remote inject 权限) |
| C | createUserMessage + agent.followup(官方同原语) | ✅ 兜底(早期历史走此路) |
通道 A 早期因缺 AbortSignal 第二参被拒(官方
prompt()首行throwIfAborted)—— 补参后即通。这印证了「官方契约的参数一个都不能少」。
2.3 三个可改动点
| 改什么 | 生效方式 |
|---|---|
| 面板配置(两条线、有效期、换档冷却等) | 即时生效(host 活读 config,无需重启) |
host.impl.mjs / effort.mjs / compact-tool.mjs / compact-range.mjs / threshold.mjs / m3.compact.mjs / m5.hud.mjs / m2.inject.mjs / m1.snapshot.mjs | plugin toggle 热换(?ts= 动态 import) |
client.js | 刷新页面 |
plugin-config.schema.mjs / wire.host.mjs / entry.mjs | 需要重启 DSH(entry 由 cordis loader 经 ESM 缓存加载) |
core.mjs | 需要重启 DSH:它必须由 entry.mjs 用同一个 ?ts= 预加载后传给 apply(state/log/svc/schedule 在 apply 期同步使用,而动态 import 是异步的)。未重启时旧 entry 不传 core ⇒ 宿主降级但不崩(打一条 warn、不接线),见 core.mjs 文件头与 boot 冒烟的同名断言 |
R3(2026-10-08)起智能思考功能域独立为
plugin/effort.mjs:host 用import(\./effort.mjs?ts=${implTs}`)加载(ts 取自 impl 自身),**与 impl 同批热换**;static.mjs§4/§5 的依赖方向与「动态 import 必须带?ts=」护栏自动覆盖它。 R5(2026-10-08)同款加载plugin/compact-range.mjs(保留范围自选,纯函数叶子)。 ⚠️ **压缩档位是模块常量表TIERS**(R16:深压8% = 0.08 / 标准16% = 0.16(引擎默认)/ 浅压24% = 0.24), 由 **Agent 经compact_context(tier=…)选择**(会话级持续生效,含强制线;未选 = 标准16%), 不在面板配置。要改成面板可调,需给plugin-config.schema.mjs` 加字段 + 重启 DSH——未做,等用户确认。
详见 docs/setup.md。
3. 当前状态
已完成:M1–M5(用量读取 / 每轮注入 / 会话自说明 / HUD 弹窗 / 历史持久化)、R1–R21
(智能思考 + 换档工具、压缩改工具触发、保留范围自算、全面审查加固、压缩档位三选一、
工具后自检回执、回执按会话隔离、用户手动改档的轮首知情、单档模型零注入 + 工具静默)。9 个域模块 + core,host.impl.mjs 1836 → 518 行。
测试:contract 597 + static 72 + report 54 + boot 21 = 744 断言全绿;关键回归均有变异验证。
逐里程碑台账(含踩坑与实测数值):docs/milestones.md;
早期进度历史存档:docs/status-archive.md。
4. 测试与取证
4.1 测试(npm test,<1 秒)
npm test # 跑全部四个套件
npm run test:contract # 只跑契约对账
npm run test:static # 只跑静态约束
npm run test:report # 只跑报告形状
| 套件 | 断言数 | 查什么 | 能抓到什么 |
|---|---|---|---|
contract.mjs | 597 | face 方法表 ↔ client 描述符 ↔ TYPERT 三端对账;Config 字段在 FIELDS/schema/M3_DEFAULTS 三处齐全且默认值值级一致;退役字段未复活;mergeConfig 读取集 ↔ schema;getHud 作用域契约(防 ReferenceError 回归);弹窗/阈值行排版契约(状态提示独立成行,防窄弹窗逐字断开);智能思考 UI 契约(开关尺寸/右对齐/字段三端/chip 两态色 token);压缩工具化契约(工具注册/意图 TTL/消费顺序/伪造恢复零残留);阈值余量契约(引擎阈值 −5pp 三处同源 / 阈值核心已抽为叶子模块且 host 不再重复定义 / 未就绪时空值降级 / 面板自取上限 / 输入即时钳制 / 已存超限自动钳回保存 / 教学传生效值);组件作用域对账(防跨组件引用面板 state ⇒ 整卡崩掉,node --check 的盲区);R5 保留范围契约(RETAIN_RATIO 与引擎 DEFAULT_RETAIN_RATIO 同源 / 起点跳过 system/message / 走 session.eventAt 而非猜 / 真跑纯函数验证预算-范围-影子量-一致性拒绝-回退上限 / 引擎错误文案逐字对账 + 边界错误分类 / 官方兜底与强制线收口接线 / 留痕);R6 教学契约(实例化两个模块真跑 renderBrief/renderCard:先说再调用的「显著标注」与「具体做法」分两半钉死 / 两个工具 description 也要求 / 参数说明也要求 / 「自己算范围」按活值渲染 / 拿不到活值时不编数字 / host 活值接线);R7 加固契约(退役零残留 4 类 × 4 文件剥注释判定 / 保留项 markerMinRatio 未被误删 / 随会话增长的 Set 已有界化 / 意图表真跑 runTool:登记→pending()→跨 sid 隔离→消费 / 全表 TTL 清扫 / 空可选集不否决档位 / 顺序契约(F1 冷却在成功分支 / F5 消费在服务确认之后 / F2 可抛语句前移)/ 真错误边界 / occupancy 搬运 / alive 守卫计数);R8/R9/R10 解耦契约(阈值核心与 M3 压缩域(执行核心 + 编排层)已抽为叶子模块 / 宿主不得把 api.RETAIN_RATIO、api.selectRange(、state.m3.lastPreStep、sweepInFlight 抄回来(查独有产物而非函数名,防改名躲过)/ 跨域回调(publishHud/recordHudAct/formatAct/clearBriefed/schedule)必须显式注入 / 未就绪时空值降级并留痕 / 模块对宿主内部件零 import);智能思考 8 条实证结论(prepend 最外层 / pending 持久 / 冷却基准 / 计数语义 / 两侧校验 / agent.ctx / sid 现读 / 删 maxTokens);R17 用户手动改档的「轮首知情」(读 modelSelection 投影的 pending,而非上一次请求的 header;行为级覆盖改档/降级/兜底五条路径) | 调用静默失败(P29:三端漂移不报错、只是拿不到数据);已实测抓过 7 个真 bug |
static.mjs | 72 | 真实 node --check(含 docs/reference/tools/*.cjs);client 自足(无外部 import,require 仅 react);entry 薄壳(<40 行、有静态 Config、动态 import 带 ?ts=);模块依赖方向无环;热换纪律;功能域叶子模块清单(effort / compact-tool / compact-range / threshold / m3.compact / m5.hud / m2.inject);调试残留扫描(tools 下的 console.log 属 CLI 正常输出,豁免);上架基础项(根清单可被市场识别 / Bundle patch 指向存在的公开 patch / 包名↔patch name 一致 / 版本一致 / README 有安装命令 / LICENSE 与 license 字段一致) | 语法错误、破坏热换、client 引入依赖、循环依赖、TODO/XXX/console.log 残留、上架基础项漂移(改了清单名/schema 而 patch 或 README 没跟上) |
report.mjs | 54 | 产出侧字段契约;republishFromReport(原 bfOnce)回填链依赖;C3② 关键事件必须走 full 档(含满环优先淘汰 slim,防 FULL 取证被高频事件挤出);getHud 作用域与 criticalCap 实参契约;调查结论留档(探针退役后结论不得丢);真实报告结构自洽(无嵌套条件断言 ⇒ 断言数不得随数据漂移);hud-acts.json 去重;dump 工具可跑 | 报告形状无声破坏(踩过 2 次:顶层读 m3/m5、脚本读已删字段);证据静默丢失(120 条全 slim、FULL 被挤光而断言全绿) |
boot.mjs | 21 | 宿主启动冒烟:真跑 apply(ctx, {}, {pluginDir, reportPath})(桩 ctx)→ HUD 面注册 → 真调 getHud(null) 必须 ok:true → criticalCap/occupancy/effortEnabled 三线 → 四个监听器 → 真调 pre-step handler(abort 与 step=1 两次)→ M2 注入路径留下记账(m2.skips 有键或 injections>0)→ 有历史时 m5.lastPublish==='ok' → 报告落盘。副作用边界:报告写临时目录、hud-acts.json 只读不写 | 接线不可达(R11 真事故:接线块被嵌进 schedule() 函数体、语法合法、423 条源断言全绿,压缩与弹窗全链路静默失效)——node --check 与源断言都看不见「代码在不在正确的函数里」 |
合计 744 条断言(四套:contract / static / report / boot)。
已验证有效:注入 3 个人为 bug(m3-act 误入精简档 / client face 改名 / host 引用 client.js),
三套件全部抓到且定位精准。新增断言均实测验证过「对回归确实失败」(两边都通过的测试等于没测):
- R5 换了 9 个针对性变异(保留比例 / 起点跳系统头 / 一致性校验 / 回退上限 / 错误分类 / 兜底分支 /
预算守卫 / measure 传递 / 强制线收口)逐个注入,9/9 被捕获——其中「去掉回退上限」第一次逃逸,
说明原断言只匹配了
return official('boundary-exhausted')而没钉住MAX_WALK_BACK,已加固。 - R6 换了 11 个(教学标注 / 教学做法 / 卡片做法 / 两个工具 description / 参数说明 / 写死比例 / 卡片保留行 / host 活值接线)——11/11 被捕获。两次逃逸都极有教育意义: ① 「先说再调用」最初用**「或」关系的单个正则**,去掉显著标注后同段的具体做法仍能匹配 ⇒ 漏网, 现改为**「显著标注」与「具体做法」分两半钉死**; ② 变异脚本把锚点打在了同文件的注释上(注释里也写了那个词)⇒ 代码没被改,看起来"逃逸"。
4.2 取证工具
npm run dump -- --tail 20 # 报告概览 + 尾部条目
npm run dump -- --kind m3-act # 按 reason 过滤
npm run dump -- --field m3.eff # 取最近一条的某字段
npm run dump -- --history-acts # 压缩历史(含 hud-acts.json)
npm run asar -- list --filter dsh-token-meter
npm run asar -- grep --pattern compactIfNeeded --ext js --ctx 3
# 会话存储逐帧取证(压缩是否真发生 / 阴影了哪些 seq / 系统头是否被保留)
node docs/reference/tools/session-records.cjs --find <会话 id 片段> --compaction
node docs/reference/tools/session-records.cjs --find <会话 id 片段> --shadowed 15513
node docs/reference/tools/session-records.cjs --find <会话 id 片段> --from 18300 --type 'compaction/*'
⚠️ 路径约定:报告的
m3/m5/m55块在每个 history 条目上,不在顶层(report.mjs已断言此约定)。 ⚠️session-records.cjs读的是 DSH 的私有存储格式(多帧 zstd + JSONL):直接扫魔数有假阳性, 必须 try/catch 跳过;官方改格式时该工具需同步。它是本项目「不靠插件自证」的关键手段—— R5 就是靠它统计 12 次压缩的shadowedSeqs,才证明system/message15513 从未被阴影化。
5. 成本模型与推荐配置
结论(推导与官方单价取证见 docs/cost-model.md):缓存命中率会下降
(每轮注入约 300–500 token 未命中;压缩重置前缀),但绝对费用两种计费口径都降——
按量付费约 10–30%(收益来自避免超大上下文的天价 miss)、订阅套餐约 40–70%(命中仍计费,
压小直接换额度)。「压得勤更贵」是错的:摊销成本 ≈ 新增内容 × 未命中价,与阈值几乎无关。
同一组阈值在高缓存价档(1:4)的钱效约为 DeepSeek(1:50)的 3.8 倍。
6. 配置面速查
| 面板字段 | 配置键 | 默认 | 说明 |
|---|---|---|---|
| 总开关 | enabled | true | 关闭后完全恢复原生 DSH(面板为开关滑块) |
| 智能思考 | effortEnabled | false | 开=暴露思考档位 + 注册 set_reasoning_effort 工具;关=提示词不注入、工具也不注册 |
| 换档冷却(秒) | effortCooldownMs | 30 | 两次换档的最小间隔(存储为 ms)。换档会使前缀缓存失效 ⇒ 计费敏感,故可配(R3-S7) |
| 智能压缩线 | markerMinRatio | 0.2 | 占用达此值时注入决策卡,模型可自行决定压缩(决策卡门槛,与已退役的文本标记无关) |
| 强制压缩线 | criticalRatio | 0.85 | 占用达此值无条件发起压缩(先按会话档位自算保留范围,官方 overflow 兜底/收口);生效值 = min(配置, 引擎阈值 − 5pp)(内置 80% ⇒ 75%) |
| 强制压缩冷却(秒) | sweepMinIntervalMs | 600 | 两次强制压缩的最小间隔(存储为 ms) |
| (无面板字段) | — | — | 压缩档位(R16):tier = 深压8% / 标准16% / 浅压24%(会话级、含强制线,夹 [40k, 窗口×50%]);语义锚点(R21):keepFrom 指位置、锚点之后原样保留。两者都取代引擎的 retainTokens = 0(R5)。详见 R5 / R21 设计文档 |
R7 删除的两个字段:
marker(压缩标记)与armedTtlMs(标记有效期)——随 marker 通道整体退役。 压缩改由工具compact_context在轮内触发,回复尾行文本标记既无教学也无执行路径。 旧配置里的残留键会被静默忽略(不报错、不迁移)。
智能思考换档工具:set_reasoning_effort(参数 effort=<档位>)——工具名固定,
档位取值由当前模型动态决定(插件注入时会告知可选档)。不设独立配置键(无需用户填)。
R1 曾用文本标记
[cp:effort <档>],因「需用户再发消息才生效」于 R2 改为工具(用户决定,不保留标记)。
历史版本曾有的
policyCardMinRatio(决策卡门槛)、highRatio(审计参考线)、lightTaskChars(轻任务阈值)已删除——前者并入markerMinRatio,后两者在决策主体 移交模型后已无触发作用。旧配置残留键会被静默忽略(不报错、不迁移)。
7. 已知设计风险(开发前必读)
- 激活安全:插件
apply()内任何异常只记录不抛(DSH 纪律:抛 = 条目装载失败)。 - CJK 计数低估:占用比是估计值(无 provider usage 时按 4 字符/token 启发式), 决策阈值要留余量。
compactNow约束:必须传 Agent 本体 + 必须 idle + signal 必传 (官方实现首行signal.throwIfAborted())。- 弹窗 DOM 注入的脆弱性:弹窗是宿主内部组件、无官方插槽,靠 aria 属性定位, DSH 升级可能失效——失效只是开关/HUD 行不见了,注入与压缩功能不受影响。
- 热换语义:
ctx.effect(execute)的execute立即执行=setup,返回值才是卸载器 (曾因此误用导致心跳从未运行 + 卸载零清理,见docs/milestones.md)。 - client 协议变更需重启:
wire.host.mjs的 face 声明与 TYPERT 走包 exports, 改协议(增删参数)必须重启 DSH 一次。
8. 文档索引
| 文档 | 内容 |
|---|---|
docs/setup.md | 环境事实、composition 真相(not-bundle 坑)、部署与热换矩阵 |
docs/milestones.md | 逐里程碑台账(M1 → 现在,含踩坑与实测数值) |
docs/status-archive.md | 早期进度历史存档(原 README §3) |
docs/cost-model.md | 成本模型全文(官方单价 / 缓存命中率 / 阈值弹性 / 计算器) |
docs/api-notes.md | 已验证的官方 API + 修正记录 |
docs/cross-repo-sync.md | 与 dsh-browser-kit 的同步清单 |
docs/r1*–r7*.md | 各里程碑的设计/审查文档(R1 智能思考调查与设计、R2 换档改工具、R3 解耦、R4 压缩工具化、R5 保留范围、R7 审查加固) |
docs/r18-single-tier-suppression-design.md · docs/r21-semantic-anchor-design.md | R18 单档模型「零注入 + 工具静默」、R21 语义锚点(keepFrom:模型指位置、插件算预算)的设计文档 |
docs/compaction-failure-diagnosis.md | 压缩全线失效定位(provider×model 锐化) |
docs/d1-module-split-brief.md | D1 模块切分任务书 |
docs/reference/README.md | 第三方(DSH 官方)抽取材料来源与许可 + 统一取证工具 |
review-brief.md / review-findings.md | 只读审查任务书与报告 |
运行期取证:plugin/.data/m1-report.json(全字段报告)、plugin/.data/hud-acts.json(压缩历史)。
node docs/reference/tools/dump-report.cjs # 报告概览 / --tail N / --kind m3-act / --field m3.eff
node docs/reference/tools/asar-query.cjs grep --pattern compactIfNeeded --ext js --ctx 3
9. 验收标准
- 每轮 prompt 含实时用量三元组(上限/已用/占比),模型可正确复述;
- 占用达智能压缩线时,模型可调用
compact_context触发压缩(可选 tier 选档),插件在下一步自动执行(用户零重发); - 占用达强制压缩线时,插件在 pre-step / idle 无条件发起压缩——保留范围按会话档位自算 (未选 = 标准16% 兜底;官方 overflow 只作兜底/收口),压缩事件出现在会话日志;
- 两条线可经 Config 配置(面板按秒/比率显示),改后即时生效(活读,无需重启);
- 插件任何异常不影响 DSH 主流程(激活安全零抛)。
- 智能思考(开启时):注入当前档位 + 可选档;Agent 调用工具
set_reasoning_effort可自主换档,本次任务内立即生效、任务不中断、无需用户操作;关闭时提示词不注入、工具不注册。