DeepSeek Harness Plugin Hub

Publish and manage complete Harness Profiles. Discover Plugins for your next setup.

Explore

PluginsPresetsDocsNews

Community

Publish a pluginContactReport an issue

Resources

Plugin Hub on GitHubDeepSeek HarnessSystem statusPrivacy notice
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

Independent and unofficial. Not affiliated with, authorized by, or endorsed by DeepSeek.

DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
C

dsh-cache-control

Cache Control

DSH Desktop session policies: persistent rule injection (session guidelines / ponytail / output shape), on-demand review, tool output compression and retrieval, and conversation appearance and storage management. See README for migration and scope of effect.

The plugin will be installed here. Keep web if you are unsure.

npx -y @deepseek-ai/dsh plugin --profile web add github:Raylen-berry/dsh-cache-control#6c830d9c0e76add87312ed8389f2426dbd895018
READMECompatibilityVersions

Compatibility and provenance

Cache Control is published as dsh-cache-control and currently resolves to version 1.16.20. The Hub verifies its manifest and preserves the exact installation source for reproducible installs.

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
10/6/2026

Versions

1.16.20stable
10/6/2026
1.16.0stable
9/29/2026
1.13.2stable
9/24/2026
Show 9 more versionsCollapse versions
1.12.4stable
9/23/2026
1.12.0stable
9/20/2026
1.11.1stable
9/20/2026
1.9.7stable
9/20/2026
1.9.1stable
9/17/2026
1.8.0stable
9/15/2026
1.6.2stable
9/14/2026
1.5.0stable
9/12/2026
1.4.2stable
9/9/2026

Related plugins

Loading related plugins…

Latest
1.16.20
DSH
*
HMR
Process restart
Tree shaking
Safe tree shaking not declared
Unpacked size
Unavailable
Files
Unavailable
Surface
web
License
MIT
Source
github
GitHub
★ 0
Weekly downloads
0
Last push
10/11/2026
View source ↗
README badge

Click the badge to copy Markdown for your README.

Do you maintain this Plugin?Claim benefit · Priority security scan

Verify the GitHub repository declared in package.json to manage this listing. After you claim it, Hub will prioritize a security scan of the current version and publish the result when it passes.

Claim this Plugin →
Report an issue

Related plugins

More verified plugins in security-access.

Experimental Auto Review@deepseek-ai/dsh-experimental-auto-reviewPer-tool LLM authorization review for the DeepSeek Harness Auto permission presetCc Safety Netcc-safety-netA coding agent CLI hook - block destructive commands and secret file accessDoctor@linxin666/dsh-doctorTransactional rescue mode for DSH profiles with a supervised launcher, isolated recovery capsule, deterministic repairs, health monitoring, and a local Web recovery consolePocketdsh-pocketPut DeepSeek Harness in your pocket: one package, one settings page, and scan a QR code on your phone to access DSH on your computer in sync (LAN + public network, real-time screen mirroring).

README

dsh-cache-control(会话策略:省 token / 会话守则 / ponytail / 输出形状 / 自动审查 / 气泡置顶 / 对话页 / 存储)

给 DSH Desktop(web profile)加一个「会话策略」控制台,按用途分为四页:

分区内容
规则策略预设、会话单独设置、生效检查、三段规则与历史、按需代码审查
省 token工具输出压缩、去重、原文取回与本次运行统计
对话外观提问气泡置顶、透明度效果、阅读宽度与拖拽把手
存储分类占用、缓存回收预览、单独清空回收目录

输入框旁的 提问 / 懒码 / 形状 三段仍可独立切换,有当前会话时只修改本会话;设置页下方三段规则是全局默认。开关自动保存;规则正文要点「保存并生效」。

1.16.0 策略管理(2026-09-28)

  • 策略预设:提供「日常聊天 / 写代码 / 深入分析」,支持保存、重命名、删除自己的组合。保存三段规则的开关和原始正文快照;内置组合恢复插件自带正文。选择全局或仅当前会话,先预览开关与正文差异再确认应用。外观、审查与省 token 开关不包含在预设中。
  • 历史与恢复:每张规则卡下方可查看历史、对比当前内容、恢复所选版本。编辑、恢复内置、应用全局预设前都会记录旧正文;相同内容重复保存不增加版本。页面列最近 50 版,更早文件保留在磁盘;恢复本身也会备份当前版本。
  • 生效检查:分别核对插件是否加载、三段规则是否挂载、当前会话最近一次模型请求的系统文本是否含应有规则。改设置后旧证据会标过期;还没有新请求就显示等待,不会替你发模型请求。包含规则只能证明已带上,不能保证模型遵守。
  • 当前会话:三段分别可选「跟随全局 / 仅此会话开启 / 仅此会话关闭」,也可一键恢复跟随全局。会话预设保存独立正文快照,随后修改全局不会改变该快照;选回「跟随全局」同时取消该段正文快照。其他会话、子会话保持各自设置。输入框旁快捷开关标明「本会话」;没有会话标识的旧宿主仍使用全局默认。
  • 规则负担与检查:编辑时显示启用正文的字节量与 token 粗估范围,提示疑似重复行和常见简短/详细要求冲突,列出位置。检查在本地进行,不调用模型、不改写正文;估算不是实测或计费值,语义提示可能误报或漏报。

全局规则仍使用原有 settings.json 和三份 .md;预设与会话设置存于 $DSH_HOME/dsh-cache-control/policy.json,历史在同目录的 history/。会话覆盖最多 300 项,自定义预设最多 40 套。全局应用共用串行写入队列,并记录事务备份;正常回滚或下次启动恢复中断写入。写入期间请求继续读原来的完整规则,避免半套新旧内容。

本次需要重启 DSH Desktop 才能加载新增的宿主接口和请求检查;仅刷新页面会提示重启。重启后更改规则从下一个模型请求生效。诊断只保留内存中的时间、字节数和检查结果,最多 100 个会话,重启即清空,不保存对话正文。

验证:5 项语法检查、12 套离线测试、29 项隔离浏览器检查通过;另用本机 DSH 0.1.7 的真实提示词组装模块通过 39 项策略检查。浏览器的策略、规则与设置走真实本地接口和临时目录,统计与存储使用合成数据;未写用户配置或发起真实模型请求。

1.15.0 改良(2026-09-28)

  • 顶部显示规则启用数、每次请求附加的规则字节量与审查可用状态;字节量不等于计费 token。
  • 宽窗双列、窄窗单列,跟随宿主明暗主题;标签支持方向键。规则草稿切换分区后仍保留,失败可重试。
  • 设置只提交修改的字段,写入串行执行,后续输入合并成最新值;失败时保留待保存内容,迟到的旧读取不会覆盖新设置。
  • 规则正文响应与规则开关分别处理,审查使用独立接口;三段规则复用同一读写流程,失败不清空草稿。
  • 只挂载当前分区。统计仅在分区打开且页面可见时轮询,退出即取消读取;存储仅在进入时扫描。
  • 存储同一时刻共用一次扫描,5 秒内复用结果,主动刷新和清理后重新统计。达到扫描上限时会明确提示。
  • 缓存回收排除提示词产物与回收目录自身;批量提交预览中的具体路径,清理前确认,部分失败也会显示原因。
  • 移入回收只是挪动文件,清空回收目录才释放磁盘空间;会话历史、附件、登录信息不列入缓存清理。

更新本地文件后,刷新页面加载新界面;index.js 的存储改进需要重启 DSH Desktop。 验证:npm test 跑离线套件;tools/verify-workbench-browser.mjs 测试真实浏览器交互与截图,不连接用户的 DSH 配置。 本版通过 4 项语法检查、11 套离线测试和 16 项隔离浏览器检查;存储测试含真实本地 HTTP 请求与临时目录清理验证。 运行浏览器套件时可用 CC_PLAYWRIGHT_MODULE、CC_BROWSER_PATH 指定本机运行时,CC_SCREENSHOT_DIR 指定截图目录。

v1.14.0 移除:原第一块「省缓存 · 压缩策略」(改写 standard preset 里 compaction-basic 那一行)** 已随 DSH 0.1.7 一并删除** —— 那个版本起 agent preset 不再是可编辑的 agent.cordis.yml,接管没有落点, 只会让设置页每次打开都 500。理由与善后见「压缩接管:为什么删掉」。

省 token · 结构化压缩会话守则 · 长期规则ponytail · 编码纪律输出形状 · 回复形状自动审查 · 按需技能
动的是什么工具输出本身(tools/post-execute 改写一次)+ 注册一个模型工具 save_token_expandsystem prompt 里一个常驻段(规则文本)system prompt 里的第二个常驻段(与守则同构、独立开关)system prompt 里的第三个常驻段(同上,但默认开)宿主 skills 目录里的一个条目(不进 system prompt)
生效时机每次工具输出返回时(当次请求就生效,不用重启)适用会话的下一个 model step(全局默认可被会话单独设置覆盖)同「会话守则」同「会话守则」模型/用户调用技能那一刻
会被压缩冲掉吗折叠掉的是改写后的短文本,原文在 spillStore 里,save_token_expand 取回内存中的完整原文;超长原文或重启后可能需按提示路径调用 read不会:压缩只折叠对话历史,system prompt 每请求重发同「会话守则」同「会话守则」不适用(压根不在 prompt 里)
代价工具表多一行(唯一一个模型工具);统计只存计数与字节数,两个开关存内存、重启回默认开内置正文约 4.3 KB × 启用此规则的请求,token 依模型而异内置正文约 3.6 KB × 启用此规则的请求内置正文约 4.1 KB × 启用此规则的请求,默认开启注册本身不注入常驻正文;调用时加载技能内容

1.13.3 检查与生效范围(2026-09-28)

  • 空会话页「工作区行」(DeepSeek / 标准模式 / PPT / 生图 那一行)左边缘对齐输入卡:两条静态 CSS 规则 —— 行内边距改走 --dsh-composer-side-clearance(与卡容器同源),行内第一个子项再补 margin-left:max(0px, calc((100% - var(--dsh-composer-card-max-width,100%)) / 2))。 无条件生效,与「对话页宽度」开关、百分比取值都无关;max(0px,…) 兜住卡片被 width:100% 夹住的窄窗。
  • 1.12.1 的 CHANGELOG 曾声称修好这条,但那两条规则从未进过任何一版 client.js(用户因此又报了一次)。 本轮真正落地,并在 verify-gate-client.mjs 加了 I 组两条源码守卫(101 → 103 项),让"只写在 CHANGELOG 里的修复"变红灯。
  • 本机真浏览器实测:卡宽 90% 时 chip 与卡左边缘差 −0.015px(亚像素舍入;改前 47.03px); 卡宽 400px / 2000px / 100% / 60% 四档偏差都是 0。
  • 生效方式同 client 半:client.js 存盘后刷新页面即生效(?rev= 是内容哈希),不必重启 DSH Desktop。

1.13.2 检查与生效范围(2026-09-24)

  • 取回:不再把超过 262,144 字符的内存预览当作完整结果;支持同步/异步读回,当前宿主没有读回接口时明确给出原文路径。重置计数保留取回、去重及回放索引。
  • 界面:请求完成后才安排下一轮;修改开关使旧读取失效;写入后回读完成才解锁;失败和超时可见,初次失败可重试;退出页面取消请求。重启语义直接展示,KPI 随可用宽度换列。
  • 测试:4 项语法检查、11 套离线测试;save-token 47 项、React 生命周期 18 项,gate-client 本机 101 项。生命周期测试真正挂载组件并执行 effect,和 SSR 格式测试互补。
  • client.js 保存后刷新页面生效;save-token-host.js 需要重启 DSH Desktop。本轮没有自动重启,避免清掉当前进程中的取回映射与统计。
  • 不新增确定性压缩兜底,不改变 compaction-basic 所有权。字节变化只能证明体积变化,不能证明摘要保留了全部关键事实。

为什么分区标题没有编号(v1.11.1)

编号是位置属性、名字是身份属性。把位置写进标题,代价是任何一次插入都要把全部下游引用重排一遍: v1.10.2 修的是 ponytail 曾写作 "②b" 导致页面上出现两个 ②;v1.11.0 插入「自动审查」又让后面三块全部顺延 (README、CHANGELOG、跨插件指路、测试断言各扫一遍)。v1.11.1 起标题只留名字,顺序由卡片书写顺序决定, 插一张卡只改一处。要指代某块直接说名字。

verify-gate-client.mjs 里两条断言钉住这件事:① 页面(含抽屉正文)不许出现圈符;② 八个名字必须齐全且顺序正确。 注意这里只剩行文里的列举序号不算违规 —— 断言扫的是渲染出的整页文本,所以 README/注释以外, client.js 里给用户看的字符串也不能带圈符。

压缩接管:为什么删掉(v1.14.0)

这一块(设置页原第一张卡「省缓存」)做的事是改写 standard agent preset 组装文件里 @deepseek-ai/dsh-compaction-basic 那一行的 config(开 = 写入 thresholdRatio / retainRatio / auto, 关 = 逐字节还原首次接管前的原文;原文存在 settings.json 的 compactionBackup 里)。 v1.6.1 还专门修过它的破坏性缺陷:只有压缩字段变化才碰那个文件,只改外观开关不会再抹掉用户自己的配置。

DSH 0.1.7 起这条路没有落点了:

  • 打包形态变了:resources\app\(真目录)换成 resources\app.asar + app.asar.unpacked\, 旧 junction(profiles\node_modules\@deepseek-ai\dsh-agent-presets)指向的目标已不存在。
  • preset 本身也不再是组装文件:安装里搜不到任何 agent.cordis.yml;包由 dsh-agent-presets 拆成 dsh-agent-preset + dsh-agent-preset-registry,组装改由代码注册表负责。
  • 后果:GET /cc/settings.json 每次都探那两个死路径,探不到就 500 ⇒ 设置页整页打不开; 启动时也刷一条 cannot locate standard preset 的告警。

删的是整条写盘路径(读组装文件、splice 那一行块、备份/还原、boot 对账、GET/PUT 的 applied / backupAt / hasBackup 字段),不是"默认关掉"—— 默认关掉只会把 500 藏起来。

善后:

  • settings.json 里遗留的 enabled / triggerPct / retainPct / auto 不再被 sanitize 保留, 下一次保存就从盘上消失(它们已不驱动任何行为)。
  • compactionBackup 保留、只读:它存的是用户接管前的 preset 原文,而那个文件现在已不存在, 它是磁盘上唯一的副本 —— 按"不认识的数据不当垃圾删"处理,永不写入、也永不丢弃。
  • 界面上的四行控件(总开关 / 触发点 / 保留尾部 / 自动压缩)与 chip 上原第一段「省缓存」一并删除, 不留"点了没反应"的开关在面板上。

会话守则 · 长期规则

插件内的 session-gate.md 就是规则本体,作为一份可长期演进的 md随包分发:

  • R1 独立研判:不默认用户是对的;命中「事实/技术错误、目标与手段冲突、与既有约束冲突、 代价不划算」四类必须指出,且反对要可核查(对象 + 理由 + 替代方案);最终裁决权在用户, 但不可逆损失(删数据、覆盖无备份、改生产、花钱、对外发布)必须先确认。
  • R2 必要提问:只有「不同理解会改变结果」且「答案查不到」时才停下来问;能查的先查; 一轮最多三问、带候选项与推荐;拿不准但可回滚就先做完再标注。v1.9.6 起补两条时机细化 (蒸自 Claude Code "Delivering work" 条款,MIT):改变方案形状的缺口问在动手前, 只影响实现细节的按默认假设做完再标注;阻塞式提问是最后手段,否则先做完不依赖答案的部分。
  • R3 分工固定:用户定目标、补真实情况、判定可用性;助手负责搜索、执行、制作、验证、交付。 交付必须可判断(改动路径 + 依据 + 未覆盖项 + 风险与回退)。
  • R5 少犯错优先(编码行为四原则):蒸馏自 multica-ai/andrej-karpathy-skills (214K★,Karpathy 诊断、Jiayuan Chang 成文,v1.9.2 起收录)——动手前说假设 / 最小实现 / 只动必须动的行 / 任务转成可验证目标。与 R1 互补:R1 管"敢反对",R5 管"少犯错"。
  • R6 查证再下结论:蒸馏自 duolahypercho/andrej-karpathy-skills 第 4 条的验证口径(v1.9.4 起收录)——先查证再断言、未验证标【假设】、完成前跑验收动作并附证据、 没跑检查就明说没跑。
  • R7 谨慎执行:蒸馏自 Claude Code 出厂条款 "Executing actions with care"(经 Piebald-AI/claude-code-system-prompts,MIT, v1.9.5 起收录)——不可逆/共享状态动作先确认、授权只按当次范围算、破坏性操作不当绕路捷径、 不认识的状态当用户半成品(能移不删)。git status 一条刻意未收:DSH 的沙箱与审批门已覆盖大半。

界面上:

  • 控件用宿主官方原子(v1.9.3):开/关行是「说明在左 + dsh-client-ui-primitives 的 Switch 在右」, 按钮走宿主 Button —— 随宿主主题与明暗自动一致。primitives 取不到时软回退到自带的 checkbox/.cc-btn, 功能两种情况完全一致(同一 require 失败只影响观感,不影响开关)。
  • 启用开关独立于其它规则段,勾选框各管各的。chip 是固定版式的三段状态: 提问 [开/关] | 懒码 [开/关] | 形状 [开/关],[开/关] 复用同一个徽标元件(.cc-badge), 面板里各分区头也用它。
  • 面板(输入框右侧)里三块用分隔线分区,可「查看规则」直接看当前生效文本。
  • 设置页里可编辑规则:写入 $DSH_HOME/dsh-cache-control/gate.md(override), 不改动插件目录里的内置 session-gate.md;点「清除自定义,回到内置」即删除 override。

实现要点(也是几条硬约束的理由):

  • 注入走 ctx.inject(['systemPrompt']) → systemPrompt.section({ name, order: 400, text }), 与 dsh-web-app 注入 app:web-surface 同一条路;text 是函数,DSH 每个 model step 重新 assemble(),所以改开关/改文本不用重启也不用新会话。拿不到 systemPrompt 服务时只 关掉会话守则,不影响其它规则段。
  • 关 = text 返回空串,renderPrompt 会丢弃空段 ⇒ 提示词里一个字都不留。
  • 文本按 mtime+size 缓存并即时重读:直接编辑 md 存盘,下一个请求就是新内容(无需重启、无需刷新页面)。
  • 注入前把成对花括号 {{ / }} 替换成全角 {{ / }}:renderPrompt 对未知变量引用是 抛错策略,用户编辑规则时写了 {{...}} 会让每次请求组装失败 —— 所以宁可改字形也不让会话挂。
  • 上限 16,384 字节(16 KB,约 6.6k tokens);超限自动截断,避免规则膨胀悄悄吃掉上下文。 (初版是 6 KB;加完 R4 输出形状一节后余量只剩 92 B,故 v1.9.0 放宽到 16 KB —— 这段文本每请求 重复计费,上限本身仍然保留,只是不再卡在刚好够用的位置。)
  • R4 输出形状不在本文件里:2026-09-16 起本文件只剩一句归属声明,真源是同目录 shape-gate.md, 由本插件作为第三段常驻规则注入(详见下面「输出形状」一节)。别在这里再抄一份,否则同一套规则会被注入两遍。 要收回原文备份:git show 98fa1af:session-gate.R4-backup-2026-09-16.md, 或看 v1.9.1 之前的 git show 566d276:session-gate.md 里的整节(同目录那个备份文件 2026-09-23 已清出仓库)。
  • 怎么知道自己的规则被砍了:/cc/gate.json 与 /cc/settings.json 内嵌的 gate 里有三个字段 —— truncated(显式布尔标记)、originalBytes(规则原文字节数)、keptBytes(实际注入 字节数,与老字段 bytes 同值)。不要用 bytes >= maxBytes 去反推"有没有截断过":截断后的 长度必然小于上限(6 KB 上限年代的实测:19,998 字节的中文规则 → 保留 5,839 字节; 10,000 字节 ASCII → 5,590 字节), 那个判据对已截断的规则恒为 false,界面于是显示"未截断";反过来原文恰好等于上限时文本原样 返回、bytes === maxBytes,它又会误报"已截断"。这正是 v1.6.2 修掉的缺陷, tools/verify-gate-truncation.mjs 就是它的回归套件(含"恰好压线"与"上限 -1"两个边界, 样本长度与期望值都相对上限生成,改动 GATE_MAX_BYTES 不会再让套件失效)。
  • 界面上被截断时显示「已截断:原 N B → 保留 M B」标签,卡片里另写一句 「你的规则被截断了:原文 N 字节,实际注入 M 字节(超出 X 字节未进入提示词)」;编辑框里草稿 超限时也会提前提示"保存后会被截断"。老客户端只读 bytes/maxBytes 照旧可用(字段只增不改)。

ponytail · 编码纪律(v1.10.0)

与「会话守则」同构的第二段常驻规则:蒸馏自 GitHub DietrichGebert/ponytail(MIT), 最懒资深工程师的七级梯子与硬约束。独立开关 ponytailEnabled(默认关),提示词段 dsh-cache-control:ponytail-gate(order 405,紧挨守则 400、在输出形状 410 之前)。 两段各读各的文件(session-gate.md / ponytail.md)、各有独立的 mtime+size 缓存,互不顶掉; 自定义覆盖走 /cc/ponytail.json(GET 预览 / PUT 写 override / 空文删回内置)。 代价:开着约 1.3K token/请求 × 所有会话(含子代理与非编码会话),正文里写明"只对编码任务生效"兜底行为。

输出形状 · 回复形状(v1.12.0,并入自 dsh-output-shape)

与上面两段同构的第三段常驻规则:蒸馏自 GitHub ayghri/i-have-adhd(MIT)的 「ADHD 友好输出」十条规则(首行给下一步 / 多步编号 / 状态复述 / 跑题后置 / 时间给量级 / 战果可见 / 报错讲因果 / 展示分组 ≤5 / 无开场白无客套)+六条破例 + 发送前自检。独立开关 shapeEnabled, 提示词段 dsh-cache-control:shape-gate(order 410,紧随 ponytail 405、plan 政策 500 之前)。

  • 默认开,与另两段相反:并入前它由独立插件 dsh-output-shape 的 bundle config 默认开启(那插件是这套规则的真源, 会话守则里的 R4 早在 2026-09-16 就摘出交给它)。合并时保持同默认 —— 否则升级即静默改变行为,用户只看到"形状规则没了"。 不想要这份 token 就去设置页或 chip 第四段关掉它。判据是 !== false(不是 === true):旧盘上没有这个键时按开处理。
  • 文件与路由:内置 shape-gate.md、override $DSH_HOME/dsh-cache-control/shape.md、 /cc/shape.json(GET 元信息 / PUT 写 override / 空文删回内置)、独立 mtime+size 缓存(三段互不顶掉)。
  • 原独立插件已下线:dsh-output-shape 从 web profile 的依赖与 bundle 列表里摘除,仓库目录归档。 段名由 dsh-output-shape:output-shape 改为 dsh-cache-control:shape-gate(段名只在运行时用,盘上没有引用,无需迁移)。
  • 它还接手了两条按需技能:i-have-adhd 与 ponytail(原来是那个插件注册的)。两条技能无开关、零常驻 token, 正文直接读本插件的规则文件(shape-gate.md / ponytail-gate.md,override 优先)—— 于是"技能里读到的规则"与 "每请求注入的规则"永远同一份,不会漂。verify-shape-gate.mjs 第 8 节把这条钉死。
  • 逃生开关沿用原名 DSH_OUTPUT_SHAPE_DISABLE=1(改名等于把别人环境里/脚本里的开关悄悄拔掉): 置 1 则无论设置如何都不注入常驻段;技能注册不受影响。界面在卡片与面板里都会写明"被环境变量强制关闭"。

自动审查 · 按需技能(v1.11.0)

注册宿主 skills 服务里的 auto-code-review(正文 skills/auto-code-review/SKILL.md)。 这张卡与「会话守则」「ponytail」两段的根本区别:一个字都不进 system prompt —— 注册状态零 token,正文只在模型或用户 真调用那一刻加载。开关关掉 ⇒ dispose,目录里连条目都不留。

为什么不蒸成常驻规则(这是本卡存在的理由)

上游 alibaba/open-code-review(Apache-2.0,阿里内部官方 AI 审查助手开源化)的 README 把"通用 agent + 自然语言 skill 做审查"列为反面教材:

通病机制原因
覆盖不全大 changeset 上 agent"抄近路",选择性只审几个文件
位置漂移报出的问题与实际行号/文件对不上
质量不稳自然语言 skill 难调试,prompt 微调就大幅波动

根因一句话:纯语言驱动对审查过程没有硬约束。它的解法是「确定性工程 × agent 混合」—— 选文件、分组、按文件特征匹配规则、评论定位、反思复核全部由代码保证;基准 AACR-bench (50 仓库 / 200 真实 PR / 1,505 条标注,80+ 资深工程师交叉验证)显示同模型下 F1 更高、 token 只用通用 agent 的约 1/9,而 recall 有意更低(偏精确率、压噪声)。

⇒ 抄规则文本进常驻段 = 只拿走它论证过会失败的那一半。ponytail 能常驻是因为它是风格取向 (YAGNI、梯子),没有"覆盖率""定位准确率"这类可度量指标;审查有。所以本插件不存规则正文, 每次现向 ocr 取,规则跟着上游升级、不在本仓库里腐烂。署名见 NOTICE。

三步(技能正文就是照这个契约写的,本机 v1.12.7 实测)

ocr delegate preview --format json          # 该审哪些文件(reviewable_files[] / excluded_count)
ocr delegate rule --format json <path>...   # 这些文件命中哪些规则(groups[].rule + files[])
# 然后逐组对着 diff 出结论:文件:行 + 违反哪条 + 一句为什么

范围不对时用 --from main --to feat 或 -c <commit> 重取,而不是手工挑文件。

运行期依赖(本插件不打包、不下载、不自动安装)

需要用户自行 npm install -g @alibaba-group/open-code-review(前置 Git ≥ 2.41)。 卡的第三行直接显示探测结果:装了 ⇒ ocr 已就绪:v1.12.7(<命令路径>);没装 ⇒ 黄字给安装命令。 探测不到不影响注册 —— 技能照常可用,只是跑到第一步就会停下说明,不会瞎猜。

⚠ 两个 Windows 坑(都踩过,别再改回去):

  1. execFile('…ocr.cmd') 在 Node ≥18.20/20.12/24 抛 EINVAL(CVE-2024-27980 修复),不是 ENOENT —— npm 在 Windows 上装的全局 CLI 恰恰就是 .cmd shim。所以 .cmd 那一路走 shell:true 且整条命令进 shell、不传 args(传 args + shell 会刷 DEP0190 告警)。
  2. DSH Desktop 宿主进程的 PATH 里没有 npm 全局 bin:命令行里 ocr 能跑、插件里 spawn 'ocr' 报 ENOENT。故 ocrCandidates() 显式补 %APPDATA%\npm\ocr.cmd。探测结果按 TTL 60s 记忆化, 不每次 GET 都 spawn 一遍子进程。

与会话守则 R5 / ponytail 的分工

那两个管"少写、写最小实现",这张卡管"写出来的东西对不对"。重叠处(死代码、过度抽象) 以 ponytail 的判断为准,技能正文里已写明不要重复提。

气泡置顶(纯界面,与前四块独立)

开关效果实现
pinLastUser滚动时把**已越过会话区上沿的最后一条「我的提问」**钉在顶部:往上翻会换成第 4、3 条……滚到底才钉最近那条(分节标题语义)从 [class*="_userRow"] 爬到 [data-chat-flow] 的直接子元素打 data-cc-pin + position:sticky;"钉哪一条"按 rect.top <= 滚动区上沿 + 2px 判定,scroll 触发重选;底衬画在该元素的 ::before 上(见下)
clearBubble我的气泡背景透明,露出壁纸(配合 dsh-bg-atelier)[class*="_userRow"] [class*="_bubble"]{background:transparent}
pinBlur钉顶底衬的模糊度滑杆,0–24px(0 = 只留半透明底、不模糊);小值有小数档:<3.5 按 0.1 步进(可选 1.3 / 1.5 / 1.7),≥3.5 按 0.5 步进<html style="--cc-pin-blur:Npx">,底衬规则写 backdrop-filter:blur(var(--cc-pin-blur,10px)) —— 拖滑杆只改一个变量,不重注入样式

我的提问气泡的动态贴合(v1.4.0 起始终生效,无开关;几何由 probe-userrow.mjs 用真浏览器 + 真函数源码实测):

为什么必须用 JS 量一次:块盒的宽度只跟"可用宽/上限"有关,与文字实际末端无关(inline 盒虽然贴字, 但背景逐行着色、行内 padding 只落在首末片段 ⇒ 框"超"到文字之外、文字也不垂直居中)。所以:

  • CSS 定骨架(尺寸一律 em / 宿主字号变量):userRow = display:block; position:relative; box-sizing:border-box; width:fit-content; margin-left:auto; max-width:none(只当"右侧车道"), 右缘再留一条轨道 padding-right:var(--cc-tail-room-px, 2.4em);userStack 只给骨架: display:block; width:fit-content; margin-left:auto; 宽度上限由 JS 逐条写 inline(v1.16.16): fitUserBubbles() 对每条提问判"在不在被钉住的那条里面" ⇒ stack.style.setProperty('max-width', 消息列宽 × (钉顶? pinWidthPct : userWidthPct) / 100 + 'px', 'important')。 为什么用 inline 而不是 CSS 规则:两套百分比共用同一批布局属性时,"哪条规则赢"换版本就会翻车 (1.16.12 拆变量 → 1.16.13 :not(后代组合器) 被 Electron 整条丢弃 → 1.16.15 两规则各写全仍不稳); inline + important 优先级最高,没有分胜负这回事,且逐条判定天然支持"1 条钉顶 + N 条普通"。 CSS 里两条规则只留骨架 + 一条不带 !important 的兜底上限(JS 还没跑时用)。 bubble = 块盒 + 上下对称 padding .47em + 圆角 1.45em;图标行 position:absolute; right:.13em; left:auto; top:var(--cc-tail-y,auto) ⇒ 落在轨道里 = 气泡右侧;图标尺寸 calc(1.5em + var(--dsh-content-font-delta,0px)) 跟宿主字号走。字号变、页面缩放变,这些一起变 —— 不再有任何"某次量出来好看就钉死"的像素数。
  • 为什么上限不能写在行上(v1.16.7 实测):margin-left:auto 只吃"包含块宽 − 已用宽", 而 width:fit-content 的已用宽是先被 max-width 夹出来的 ⇒ 剩余为 0 ⇒ auto 退化成 0, 行停在左边缘、短句气泡看着像"居中"。离线复现见 tools/verify-pin-width.mjs (列 1140、45%:行上写 max-width ⇒ 气泡右缘差 663px;上限搬到栈上 ⇒ 36px)。
  • JS 量一次(fitUserBubbles()):先用 naturalBubbleWidth() 量文字自然宽 (内容拷进 width:max-content 的隐藏盒量一次,与当下折行无关),再 stack.style.width = 自然宽 + 左右内边距(框贴文字,超上限由栈的 max-width 夹回);接着实测图标行宽高 ⇒ 写 --cc-tail-room(轨道 = 图标行宽 + .45×图标高)与 --cc-tail-y(与最后一行同高)。 自然宽按元素缓存在 WeakMap 里,文字或列宽变了才重量。
  • 为什么不能量"折好的行"(v1.16.7 实测):那种量法会在内联宽先被写小的时候自锁 —— 文字在那个小宽度里折行,量出来最宽的一行也正好 ≈ 那个小宽度,下一轮把错值再写一遍, data-cc-fit 签名再把它焊死(用户实测到的 192px 就是这么来的)。
  • 实测(列宽 1180、字号 15px):10 字 174px、30 字 444px、长文 579px;复制键距气泡右缘 13.1px(em 轨道,随字号缩放)且与末行同高;上下留白 8/8.1 相等;420px 窄容器不越框。
  • 两条踩过的坑(别改回去): ① 用 bubble.children.length > 0 判"含内嵌块就跳过",会把带 @路径 引用的提问(宿主渲染 成 <span>)整条漏掉 ⇒ 框宽退回"块宽 = 上限"的固定观感(就是"完全不动态"那次反馈)。现在 只跳过真的含 img/video/canvas 或某行矩形异常高(内嵌块)的气泡。 ② applyAppearance() 里任何一步抛错(例如常量改名)会连带把后面的观察器全跳过 ⇒ 被钉元素 不出现,看起来就是"模糊度失效"。现在每段各自 + ,首屏再补量两次,并在 后清签名重算(字体切换会改行宽,一次量错会被签名锁住)。

底衬形态的三次选定: 2026-09-07 选半透明毛玻璃(不是实底、不是无底衬); 2026-09-07 改成圆角矩形——原先把毛玻璃铺在被钉住的整行上,而行宽 = 整个会话列宽, 于是气泡左边一大片空白也在模糊。现在:

  • 行自身只留 position:sticky,background / backdrop-filter 一律不再铺;
  • 底衬画在 ::before 上:right:-6px(右缘贴住气泡右缘,宿主 .userRow 是 align-items:flex-end 右对齐),宽度 var(--cc-pin-w, calc(min(calc(var(--dsh-chat-content-width,748px) * .55), 100%) + 12px)) —— v1.4.2 起 --cc-pin-w 由 JS 按这条提问的实测宽度写入(气泡含右侧图标轨道的像素宽 + .8em 呼吸位), 短句就是短底衬、长文就是长底衬;读不到几何时(getBoundingClientRect 拿不到正宽)退回括号里那个 "列宽 × .55 + 12px" 的旧上限兜底;
  • 长文钉顶时不再无限撑高:气泡本体 max-height:38vh + overflow-y:auto + overscroll-behavior:contain(在气泡内滚,滚到底才交还给会话流),scrollbar-width:thin;
  • border-radius:16px 圆角矩形;四周出 2–6px 呼吸位(top:-2px;bottom:-2px,不加 padding ⇒ 不挤动布局);
  • z-index:-1:被钉行有 z-index:6 自成堆叠上下文,负层因此落在"正文之上、气泡之下", backdrop-filter 采到的正是身后滚过去的正文;
  • 模糊度走 --cc-pin-blur 可调变量,默认 10px。

宽度怎么来的(v1.4.2):updatePinPlate() 在"钉住哪一条"确定后、以及每次重排(fitUserBubbles) 结束时各跑一次 —— 量被钉行里 [class*="_userRow"] 的实际像素宽,加上 根字号 × .8 当呼吸位, Math.ceil 后写进该行的 --cc-pin-w;量不到就 removeProperty,让 CSS 里的旧上限接管。

一个必须知道的真实边界:sticky 的移动量 = 父级高度 − 自身高度,所以只有当你那条提问下面 还有比它更高的内容(通常是长回答)时,钉顶才看得出来;短回答或空会话里它就像没生效。

为什么不是纯 CSS 一行:sticky 的移动量 = 父级高度 − 自身高度。真实结构(读自 dsh-client-ui-chat 的产物)是 [data-conversation-scroll] > … > [data-chat-flow] > [data-chat-flow-key] > .userRow > .userStack > .bubble, 给内层 .userRow 直接加 sticky 不会动(父级等高、没有剩余高度),所以要钉的是 [data-chat-flow] 的直接子元素(每条消息一层)。找行用 [class*="_userRow"], 定层用宿主的稳定属性 [data-chat-flow](该属性在 column 上、每条消息的 data-chat-flow-key 在其子层,均在产物里核实过);属性不在时退回"按剩余高度往上爬"的通用判据。 气泡透明只能按类名匹配,而 uSmzmW_ 这类前缀是构建哈希 ⇒ 用后缀选择器 [class*="_userRow"] [class*="_bubble"],宿主升级改名时最坏结果是这条样式不生效,不会连累其它功能。 设置页会把"钉到了哪个元素 + 共几条提问"打印出来供自检。

观察器分两条:钉顶那条只在开关打开时挂 document.body(childList + subtree,rAF 去抖),关闭即断开并清掉所有 data-cc-pin;气泡贴合那条只挂 [data-chat-flow] 容器(+ scroll / resize,90ms 去抖),负责重算框宽、 字尾位置和"钉哪一条"。插件停用/卸载时 stopFitWatch() 会撤掉观察器并清干净 stack.style.width、 --cc-tail-*、data-cc-fit 三样内联痕迹。

输入框右下角 chip 里的「开 / 关」徽标不吃背景(2026-09-07):.cc-chip .cc-badge 三条规则 把 background / border-color 都置 transparent,状态只靠文字颜色区分(开=品牌蓝、关=三级灰、 未装载=警示黄)。面板与分区头里的同名徽标保持原样(那里有底色对比的需要),所以规则限定在 .cc-chip 作用域内。

对话页(固定会话列宽,v1.3.0 从 dsh-bg-atelier 移入)

开关效果
chatWidthEnabled + chatWidth关闭 = 跟随 DSH 自适应;打开 = 把会话列宽钉在可用宽度的 30–100%(含 60/70/80/90/100 快捷键;v1.5.0 起是百分比,之前是 640–3840px)
hideResizer(v1.6.0,v1.7.0 起只管两竖杠)关闭 = 保留会话区两竖杠(宽度把手);打开 = 把它们隐藏。列宽一旦钉成固定百分比,这两条把手拖了就不再改变列宽,只剩"鼠标扫过去冒出两竖杠、拖了没反应"的误触(用户 2026-09-14 反馈)
hideDivider(v1.7.0)关闭 = 保留侧栏/详情栏分隔条;打开 = 把它隐藏。分隔条与两竖杠不同,拖它仍然有效(改侧栏宽度),所以拆成独立开关、默认不藏
  • 隐藏把手按语义识别、不按类名:宿主的类名是 CSS-module 哈希(实测 ._1tdjgG_handle / ._8JRpoa_widthHandle), DSH 一升级就变,而 cursor 含 resize 才是"这是条把手"的功能特征。两类再用宿主直接写在把手元素上的 data-width-handle 属性区分(有 = 会话区宽度把手,没有 = 框架分隔条)——数据属性不是哈希,比类名耐升级。 实现是给命中的元素分别打 data-cc-hide-resizer / data-cc-hide-divider,再由插件样式表 [data-cc-hide-*]{display:none!important} 隐藏;只认可见元素、排除自己面板内的元素, 并挂一条 MutationObserver(300ms 去抖)—— 宿主重建框架会换掉那些把手。 关掉对应开关或停用插件时把标记撤干净,不给宿主留一条看不见的把手。 两个开关各有独立能力位(resizerReady / dividerReady):旧 host 不认识某个键时只禁用那一个开关, 不连带其它。

  • 钉法:先按 [data-composer-card] 往上找到内联带 --dsh-conversation-column-width 的那个祖先 (= 会话根,宿主 publishWidths 就在它身上标定列宽),再往它身上写 --dsh-chat-content-width / --dsh-composer-card-max-width(宽 +32)/ --dsh-chat-user-width 三个变量并带 !important,绕过宿主的响应式 clamp;另有一条 :root{--dsh-chat-user-width:…!important} 兜底,覆盖 composer 还没挂上的窗口期。关闭时逐个 removeProperty + 删掉兜底样式,交回自适应。

  • 会话根随切会话/导航会重建 ⇒ 另挂一条 MutationObserver(300ms 去抖)只在开关开着时存在, 根节点一重建就把变量补写回去。

  • 滑杆拖动过程中只做即时预览(局部 state + 直接钉 CSS 变量),松手/失焦/方向键才 STORE.set → 存盘: 否则每拖一格都会把设置页整页重渲染一遍,手感发涩。

  • 与「气泡置顶」的联动:底衬宽度 v1.4.2 起跟随这条提问的实测宽度(--cc-pin-w),读不到几何时才退回 --dsh-chat-content-width × .55 的旧上限;所以这里改列宽,只在"兜底路径"下才会等比影响钉顶底衬。

  • 一次性迁移:这两项原先存在 $DSH_HOME/dsh-bg-atelier/settings.json。host 启动时若发现自家 settings.json 缺 chatWidth / chatWidthEnabled,就读底图工坊那份搬过来并写盘 (migrateFromAtelier(),日志 对话页宽度已从 dsh-bg-atelier 迁入);搬完之后自家有字段就不再读对方, 你之后改的值不会被对方旧值盖回。bg-atelier v1.3.0 起客户端不再声明这两个字段,也就不会再 PUT 回去。

存储(v1.8.0)

按用途分别统计各目录占了多少盘(会话记录 / 投影缓存 / 附件副本 / 浏览器观察窗 / 生图产物 / 费用记录 / 本插件数据),给出文件数·字节·最新最旧时间与总计。清理只收"明确可再生成"的东西, 且先给候选清单再确认、动作是移入回收而非删除;purge 单独确认后清空回收目录。 候选仅含观察窗截图与浏览器磁盘缓存,提示词产物与回收目录本身不再列入。 GET /cc/storage?refresh=1 强制重新统计;普通读取共享在途扫描并短暂缓存 5 秒。 host 三条路由:GET /cc/storage、POST /cc/storage/clean、POST /cc/storage/purge。

省 token · 结构化压缩(v1.13.0,并入原 dsh-plugin-save-token)

来源:并入自原独立插件 dsh-plugin-save-token v2.4.1(MIT,© playwithai / vibe-any), 上游 host 半 src/index.js 与纯函数核 src/compress.js 原样搬进本仓库 (save-token-host.js / save-token-core.js,两个文件头都写着相对上游删了什么)。 署名、改动范围与"为什么这样改"见 NOTICE。它不再是独立插件:没有自己的 roster 条目, 由 index.js 以 ctx.plugin(saveToken, config) 嵌套挂载,是这张设置页卡片的 host 半。

做什么:在 tools/post-execute 上把工具输出改写一次 —— 结构感知压缩(JSON / 表格类先认结构再 重排)、无损重编码(同一份数据换更短的表示,零损失)、重复调用去重 —— 原文写进 spillStore, 改写后的短文本留在上下文里,并附一条取回提示。模型需要原样内容时调 save_token_expand(按 locator 从 spillStore 逐字节取回)。压缩只在这一个点上发生一次:不去改写历史消息,已发出的请求前缀 逐字节不变,缓存命中不受影响。

  • 唯一一个模型工具:save_token_expand。此前本插件在工具表上是零占位,v1.13.0 起多一行。
  • 依赖 spillStore:拿不到它(宿主没提供 / 磁盘写不进去)时压缩永久关闭、工具输出原样进 上下文,设置页那张卡会显式写出"可逆存储(spillStore)当前不可用"与原因 —— 宁可不用,也不做 一份取不回来的压缩。
  • 路由(前缀注册 /cc/st,只服务本插件自己的设置页):GET /cc/st/api/dashboard、 POST /cc/st/api/set-enabled、POST /cc/st/api/reset。GET 的响应里没有 ok 字段(判断取数成功 看 flags),POST 返回 {ok:true|false};未知端点 404、内部错误 500。
  • 设置页卡片:4 个 KPI(模型请求 / 输入 token / 输出 token / 省下 token)+ 一条内联 SVG sparkline(灰=实际送出、绿=省下)+ 按工具归一化的省字节条 + 最近活动表(时间、中文类型标签、 只给省下的量)+ 「说明」抽屉。每 2.5 秒拉一次 dashboard,卡收起时照拉(数字要活着),卸载时清掉定时器。
  • 两个开关存内存:压缩 与 去重(默认都开)不写 settings.json,重启回默认; 计数同理(重启归零)。这是刻意的:这两个开关是"这一轮我想不想让它插手"的临时闸,不是配置。 ⚠ 它与本插件别处的开关不一样,别去 settings.json 里找它们,也别指望导出/导入带走。

主动不做的三件事(都是真做过判断的,不是漏了):

  1. 上游的 compaction assist 整段删除(上游 "arm 3":agent/pre-step 压力触发、水位线、冷却期、 compactStats、ctx.get('compaction'))。它和本插件自己的「省缓存」抢同一个压缩引擎 —— 当初(v1.13.0)的判断是"两套阈值同时改写同一个 preset 行必然互相打架",所以删除而不是默认关掉: 默认关掉只是把冲突藏起来,以后谁手滑打开就又撞上。 注:v1.14.0 起「省缓存」本身已随压缩接管一并移除(见上),这条记录的是当时的理由。
  2. 上游的 conversation.composer.dock 常驻小条不装。那个槽位归 dsh-bill(它在同一位置画每会话 费用行),同一格塞两块 UI 会互相挤掉;统计仍在,只是改由设置页这张卡呈现。
  3. 不落盘任何 prompt 文本(与上游口径一致:只留计数与字节数)。别名/标签可能出现在活动表里, 正文不会。

安装

前提:DSH Desktop(dsh CLI 可用),并在装完后重启桌面应用一次。DSH 关闭状态下任选其一:

  1. 从本仓库装(推荐):
    git clone https://github.com/Raylen-berry/dsh-cache-control.git D:\dsh-plugins\dsh-cache-control
    dsh plugin --profile web add link:D:/dsh-plugins/dsh-cache-control
    
    (Linux/macOS 把路径换成自己的绝对路径即可;link: 改动即生效,便于边改边试。)
  2. 手工接线:在 profiles/web/package.json 的 dependencies 与 dsh.profile.bundles 里加 dsh-cache-control,并把 profiles/web/node_modules/dsh-cache-control 做成指向本目录的 junction / symlink。

装完后 host 半(index.js,含会话守则段注册)随 profile 装载。

⚠️ 改了 client.js 必须重启,光刷新页面没有用(我此前说过"刷新即可",那是错的)。 依据(宿主 dsh-client-modules 的产物 + 实测):插件 client 半不是按请求从磁盘读的 —— 它在 服务启动时一次性 compose 成带 rev 哈希的 combo,挂在 /plugins 前缀下、以 cache-control: public, max-age=31536000, immutable 提供。原始路径 /plugins/dsh-cache-control/client.js 实测是 404(我先前在这里写过它,是错的), 只有 compose 后的哈希 URL 才有响应。唯一能让新字节进图的入口是 rebuilt(id), 而它属于 HMR watch(需要 pnpm run dev:web 在跑)。打包运行的桌面应用没有这个 watch ⇒ 磁盘上的新 client.js 只有重启才会进组合。重启后 rev 变了、index 注入的是新 URL, 所以浏览器那份一年期 immutable 缓存不会挡住新版。

此后:改规则 md 立即生效(host 每次组装重读磁盘);开关与滑杆改动即时写盘; 压缩参数对之后新建的会话生效;界面与 chip 的改动要重启才可见。

发布前检查(CI 与本地同一条命令)

push / PR 都会跑 .github/workflows/ci.yml,它做两件事:npm ci(只装 devDependencies)→ npm test。 本地跑的就是同一条命令:

npm install                    # 只装 devDependencies(就一个 react);CI 用 npm ci
npm test                       # = node tools/run-all.mjs
node tools/run-all.mjs --list  # 只看清单:跑哪些、以及哪些被排除、为什么

出网边界:只有 npm ci / npm install 那一步出网(按 package-lock.json 装 devDependencies)。 npm test 本身不出网 —— 不做真实下载、不调模型、不读 %APPDATA% 下的真实 settings.json。

可迁移性(2026-09-30 起):tools/ 下各套件不再写死开发机路径 ——

  • 插件目录默认按本文件位置推导(../),换盘符/换目录直接跑;
  • DSH_APP_MODULES 默认指向仓库自身的 node_modules/,所以先 npm ci 装出 devDependencies 即可, 不再依赖本机 DSH Desktop 的安装目录;
  • 各环境的 DSH_CC_PLUGIN / DSH_APP_MODULES / DSH_TOOL_HOME / DSH_BGA_CLIENT 覆盖项全部保留。

所以在任何机器上,只要 npm ci && npm test,就能拿到与 CI 一致的结果。

tools/run-all.mjs 把每套都跑完再汇总(不用 && 串,避免第一套一失败就看不到后面), 任一套非 0 退出 ⇒ npm test 退出码 1 ⇒ CI 变红。CI 用 Node 20/22/24 三档矩阵、windows-latest。

干净环境实测(DSH_HOME / APPDATA / LOCALAPPDATA / USERPROFILE / DSH_APP_MODULES 全指空目录, 独立下载的 node),三档(Node 20/22/24)结果一致 —— 5/5 套件通过:

套件结果
tools/verify-gate-truncation.mjs31 项通过
tools/verify-host-width.mjs全部 PASS
tools/verify-settings-payload.mjs9 项通过(v1.7.0 起含 hideDivider 对齐断言;v1.11.0 起含 reviewSkillEnabled 与"载荷锚点定位"两条)
tools/verify-panel-and-resizer.mjs25 项通过(v1.7.0 起两类把手分开关断言)
tools/verify-session-gate.mjs39 项通过(v1.9.4 起纳入;本机跑时另加真实 home 对照 3 条)

上表是 v1.9.x 那次"干净机器实测"的记录,当时确实只有 5 套。v1.10.2 起进 CI 的是 8 套: 多出 verify-ponytail-gate.mjs(23 项,v1.10.0 纳入)、verify-shape-gate.mjs (60 项,v1.12.0 纳入)与 verify-gate-client.mjs (v1.12.0 起本机 80 项 / 无真实 home 时 78 项 + SKIP,夹具化见下面那条)。 v1.13.0 起是 10 套:再加 verify-save-token.mjs(40 项;verify-settings-concurrency.mjs 更早已在列,本条只补上面漏掉的那 8→10 差额);verify-gate-client.mjs 本机 101 项 (v1.13.0 加 I 组 19 条;v1.13.1 再加 2 条源码守卫 —— 位置调用口径 + 设置页错误边界, 两条守的都是"1.13.0 整页白屏为什么 99 条没拦住")。

verify-panel-and-resizer.mjs 原来因为"要本机 DSH 安装目录的 node_modules/react"被排除。 现在 react 进 devDependencies,run-all.mjs 把 DSH_APP_MODULES 指向仓库自己的 node_modules/, 于是本地与 CI 都不再依赖任何人的安装路径(反向证据:把 DSH_APP_MODULES 指回空目录, 该套件立刻报 ERR_MODULE_NOT_FOUND: Cannot find module '<空目录>/react/index.js')。

verify-session-gate.mjs 被排除的原因(v1.9.4 已消除两条):① DEPLOYMENT_PERSONA TypeError ——根因是断言引用了宿主从未导出的符号名,套件在段序断言处崩、后面 20+ 条从没跑过;② 读 %APPDATA% 真实 settings ——换成"真实 home 存在才比对"。它 import 的 @deepseek-ai/dsh-system-prompt 走自己 devDependencies 里那份(createRequire 从仓库 package.json 解析,版本与本安装宿主一致),所以把 DSH_APP_MODULES 指到空目录仍 39/39。 v1.14.0 起该套件连 preset 夹具也不需要了(压缩接管删除后它只碰 settings.json / gate.md)。

verify-gate-client.mjs 同样在 v1.10.2 挪回 CI,消除的是三条本机依赖:① preset 从 %APPDATA% 复制 ⇒ 换成与 verify-session-gate 同款的最小夹具;② 收尾"真实 settings.json/gate.md 未被改动" 两条硬读真实 home ⇒ 真实 home 不存在时 SKIP 并如实打印(v1.12.0 起本机 80 条,CI 上 78 条 + SKIP); ③ primitives 桩原来要求"能读到宿主真包源码"才装载 ⇒ 改为始终按签名复刻桩,读不到只打一行 NOTE。 react-dom 随之补进 devDependencies(钉到与 react 同版本 18.3.1,原来只有 react、server.js 靠宿主目录)。 反向证据(本轮实测):APPDATA 指空目录后跑 npm test ⇒ 8/8 套件通过,该套件 78 passed / 0 failed + SKIP; verify-session-gate 同步退化为 36 项(它自己的真实 home 对照 3 条也走 SKIP)。 v1.14.0 起该套件不再需要 preset 夹具,真实 home 对照也从 3 条变 2 条(只剩下 settings.json / gate.md)。

未纳入 CI 的套件(原因同时写在 tools/run-all.mjs 的 EXCLUDED 里): verify-ui-appearance.mjs / verify-gate-http.mjs(要 %APPDATA% 下的真实 preset)。

verify-gate-http.mjs 本轮没有做夹具化:它先把真实 preset 复制到临时 DSH_HOME, 然后有几条断言是"逐字节比对真实 preset 有没有被本次验证改动" (fs.readFileSync(REAL_PRESET) + sha256)。换成仓库内夹具就得重写那几条的比对对象, 而那属于放宽验证口径 —— 本轮的规矩是只允许"等价或更强"的改动,所以保持排除并在此写明。

换台机器:可迁移性与必须手动的步骤

给后续在任何一台机器上接手的人或 agent:本插件装起来不需要任何手工点击, 但下面几条"换机后不生效 / 得手动做"的事,必须先看清楚,别以为"克隆下来就完事"。 (起因:用户 2026-09-12 反馈"工作电脑上传、回家发现可用性很差、必须手动操作"。)

A. 装(agent 可全自动)

git clone https://github.com/Raylen-berry/dsh-cache-control.git <你放插件的绝对路径>
dsh plugin --profile web add link:<同一个绝对路径>

link: 挂载的意义:改完即生效(开发态),不需要每次重装。

B. 必须重启 DSH Desktop(人工触发,agent 不能替你决定) client 半在服务启动时才 compose 进图(依据见上一节),所以"装完刷新页面"是没用的。 重启会掐断正在跑的会话轮次 —— 让用户自己挑时间。

C. 设置不随仓库走**(换机器后各开关全是默认关)** 所有状态都在 $DSH_HOME/dsh-cache-control/:settings.json(各开关 + 数值)、 gate.md(会话守则的自定义覆盖,可选)、ponytail.md(ponytail 的自定义覆盖,可选)、 shape.md(输出形状的自定义覆盖,可选)。 仓库里没有它们,因此换机器后要重新打开: 「会话守则」「ponytail」「气泡置顶」「对话页」—— 否则会表现为"插件装了但什么都没发生"。 (「自动审查」与「输出形状」按 DEFAULTS 默认开,换机器不用拨:前者只注册一个技能,ocr 没装也不报错, 只是用到时第一步会停下说明;后者是常驻规则,不想要那份 token 才需要去关。 「省 token」的压缩/去重两个开关不写 settings.json、只活在 host 进程内存里,换机器后本来就是默认开, 所以它既不用拨、也不会被导入导出漏掉。)

D. 「省缓存」改的是 preset(v1.14.0 起已移除) 它原先把参数写进 $DSH_HOME/profiles/**/standard/agent.yml 里 compaction-basic 那一行 (带 # managed by dsh-cache-control 标记)。DSH 0.1.7 起该组装文件不再存在、这条路径已整条删除, 换机器不再需要任何与压缩参数有关的操作(理由与善后见「压缩接管:为什么删掉」)。

E. 已知的宿主坑:插件会被 generation 迁移搬走(本机踩过) 部分 DSH Desktop 版本在启动时会做 installGeneration 迁移,会把 link: 挂载的插件重新 stage 一遍,期间把一个绝对路径当相对路径拼接⇒ ENOENT、迁移被 defer, profiles/web/.install-complete 永远写不出来的同时插件也可能不加载。 本机的处置是给应用 bundle 打一个本地补丁(把本插件加进 KEEP_IN_SHARED_TREE)——该补丁不在本仓库里, 它属于"每台机器各自的 DSH 应用目录"。识别方法:启动日志出现 migration deferred / could not stage,或 profiles/web/.generations-deferred.json 反复生成。 遇到就按本机 dsh-local-patches/README.md 的脚本处理(DSH 每次升级都会覆盖该补丁,升级后要重跑)。

G. 设置导出/导入(换机器一键搬配置,v1.5.0 新增)

node tools/settings.mjs export --out D:\cc-settings.json    # 旧机器
node tools/settings.mjs import D:\cc-settings.json --yes    # 新机器(覆盖前自动备份 settings.json / gate.md)

show 看当前值;不带 --yes 演练。它连 gate.md(会话守则的自定义规则)一起搬 —— 这是本插件最不该手抄的东西。导入会按与 host sanitize 同口径的规则钳制 (触发点 5–95、保留尾部 < 触发点、底衬模糊 0–24 且 1 位小数、钉顶上限 12–80vh、 对话页宽度 30–100% 且旧 px 值归一 80%),未知字段丢弃并列出来; host 读盘时还会再 sanitize 一次,所以口径即使漂了也不会写坏引擎侧。 ⚠ 导出/导入只搬 settings.json 与 gate.md:ponytail.md / shape.md 两个 override 要自己拷 (它们就在同一个目录下)。v1.12.0 修掉一处静默丢键:这个脚本的 DEFAULTS 曾经漏了 ponytailEnabled / reviewSkillEnabled,于是"导出再导入"会把这两个开关悄悄抹回默认 —— verify-settings-payload.mjs 只比对 host 与 client,管不到这个脚本,所以以后加开关记得三处都补。 (v1.14.0 注:原「省缓存」本来就不在这个文件里 —— 它改的是 $DSH_HOME 里 standard preset 的那一行; 该功能已随 DSH 0.1.7 移除,现在整份配置都能靠导出/导入带走。)

F. 换机后自查(30 秒)

node tools/verify-audit.mjs 2>$null; node tools/verify-host-width.mjs   # 期望全绿 / 无 FAIL
# 设置页应出现「会话策略」八个分区(标题无编号);对话页默认 80%(百分比,v1.5.0 起)
# 「省 token」那张卡应显示 4 个 KPI、按工具省字节条与最近活动;没跑过工具时是"还没有…"空态

验证

回归与探针脚本都在本仓库 tools/ 下(只用于开发,不进 npm 包,见 package.json 的 files)。 分两层:逻辑回归(Node 里跑,写盘全部落在临时 home,不碰你真实的 $DSH_HOME)与 浏览器实测(无头 Chrome,把宿主产物里的真实 CSS 规则与真实类名塞进复刻约束的夹具,判定用数字不用肉眼)。

node tools/verify-session-gate.mjs    # 规则解析 / 花括号防御 / 截断 / 压缩行无回归
node tools/verify-ponytail-gate.mjs   # ponytail 段:独立开关 / 独立缓存 / 截断 / override 往返
node tools/verify-shape-gate.mjs      # 输出形状段(v1.12.0):默认开 / 逃生开关 / 三段独立 / 技能同源 / 不重复注入
node tools/verify-gate-http.mjs       # host 半真起 http 服务:路由、两开关正交、异常输入
node tools/verify-gate-client.mjs     # client 半真渲染:磁盘 → 路由 → STORE → DOM(含抽屉展开态)
node tools/verify-ui-appearance.mjs   # 外观引擎 + 置顶跟随滚动选条 + chip 点击语义 + 对话页宽度钉法
node tools/verify-host-width.mjs      # host:字段钳制 + 从底图工坊的一次性迁移(临时 DSH_HOME)
node tools/verify-gate-truncation.mjs # 规则截断:原/留长度 + 显式标记 + 边界与两个 HTTP 响应契约
node tools/verify-save-token.mjs      # 省 token(v1.13.0):嵌套挂载面、expand 工具、字节往返、/cc/st 路由

「省 token」(v1.13.0)的断言分两处:host 半在 verify-save-token.mjs(真压缩一份 15,862 B 的 工具输出 → 6,526 B,再用 save_token_expand 逐字节还原比对;无 spillStore 时原样透传; /cc/st/api/* 三个端点含 404 与开关键名),client 半在 verify-gate-client.mjs 的 I 组 (喂夹具渲染纯视图:KPI 换算 / 字节条归一 / 空态 / spillStore 不可用降级 / 点击回调送出正确的键与值)。 取数那半步(useEffect 里的 fetch + 2.5s 轮询)在 SSR 套件里跑不到(renderToStaticMarkup 不执行 effect),1.13.2 起由 verify-token-lifecycle.mjs 用真实 React 挂载补齐:取数、竞态、点击回调、卸载中止、 超时、重试、后台暂停与前台恢复共 23 条;视图因此拆成独立的纯函数 renderSaveTokenView(组件 SaveTokenCard 负责连接取数与 视图,internals.SaveTokenView 是它的旧名别名)。

本机自检:改 client 半不必重启 DSH(v1.13.1 实测)。__DSH_BOOT__.entries[].rev 是整包一次修订 (本机 66 个条目共用前缀 d168e781a0c293b0- + 各自序号,不是逐文件哈希):内容一变前缀就变,所以 改完 client.js 存盘、刷新页面即加载新代码,还能用 fetch('/plugins/??<id>/client.js&rev=<新 rev>') 读出自己刚加的字符串自证。host 半相反: index.js / save-token-host.js 只有 name/inject/group 变了才重新 import,改函数体 必须重启 DSH Desktop。但"白屏"与"跑不到"是两回事:client 半的新代码即使加载成功, 渲染期抛错在 production React + 槽的错误边界下一个字都不打(1.13.0 就是这样白屏的), 所以排障第一步是刷新后把 console.error 钩住、或直接看面板里 PageBoundary 是否吐了堆栈。

「自动审查」的断言分挂在两处:保存行为(逐一触发每个设置控件,审查开关走独立接口, 普通设置只提交改动项,防止旧快照覆盖其它接口的最新值)与 client 渲染(八个分区标题名字齐全且顺序正确、页面里不许出现圈符编号)。ocr 探测本身是 子进程调用,不进套件(CI 上没有 ocr,且它属于"环境事实"而不是逻辑);改探测逻辑时手工跑一次 node -e "import('./index.js').then(h=>h.probeOcr().then(console.log))" 看结果。

浏览器侧(会往 tools/*-out/ 落 HTML/JSON/PNG,已在 .gitignore 里):

node tools/probe-userrow.mjs          # 气泡贴文字 / 复制键在气泡右侧轨道 / 上下留白对称(跑的是 client.js 里的真函数源码)
node tools/cc-appear-probe.mjs        # 钉顶底衬形态与模糊度:--dump-dom 让页面自量自报
node tools/cc-fixture.mjs             # 面板朝向与 portal:插槽/portal × 旧朝向/新朝向
node tools/cc-appear-fixture.mjs      # 同一批判定的 CDP 版

脚本里的默认路径是本机(Windows + DSH Desktop)的绝对路径,换机器用环境变量覆盖即可: DSH_CC_PLUGIN(本插件目录)、DSH_APP_MODULES(宿主 node_modules)、DSH_CHAT_BUNDLE (dsh-client-ui-chat/lib/client.js)、DSH_CC_INDEX、DSH_TOOL_HOME(临时 home)、 DSH_TOOL_OUT(夹具输出目录)、CHROME(Chrome 可执行文件)。 verify-gate-client.mjs 里对底图工坊的交叉断言在找不到对面插件时会自动 SKIP(DSH_BGA_CLIENT 可指定)。

夹具里量出来的关键数字(写在这里,下次改动好比对是否退化): 旧朝向面板可见比例 0.021(超出视口底部 411px);新朝向完整可见。 钉顶:position:sticky、top:0px、被钉元素 y:0 h:84、travelPx:676、滚动 1298px 后仍在滚动区顶; 透明:开 rgba(0, 0, 0, 0) / 关 rgb(47, 47, 52)(后者是宿主 --dsw-specific-bubble 的真值)。 底衬(2026 形态,cc-appear-probe.mjs 实量,夹具视口 1280 / 列宽 748):整行 rowBg rgba(0, 0, 0, 0) + rowBackdrop none(左侧干净);::before 底衬 v1.4.2 起按该条提问实测宽度:夹具里 411px 宽的气泡 ⇒ --cc-pin-w ≈ 423.4px (= 411 + 根字号 15 × .8,向上取整),对整行 748px ⇒ 左边留出 ~324px 不糊;短句提问则底衬跟着变短。 (v1.4.2 之前是"定长":423.4px = 748×.55 + 12,短消息时明显比气泡宽;v1.4.0 之前是 537.094px = 748×.702 + 12。) border-radius:16px; position:absolute / z-index:-1;右缘 right:-6px 与气泡右缘差 6px; --cc-pin-blur 未设 ⇒ blur(10px),设 0px ⇒ blur(0px),设 20px ⇒ blur(20px)。 注意夹具的坑:最后一条提问下面若没有长回答,sticky 没有移动量,量出来会误判成"没钉住"; --dump-dom 那条还必须给每个状态独立的 --user-data-dir,否则后启动的实例会把活儿交给 已在跑的浏览器进程、自己退出,dump 出来就是空文件。

卸载 / 回退

  1. 关闭会话守则 / ponytail / 输出形状三个开关即可让对应规则段消失(空段在渲染时被丢弃)。 v1.14.0 起不再需要为「省缓存」做任何还原动作 —— 压缩接管已整条移除,它当年可能写进 agent.cordis.yml 的 # managed by dsh-cache-control config 块也随该文件一起不存在了。
  2. 在 profile 移除依赖与 bundle 项、删除 junction;或 dsh plugin --profile web remove dsh-cache-control。
  3. 重启应用。插件停用/卸载后不残留任何行为改动(gate.md override 与 settings.json 是数据,需自行删除)。

版本与变更记录

下面这份清单停在 v1.7.x(1.8–1.12 的条目都在 CHANGELOG.md 里,没往回补)。新增条目往头上加。

  • v1.14.0(删掉「省缓存 · 压缩策略」整条写盘路径)—— 详见 CHANGELOG.md 的 1.14.0 条目与上面 「压缩接管:为什么删掉」。要点:DSH 0.1.7 起 agent preset 不再是可编辑的 agent.cordis.yml (安装里已无该文件,包由 dsh-agent-presets 拆成 dsh-agent-preset + dsh-agent-preset-registry), 接管没有落点、每次开设置页必然 500 ⇒ 删除读组装文件 / splice / 备份还原 / boot 对账 / GET·PUT 的 applied·backupAt·hasBackup,以及设置页那张卡与 chip 的「省缓存」段;compactionBackup 保留为只读历史数据。分区 9 → 8、chip 4 段 → 3 段、DEFAULTS 16 → 12 个键。

  • v1.13.1(修 1.13.0 的回归:设置页点「会话策略」整页白屏)—— 详见 CHANGELOG.md 的 1.13.1 条目。 要点:SaveTokenCard 把纯视图误写为元素调用 h(SaveTokenView, { d, … }),视图签名是 (夹具, 回调), props 对象被当夹具 ⇒ 读 d.flags.expandTool 抛错 ⇒ CacheControlPage 整棵树消失;production React 在槽的错误边界里不打日志,所以"一片空白而且控制台干净"。改为同口径的位置调用 + 新增 PageBoundary (抛错把堆栈显示在面板里并 console.error)。verify-gate-client 99 → 101 条(两条源码守卫: 调用口径、错误边界),版本 → 1.13.1。

  • v1.13.0(并入原 dsh-plugin-save-token;本插件第一次有模型工具)—— 详见上面「省 token · 结构化压缩」 与 CHANGELOG.md 的 1.13.0 条目。要点:save-token-host.js / save-token-core.js 两个文件原样入库 (MIT,NOTICE 有署名与删改清单),以 ctx.plugin() 嵌套挂载;上游 compaction assist 整段删除 (与「省缓存」抢同一个引擎),上游 composer.dock 小条不装(那个槽位是 dsh-bill 的); 新增工具 save_token_expand 与 /cc/st/api/* 三条路由;files 补两个文件、版本 → 1.13.0; npm test 10/10 通过(新增 verify-save-token.mjs 40 条 + verify-gate-client I 组 19 条)。

  • v1.7.0(把"隐藏拖拽条"拆成两个开关:两竖杠与侧栏分隔条分开管)

    • 起因:v1.6.0 的 hideResizer 一条开关按 cursor:*-resize 语义标记,实测会同时命中三类元素: 会话区左右两条宽度把手(._8JRpoa_widthHandle,就是"误触冒出两竖杠、拖了没反应"的那两位) 与 AppFrame 的侧栏/详情栏分隔条(._1tdjgG_handle,8px 宽)。前两位拖了确实没反应, 但分隔条是能用的(拖它会改侧栏宽度)——一个开关把死控件和活控件一起藏了(用户 2026-09-14 要求拆开)。
    • 改法:hideResizer 语义收窄为只管带 data-width-handle 属性的宽度把手(存量盘上值不动, 升级后"两竖杠仍被藏、分隔条自动回来");新增 hideDivider(默认关)管分隔条。 两类共用 cursor 含 resize 的语义识别,再用宿主写在把手元素上的 data-width-handle 数据属性区分 (它不是 CSS-module 哈希,耐升级);两套标记各一个属性(data-cc-hide-resizer / ), 各一条 CSS,互不牵连:关一个开关不会把另一类也撤了。 与 同样是 ,旧 host 重启前只禁用新开关,不影响已有的。

说明:本插件只写 $DSH_HOME/dsh-cache-control/* 与 standard preset 的 compaction-basic 行。 任何替你改动运行时开关值的行为都应算作越界——v1.1.0 曾直接写入过 gateEnabled: true,已记在此处。

说明与限制

  • 会话守则是软约束:它让规则每请求都在提示词里、且不被压缩稀释,但不产生技术硬拦截 —— 模型仍可违反。 真要拦截得走工具层/审批钩子,那是另一件事。
  • 修改点位于打包目录(app 安装的 node_modules,profile 以 junction 指向它); 若 DSH 升级重建该文件,插件启动时会自动对账并重新应用当前设置(写回标记行)。
  • 浏览器端刻意不编辑组装文本;本插件的“编辑”由其 host 进程完成,页面只有开关、滑杆与规则编辑器。
  • 数值换算显示用 ROUTED_CONTEXT_WINDOW = 1_000_000 这个常量(当前路由 qwen3.8-flash 声明的窗口 也是 1,000,000,所以对你这台机器是对的);若换到别的窗口的模型,界面上的 token 数会失真, 但引擎侧是比例式阈值,实际触发点仍按窗口同比变化。token 数为 token-meter 的估算口径。
  • v1.2.0 起 chip 固定为三段:省缓存 [开/关] | 提问 [开/关] | ▾,前两段点击即切换、 ▾ 弹滑杆与规则面板;标签不随状态改名(v1.1.0 那套"两个都开就叫会话策略"已去掉)。
  • tools/verify-session-gate.mjs 的既有失败已于 v1.9.4 修复并纳入 CI(此前记录:从宿主包取 FIRST_PARTY_SECTION_ORDER.DEPLOYMENT_PERSONA,而该导出名从未存在 ⇒ 段序断言处崩、后面 20+ 条从没跑过)。 修法见 CHANGELOG 1.9.4:段序判据改读已解析包的源码文本数值(persona=0 / plan=500), preset 依赖换成仓库内最小夹具。这段留在这里是提醒:测试脚本与宿主导出脱节时,崩在中途的套件 会伪装成"只坏了前半截" —— 排除理由里写"既有失败"之前先数一下它到底跑到了第几行。
try/catch
warnOnce
document.fonts.ready
  • 设置页「气泡置顶」卡里带一行底衬实测读数:被钉元素有/无、--cc-pin-blur、 getComputedStyle(el,'::before').backdropFilter、底衬宽、会话字号 +「重读」按钮 ⇒ 以后"看起来失效"能当场分辨是哪一种成因。
  • 只处理纯文字气泡;data-cc-fit 记签名,流式输出不会每帧重排;停用插件时 stopFitWatch() 把 stack.style.width / --cc-tail-* / data-cc-fit 全撤干净。
  • 时间戳零占位:平时 opacity:0 却仍占位 ⇒ max-width:0;padding:0;overflow:hidden, :hover 才展开(展开后的内边距也是 em)。
  • 可调点:userWidthPct(设置页「提问气泡宽度」滑杆,30–100% 消息列宽,默认 58, 写 --cc-user-width-pct,只管普通提问气泡;v1.16.7 新增;v1.16.9 曾改成会话区、 v1.16.11 按用户实测改回消息列**)、 pinWidthPct(设置页「钉顶气泡宽度」滑杆,同样 30–100% 消息列宽,写 --cc-pin-width-cap, 只管被钉住那条;v1.16.12 起两个滑杆才真正分开 —— 此前它们共用一个变量, 于是"提问气泡宽度"实际在控钉顶、普通气泡没有 UI 可调)、 --cc-col-w = 消息列实测宽(气泡宽度基数,已含「对话页宽度」% ⇒ 两个设置相乘)、 --cc-area-w = 会话区实测宽(同一函数 measureWidthVars() 写入;量不到时 CSS 退回比例基数)、 --cc-tail-room-px(轨道宽 = 复制键离框缘的距离,JS 自动量、也可手动覆盖)、 FIT_ICON_H(仅量不到图标时的兜底高度)。 v1.16.10:USER_BUBBLE_MAX_EM(41em)已删 —— 每个气泡的宽度只有各自那个百分比旋钮, 之前它是 min() 的第二条上限,导致百分比拉到 100% 也只到 ~645px。钉顶底衬宽度自 v1.4.2 起按这条提问的实测宽度**写入 --cc-pin-w(量不到才退回 消息列宽×.55 + .8em)。
  • data-cc-hide-divider
    dividerReady
    resizerReady
    独立能力位
  • 顺带补漏:tools/settings.mjs(导出/导入)的字段名单停在 v1.4.x,hideResizer 从来不在里面 —— 导入一份开着把手隐藏的设置会被"丢弃未知字段"静默抹掉。现在 hideResizer / hideDivider 都在名单里。
  • 验证:verify-panel-and-resizer 从 19 条扩到 25 通过 / 0 失败(新增:只开一个开关时另一类 不被标记、关一个不撤另一个、能力位单独禁用、isWidthHandle 分类、setHideDivider no-op 路径); verify-settings-payload 9 通过(新增 hideDivider 载荷对齐断言);verify-host-width 全绿 (sanitize 键齐全断言扩到 hideDivider);npm test 套件 4/4 通过。
  • v1.6.2(修"截断标记判错":规则被砍了,界面却说没截断)

    • 症状:会话守则规则超过 6,144 字节时会被截断,但界面不显示"已截断",用户以为整份规则都进了 提示词 —— 实际超出部分从没被模型看到。
    • 根因:host 的截断函数只返回一个字符串,界面判断"有没有截断"是靠结果长度猜的: truncated: bytes >= GATE_MAX_BYTES。而截断后的长度必然小于上限(末尾那句省略提示算在内 也只有 5.6–6.1 KB 一档,且随原文的字节/字符比浮动)⇒ 该判据对已截断的文本恒为 false。 反方向也错:原文恰好 6,144 字节时文本原样返回、bytes === maxBytes,它又把没截断的判成截断。
    • 修法:截断函数 truncateBytes() 改为直接返回全部事实 { text, originalBytes, keptBytes, truncated },调用方一律读 truncated;gateMeta 与 PUT /cc/gate.json 的响应原样带出这三个事实(bytes/maxBytes/lines/text/source/enabled 等老字段一个不动,老客户端不受影响);界面标签与提示改成 「已截断:原 N B → 保留 M B」+「你的规则被截断了:原文 N 字节,实际注入 M 字节(超出 X 字节未进入提示词)」。
    • 同类第二处:全仓库再找了一遍"靠长度/结果反推状态"的写法,结论是只有这一处是缺陷。 保留的同类写法都有正当理由,逐条记录在这里:readBody 用显式 overflow 布尔标记(正确做法); truncateBytes 里的 buf.length <= max 与界面编辑框的 draftBytes > gateMaxBytes、 tools/settings.mjs 的"导入前预警"都是输入侧判据(比的是"待处理文本自己 vs 上限", 不是在反推已经发生的截断);client.js 的 atBottom = scrollTop >= max - 1 是位置判定 (滚动几何的即时谓词),与"用产物长度推状态"不是一回事。
    • 回归:新增 tools/verify-gate-truncation.mjs(v1.6.2 时 31 通过 / 0 失败;v1.9.0 把上限 放宽到 16 KB 后,样本长度与期望值改为相对上限生成,现为 30 通过 / 0 失败 —— 本条其余 数字是当时的记录):① 超长(中文 19,998 B / ASCII 10,000 B)⇒ truncated:true 且 originalBytes/keptBytes 齐备(中文那条正好复现审计给的 5,839 B);② 恰好 6,144 B ⇒ false 且内容逐字节原样;③ 6,143 B ⇒ false;④ 空/极短 ⇒ false 且内容原样;⑤ 六种输入的"标记 / 原文长度 / 保留长度 / 实际内容"四者互相对得上(没有"标记为假 但内容变短"的样本);⑥ 老字段名齐全;⑦ 两个 HTTP 响应(GET /cc/gate.json、GET /cc/settings.json、PUT /cc/gate.json)都带标记且与文本一致;⑧ 界面接线(读标记、不自己比长度)。 另有两条照妖镜断言直接把旧判据钉死:同一个样本上断言"bytes < maxBytes 而标记为 true"与 "bytes === maxBytes 而标记为 false"。verify-gate-client.mjs 从 67 条扩到 72 条 (新增 5 条:真渲染出的 HTML 里出现「已截断:原 19998 B → 保留 5839 B」、那句人话含两个字节数、 用警示色类、数字与 host 响应一致、未截断时一个截断字样都不出现)。
    • 反向验证:把新套件用 DSH_CC_INDEX 指向 git show HEAD:index.js 那份改动前实现 ⇒ 25 失败 / 6 通过(退出码 1);指回本仓库这份 ⇒ 31 通过 / 0 失败。这证明断言真的盯着这个缺陷 而不是"假绿"。
    • 验证数字:本仓库套件全绿 —— verify-gate-client 72、verify-gate-http 43、 verify-gate-truncation 31、verify-host-width 51、verify-ui-appearance 50、 verify-panel-and-resizer 19、verify-settings-payload 8,全部 0 失败; verify-session-gate 仍为既有失败(见文末"说明与限制",与本轮无关)。
  • v1.6.1(修一个破坏性配置缺陷:保存外观开关会抹掉用户自己的压缩配置)

    • 症状:只改 ③ 气泡置顶 / ④ 对话页的任何一个外观值(钉顶、气泡透明、模糊度、宽度、 隐藏拖拽条),standard 组装文件就被重写一遍;如果当时总开关是"关",那一行块里 用户自己写的压缩配置会直接消失(回落到 DSH 出厂默认 0.8 / 0.16)。
    • 根因:保存入口 PUT /cc/settings.json 无条件 applyToStandard(settings),而 applyToStandard 在 enabled === false 时执行 spliceCompactionRow(text, null) —— 也就是"删掉那一行块里的 config"。链路与 tools/verify-host-width.mjs 里的断言一一对应。
    • 修法(三条,全部有断言):
      1. 只有压缩字段变化才碰那个文件:compactionFieldsChanged() 只看 enabled / triggerPct / retainPct / auto;外观/门禁字段的保存完全不触碰组装文件 (响应里 touched:false 即"这次没碰")。
      2. 接管前先备份、关闭时还原:首次接管时把接管前的整篇原文存进 settings.json.compactionBackup = { text, at };关闭开关时优先逐字节还原这份原文, 还原后清掉备份(下次再开 = 重新接管、重新采备份)。只有没有备份可还原时才回落到 旧的"摘掉受管 config"。受管行的识别沿用原有 # managed by dsh-cache-control 标记,没新造标记。
      3. 幂等:保留 next === text 早退 —— 重复保存同一份设置不会产生第二次写盘; 重写行块时只替换"标记 + 其后的 config 块",name: 与用户手写的注释原样保留。
    • 修的过程中被断言抓出来的两个次生问题(都已修,都有断言):
      • CRLF:原来 split('\n') 把 \r 留在行尾,config: 的整行比较会失败 ⇒ 旧 config 不被替换、 反而是又追加一份(YAML 重复键)。改成 split(/\r?\n/)。
      • 未受管时也追加:首次接管(文件里本来就有用户的 config: 块)时,旧 config 没被摘掉 ⇒ thresholdRatio 出现两次。现在"行块末尾的 config 块"一律整段替换。
    • 回归:tools/verify-host-width.mjs 从 17 条扩到 51 条(本次新增 34 条,走真 HTTP 路由, DSH_HOME 指临时目录):① 只改外观(7 个字段一次提交)⇒ 该文件哈希与 mtime 都不变; ② 改压缩字段 ⇒ 确实被改(哈希变化、thresholdRatio 只出现一次、用户注释仍在、备份 === 接管前原文); ③ 重复保存同一份设置 ⇒ changed:false + 哈希/mtime 不动;④ 关闭 ⇒ 逐字节还原、 用户原有的 0.66/0.11 回来、备份被清、再关一次不再写盘;④b 无备份回落路径; 启动对账(开关=开但文件无受管行 / 开关=关但文件有受管行)两条路径同样受这套语义约束; ⑤ sanitize 带出 compactionBackup(任何一次写盘都不许吃掉它);⑥ splice 幂等与纯函数性。
    • 验证数字:verify-host-width 51 通过 / 0 失败、verify-gate-http 43(新增 3 条: 备份===接管前原文、只动门禁的保存 touched:false、关压缩逐字节还原 + 真实 preset 逐字节未变)、 verify-gate-client 67、verify-ui-appearance 50、verify-panel-and-resizer 19, 全部 0 失败;verify-session-gate 为既有失败(见文末"说明与限制")。
  • v1.6.0(两个独立改动,同一个文件所以同一笔提交:修一个真 bug + 加一个开关)

    • 修:滑杆面板 ▾ "点不开"(用户 2026-09-14 反馈)。不是点击没接上 —— 点了 aria-expanded 会变 true、DOM 里也有 .cc-panel,但它被摆到屏幕外(实测 rect.y = 33554004、 内联 bottom: -6.71e7px)。根因是那次"一次性校正"的 effect 量纲混用:期望值写成"距视口底部的距离" (vh - c.top + 6),实测值 r.bottom 却是视口 Y 坐标,两者相减恒不为 0;而 patch 又把 dy 累加回 bottom、依赖数组里还含 pos.bottom ⇒ 每帧误差翻倍,二十来帧就发散到 -6.7e7px。 修法:定位数学抽成纯函数 computePanelPatch()(两边统一用视口 Y,一次补到位即收敛), 并加 ±400px 量级闸门 —— 任何不可信的测量只能被忽略,不可能再把面板丢出屏幕。 回归:新增 tools/verify-panel-and-resizer.mjs(19 通过 / 0 失败),把"已对齐返回 null / 有偏移一步到位 / 一步后收敛 / 离谱值被闸门挡掉 / NaN 挡掉 / 翻转分支同量纲"全部锁住。
    • 增:④ 对话页新增 hideResizer(默认关),可选隐藏 DSH 原生分栏把手,理由与实现见上面 ④ 那节。 host 侧 sanitize 只负责原样存取;client 侧用一个独立能力位 resizerReady 判断 "host 认不认识这个键",不并进 appearanceReady —— 否则旧 host 下会把已有几个外观开关一起禁用。
    • 验证:本仓库套件全绿(verify-ui-appearance 50、verify-gate-client 67、verify-gate-http 40、 verify-panel-and-resizer 19、verify-host-width 通过;verify-session-gate 是既有失败, 见文末"说明与限制")。
    • 顺带:README ④ 那节的表格还写着 640–3840px(v1.5.0 已改成百分比却没同步),一并改正。
  • v1.5.0(④ 对话页:宽度单位 px → 百分比)

    • 起因(用户 2026-09-12 原话):"改成百分比,具体的数值不仅会随着全屏或是缩小有变动, 还会因为显示器的比例出现不协调。"固定 640–3840px 在全屏/缩窗时不跟着走,换显示器比例就失配。
    • 改法:chatWidth 语义从 px 变成 30–100 的百分比,三个变量写同一个 P% —— --dsh-chat-content-width / --dsh-composer-card-max-width / --dsh-chat-user-width 都写 P%(:root 兜底那条同样写 %);滑杆 30–100、步长 1,快捷键改成 60/70/80/90/100%。
    • 真浏览器实测(本机会话区 clientWidth 1139,在真会话里量消息列与输入卡): 80% ⇒ 消息列 860px、输入卡 886px;60% ⇒ 645 / 664;100% ⇒ 1075(满宽)/ 1107。 即三个变量都是"可用内容区的 P%"(100% 时 1075 = 1139 − 两侧 32px 内边距), 而输入卡恰好 = P% ×(内容区 + 32) ⇒ 卡片永远比消息列宽 32×P 像素 —— 与 px 时代"+32px 出挑"的关系完全一致,只是整体按比例缩放。 (一度以为会"复合两次"导致卡片过窄:那是在空会话页量的,那页的 composer stack 宽度不同; 真会话里不复合。这条记在这里,免得下次又被空页面误导。)
    • 迁移:盘上的旧 px 值(>100,例如你现在的 900)不再按 px 用,一律落到默认 80% —— 900px 在本机 1139px 会话区里正好≈79%,观感等价;由 normalizeChatWidth() 在 host 与 client 两端同口径处理(sanitize / clampChatWidth), 底图工坊那次一次性迁移(migrateFromAtelier)也一并走这条口径。
    • 验证:verify-host-width.mjs 全绿(新增"60 原样 / 10→30 / 900 与 3840 旧 px → 80"四条); verify-ui-appearance.mjs 50 passed / 0 failed(新增"三个变量都是 90%"与"旧 px 1600 → 80%"); verify-gate-client.mjs 67 passed / 0 failed。
    • 顺手修了 verify-gate-client.mjs 里一条早就假失败的断言:它要求底衬规则含 width:calc(min(,而 v1.4.2 起那层已经是 width:var(--cc-pin-w, calc(min(…))) (早于本轮的单位改动,与本轮无关,只是这次跑测试才发现)。 另把 verify-host-width.mjs 里"删掉文件再看它不存在"的断言改成哨兵文件写法 —— 本机沙箱会把 fs.rmSync 拦成空操作(实测 rmSync 之后 existsSync 仍为 true), 那种写法会假失败,改成"跑完看哨兵内容有没有被改写",反而更直接地证明"不写盘"。
  • v1.4.2(钉顶底衬改"实测宽度" + 长文限高)

    • 底衬宽度不再"定长":新增 updatePinPlate(),在钉住哪一条确定后、以及每次气泡重排 (fitUserBubbles)结束时,量出被钉行里 [class*="_userRow"] 的实际像素宽 + 根字号 × .8 呼吸位,写进该行的 --cc-pin-w(短句 ⇒ 短底衬);量不到就 removeProperty, 由 CSS 里的旧上限 min(列宽×.55, 上限) + .8em 兜底。
    • 长提问钉顶不再无限撑高:气泡本体 max-height:38vh + overflow-y:auto + overscroll-behavior:contain + scrollbar-width:thin —— 超出时在气泡内滚,滚到底才交还会话流。
    • 只动 client.js 与版本号;host 半、路由、settings 字段均未变,不需要重启(客户端热更新即可)。
  • v1.4.1(根因修复 + 尺寸自适应)

    • applyAppearance 每段各自 try/catch + warnOnce:一处抛错不再连带把钉顶/贴合观察器全部 跳过(那会让"底衬模糊度"看着像失效);首屏补量两次 + document.fonts.ready 后清签名重算。
    • 气泡量测不再按 children.length 整条跳过(带 @路径 <span> 的提问曾被全漏 ⇒ 框宽退回固定 上限),改为只跳过真含 img/video/canvas 或内嵌块的气泡;轨道宽与纵向对齐改为实测图标行。
    • 尺寸全面 em 化:气泡内边距/圆角/图标尺寸/轨道宽/上限(USER_BUBBLE_MAX_EM 取代 px 常量)/ chip 徽标与标签位移一律 em 或实测值,跟随会话字号与页面缩放自适应。
    • 设置页 ③ 新增底衬实测读数自检行 +「重读」按钮;提问气泡图标区隐藏宿主 tooltip。
  • v1.4.0(改名 / 气泡微调 / 小数档)

    • 分区名改:② 门禁 → ② 会话守则(chip 工具提示、设置页、规则 md 标题同步); ③ 外观 → ③ 气泡置顶。
    • 我的提问气泡微调(始终生效):宽度上限系数 .702 → .55;上下 padding 压到 6px (行距/字号不动);时间/复制那一行从气泡下方挪到气泡文末右侧同排,放不下才换行; 钉顶底衬公式同步改用 .55(实测列宽 748 ⇒ 气泡 411px / 底衬 ~423.4px)。
    • pinBlur 支持 1 位小数:<3.5 按 0.1 步进(可到 1.3 / 1.5 / 1.7),≥3.5 按 0.5 步进; host sanitize 同口径保留 1 位小数。
  • v1.3.0(本轮四项)

    • 钉顶底衬改形态:从"铺满被钉住的整行"改为画在 ::before 上的定长圆角矩形, 整行不再有 background / backdrop-filter ⇒ 会话列左侧不再被模糊;新增 可调模糊度 pinBlur(0–24px,写在 <html> 的 --cc-pin-blur 上,拖滑杆不重注入样式)。
    • chip 的「开 / 关」徽标改纯透明:.cc-chip .cc-badge 三条规则去掉背景与描边, 状态只靠文字颜色;面板内的同名徽标不受影响。
    • 名称一律压到 2–4 字:设置页导航条目 会话策略 · 省缓存/守则 → 会话策略; 分区标题 → 省缓存 / 会话守则 / 气泡置顶 / 对话页;快捷面板分区头同步缩短。 长说明没删,都挪到各卡正文与 hover 里。
    • 接住 bg-atelier 的「对话页固定宽度」:新增 ④ 对话页卡(开关 + 640–3840px 滑杆 + 常用宽度快捷键 + 会话根重建时的补写观察器),并由 migrateFromAtelier() 在启动时 一次性把 $DSH_HOME/dsh-bg-atelier/settings.json 里的 chatWidth / chatWidthEnabled 搬进自家 settings.json(只在自家缺这两个字段时读对方,之后不再读)。
    • sanitize 新增三字段钳制:pinBlur 0–24(v1.4.0 起保留 1 位小数)、chatWidth 640–3840、chatWidthEnabled 只认 true。
    • 验证补三条:cc-appear-probe.mjs(无 CDP 的浏览器实量,本沙箱里唯一跑得通的那条)、 verify-host-width.mjs(钳制 + 迁移幂等)、verify-bga-trim.mjs(对面插件的回归)。
  • v1.2.0

    • 新增 ③ 会话区外观:pinLastUser(最近一条「我的提问」钉顶)、clearBubble(我的气泡背景透明)。 纯客户端样式 + 运行时打标记,设置项持久化在同一个 settings.json。 两项形态按用户选定实现:钉在会话滚动区顶部、钉住条目自带半透明毛玻璃底衬。 开关默认 false,不由插件替用户打开。
    • chip 改为三段可点:省缓存 [开/关] | 提问 [开/关] | ▾。点前两段直接切对应开关, ▾ 才弹滑杆与规则面板(不用进设置页);每段 hover 有独立介绍。
    • 修"点开面板看不见":旧写法 top: chip 下沿 + 6,而输入条贴在视口底部 ⇒ 面板整块开到屏幕外。 无头 Chrome 夹具实测(03-调试临时/cc-fixture.mjs,三个 variant 只动「父级」与「朝向」两个变量): 旧朝向可见比例 0.021(超出视口底部 411px);改成贴 chip 上沿 + max-height 锁进可用空间后完整可见。 同时把面板 portal 到 body(与宿主 dsh-client-ui-attachment 同源做法)——实测它不是主因: 朝向正确时留在插槽里也完整可见;portal 消除的是 contain:paint/"裁剪盒恰是包含块"这类组合。
    • 新增两道能力/状态闸:loaded(没成功读到设置就拒绝保存,防止默认值盖掉真实配置)、 gateReady / appearanceReady(旧 host 不认识的字段一律禁用对应开关,避免写盘被抹)。
    • 修 readBody 超限时 req.destroy() 打断连接的缺陷(客户端 ECONNRESET + Windows libuv 断言),改为排空后回 400。
  • v1.0.0 仅省缓存(压缩策略)单开关。