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.

Cache Control — DSH Plugin for DeepSeek Harness
DeepSeek Harness Plugin Hub
ProfilesPluginsCategoriesNewsDocsSign inManage Profiles
ProfilesPluginsCategoriesNewsDocsSign in
← Plugins
C

dsh-cache-control

Cache Control

Eight session-policy toggles (all settings-page names are 2–4 characters; **section titles do not include numbers**—numbering is positional, so inserting one card requires reordering all downstream references; this has already required rework in v1.10.2 and v1.11.0. Refer to a section by name direct

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#de473175828b86fbcacd3692ae71df523e5b687f
READMECompatibilityVersions

Description

Eight session-policy toggles (all settings-page names are 2–4 characters; **section titles do not include numbers**—numbering is positional, so inserting one card requires reordering all downstream references; this has already required rework in v1.10.2 and v1.11.0. Refer to a section by name directly). **When switching machines: you must restart DSH Desktop after installation; all toggles and rule overrides are in $DSH_HOME/dsh-cache-control (not tracked with the repository). After switching machines, reopen “Cache Saver / Session Rules / ponytail / Output Shape / Pin Bubbles / Conversation Page” (Output Shape is enabled by default and needs no action), otherwise it will appear as “installed but nothing happened”; “Cache Saver” modifies the preset line in $DSH_HOME and must also be reopened once. See README “Switching Machines: Portability and Required Manual Steps” for details.** Cache Saver rewrites the compaction-basic parameters of the standard agent preset (trigger point / percentage of the original tail retained / automatic compaction), affecting only sessions created afterward; Session Rules permanently injects the plugin’s session-gate.md rules into the system prompt (R1 independent assessment, R2 necessary questions, R3 fixed division of labor; R4 Output Shape itself is in shape-gate.md in the same directory and only retains the ownership declaration; v1.9.2 added R5’s four coding-behavior principles, v1.9.4 added R6 “verify before concluding,” v1.9.5 added R7 “execute cautiously,” and v1.9.6 further specified when R2 should ask questions), taking effect on the next request in all sessions; since v1.9.3, the settings-page toggle and button use the host’s official primitives (with bundled controls as a soft fallback); ponytail is a second permanently injected rule set structurally parallel to Session Rules (v1.10.0; coding discipline: the seven-level ladder / YAGNI / fix the root cause), with its own toggle and override file; Output Shape (v1.12.0) is a third permanently injected rule set, **merged in from the formerly separate dsh-output-shape plugin** (segment name dsh-output-shape:output-shape → dsh-cache-control:shape-gate, order remains 410; that plugin has been retired): ten response-shape rules plus six exceptions, **enabled by default** (it was enabled by default before the merge, so upgrades do not change behavior), with its own toggle and override file, and it also takes over the former two on-demand skills, i-have-adhd / ponytail (the skill text reads directly from the same rule file and does not consume permanent tokens); Automatic Review (v1.11.0) registers the **on-demand skill** auto-code-review, with not a single word entering the system prompt: each time, it queries the external ocr (alibaba/open-code-review, Apache-2.0) in delegation mode for “which files to review + which rules were hit”; the current model still makes the judgment and no API key is required. The interface displays the ocr detection result and honestly reports when it is not installed; this plugin neither bundles nor automatically installs it; Pin Bubbles pins “the last question that has crossed above the top edge of the session area” (it changes as scrolling moves between items), fits the bubble width to the text, places the copy button on the track to the right of the bubble, and allows adjustment of the backing blur; Conversation Page sets the session-column width to 30–100% of the available width (since v1.5.0, changed from fixed px to a percentage; it follows fullscreen/window resizing/display changes; the entire original dsh-bg-atelier “Background Atelier · Conversation Page” section was moved in, and old values are migrated automatically at startup), and can separately hide the two vertical bars in the session area (width handles that do nothing when dragged) and the sidebar divider (split into two toggles with independent capability flags in v1.7.0); Storage measures disk usage by purpose and provides “move to recycle bin”-style cleanup (v1.8.0). The eight items are independent. On the chip, each of the four permanent segments can be clicked separately (Cache Saver / Questions / Lazy Code / Shape); Automatic Review, Pin Bubbles, Conversation Page, and Storage are in the settings page.

Compatibility and provenance

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

DSH compatibility
*
Runtime surfaces
web
Release source
github
Registry updated
9/20/2026

Versions

1.12.0stable
9/20/2026
1.11.1stable
9/20/2026
1.9.7stable
9/20/2026
Show 5 more versionsCollapse versions
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.12.0
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
9/20/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 memory-context.

Contextdsh-contextA DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.Weknora@wxg-prc-cpg/dsh-weknoraWeKnora knowledge retrieval tools for DeepSeek Harness (dsh): semantic search, document reading and RAG/agent answers over your own knowledge bases.Memsearch Dsh@zilliz/memsearch-dshMemSearch plugin for DeepSeek Harness: shared markdown memory across agents, with capture, pre-step context injection, memory-recall skill, and a skill-candidate review panel.Memory@furongjun1999/dsh-memoryLingshu (Lingshu·líng shū) DeepSeek Harness plugin: a complete brain—long-term memory/knowledge flywheel/self-awareness/recursive reflection integrated with DSH, with conversations automatically distilled into the md_cg cognitive graph (md documents)

README

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

给 DSH Desktop(dsh 0.7.2-alpha,web profile)加八个互相独立的开关,设置页名称就叫 会话策略(4 字,八个分区标题 省缓存 / 会话守则 / ponytail / 输出形状 / 自动审查 / 气泡置顶 / 对话页 / 存储 一律 2–4 字,不带序号 —— v1.11.1 起去掉编号,理由见下面「为什么分区标题没有编号」): chip 上 省缓存 / 提问 / 懒码 / 形状 四段各自可点,自动审查、气泡置顶、对话页、存储全部走设置页。

省缓存 · 压缩策略会话守则 · 长期规则ponytail · 编码纪律输出形状 · 回复形状自动审查 · 按需技能
动的是什么standard preset 里 @deepseek-ai/dsh-compaction-basic 的 configsystem prompt 里一个常驻段(规则文本)system prompt 里的第二个常驻段(与守则同构、独立开关)system prompt 里的第三个常驻段(同上,但默认开)宿主 skills 目录里的一个条目(不进 system prompt)
生效时机之后新建的会话(preset 按组装文件 mtime 分代)所有会话的下一个 model step(含当前会话,无需重启)同「会话守则」同「会话守则」模型/用户调用技能那一刻
会被压缩冲掉吗—不会:压缩只折叠对话历史,system prompt 每请求重发同「会话守则」同「会话守则」不适用(压根不在 prompt 里)
代价无额外 token规则体积 × 每请求(含子代理/工作流子会话)约 1.3K token/请求 × 所有会话约 1.2K token/请求 × 所有会话(默认就开着)注册零 token;成本只在真调用时

「气泡置顶」与「对话页」是纯界面开关,只改样式与 CSS 变量,不动消息数据、不动宿主代码; 「存储」只做各用途占盘统计与"移入回收"式清理。 只影响 standard 之外的说明:会话守则走宿主全局提示词层,对所有 preset、子代理、workflow 子会话都生效。

「气泡置顶」与「对话页」是纯界面开关,只改样式与 CSS 变量,不动消息数据、不动宿主代码; 「存储」只做各用途占盘统计与"移入回收"式清理。 只影响 standard 之外的说明:会话守则走宿主全局提示词层,对所有 preset、子代理、workflow 子会话都生效。

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

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

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

省缓存 · 压缩策略

设置页(设置 → “会话策略”)与对话区快捷面板里各有一条独立开关:

  • 总开关:启用 → 把下列参数写进 standard preset 的 compaction-basic 行; 关闭 → 还原首次接管前那一行块的原文(v1.6.1 起;原文存在 $DSH_HOME/dsh-cache-control/settings.json 的 compactionBackup 字段里,逐字节回写)。 没有备份可还原时(例如手工删过 settings.json)才回落到"移除受管 config、恢复 DSH 出厂默认"。
  • 只有压缩字段会碰那个文件(v1.6.1 起):enabled / triggerPct / retainPct / auto 没变时,保存设置完全不触碰 standard 组装文件 —— 只改「气泡置顶」「对话页」的外观开关不会再把你 自己写在那一行里的压缩配置抹掉(旧版每次保存都无条件重写,这是破坏性缺陷)。
  • 压缩触发点:占路由模型上下文窗口的百分比,默认 25%(deepseek-v4-flash 窗口 1,000,000 tokens ⇒ 约 250k 触发)。
  • 保留原文尾部:逐字保留最近内容的窗口百分比(必须小于触发点),默认 5%(⇒ 约 50k)。
  • 自动压缩:auto 开关;关闭后不再自动压缩与溢出恢复,仅保留手动 /compact。

会话守则 · 长期规则

插件内的 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, 由本插件作为第三段常驻规则注入(详见下面「输出形状」一节)。别在这里再抄一份,否则同一套规则会被注入两遍。 要收回原文备份:session-gate.R4-backup-2026-09-16.md。
  • 怎么知道自己的规则被砍了:/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,上限 min(列宽×.55, var(--cc-user-bubble-max,41em)),右缘再留一条轨道 padding-right:var(--cc-tail-room,2.4em); 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)) 跟宿主字号走。字号变、页面缩放变,这些一起变 —— 不再有任何"某次量出来好看就钉死"的像素数。
  • JS 量一次(fitUserBubbles()):Range.getClientRects() 取逐行矩形 ⇒ ① stack.style.width = 最宽行 + 左右内边距(框贴文字;列宽变窄靠 max-width:100% 自动夹回); ② 实测图标行宽高 ⇒ 写 --cc-tail-room(轨道 = 图标行宽 + .45×图标高)与 --cc-tail-y (与最后一行同高)。变量必须写在 userRow 上:图标行是 row 的子节点,写到 userStack 上 继承不到 —— 这是复制键一度跑偏的直接原因。
  • 实测(列宽 1180、字号 15px):10 字 174px、30 字 444px、长文 579px;复制键距气泡右缘 13.1px(em 轨道,随字号缩放)且与末行同高;上下留白 8/8.1 相等;420px 窄容器不越框。
  • 两条踩过的坑(别改回去): ① 用 bubble.children.length > 0 判"含内嵌块就跳过",会把带 @路径 引用的提问(宿主渲染 成 <span>)整条漏掉 ⇒ 框宽退回"块宽 = 上限"的固定观感(就是"完全不动态"那次反馈)。现在 只跳过真的含 img/video/canvas 或某行矩形异常高(内嵌块)的气泡。 ② applyAppearance() 里任何一步抛错(例如常量改名)会连带把后面的观察器全跳过 ⇒ 被钉元素 不出现,看起来就是"模糊度失效"。现在每段各自 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)。
  • 可调点:USER_BUBBLE_MAX_EM(上限,默认 41em;测试缝 internals.setBubbleMaxEm / setBubbleMaxPx)、--cc-tail-room(轨道宽 = 复制键离框缘的距离,JS 自动量、也可手动覆盖)、 FIT_ICON_H(仅量不到图标时的兜底高度)。钉顶底衬宽度自 v1.4.2 起按这条提问的实测宽度 写入 --cc-pin-w(量不到才退回 的旧上限)。

底衬形态的三次选定: 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 才清空回收目录。 host 三条路由:GET /cc/storage、POST /cc/storage/clean、POST /cc/storage/purge。

安装

前提: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% 下的真实 preset / settings.json。

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,夹具化见下面那条)。

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% 真实 preset/settings ——换成仓库内最小夹具 + 「真实 home 存在才比对」。它 import 的 @deepseek-ai/dsh-system-prompt 走自己 devDependencies 里那份(createRequire 从仓库 package.json 解析,版本与本安装宿主一致),所以把 DSH_APP_MODULES 指到空目录仍 39/39。

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)。

未纳入 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 才需要去关。)

D. 「省缓存」改的是 preset,不是插件目录 它把参数写进 $DSH_HOME/profiles/**/standard/agent.yml 里 compaction-basic 那一行 (带 # managed by dsh-cache-control 标记)。换机器/换 profile 后,必须在新机器上再打开一次总开关 才会重新写进去;关闭总开关会把该文件还原成上次接管前的原文(v1.6.1 起; 备份在 $DSH_HOME/dsh-cache-control/settings.json 的 compactionBackup 里,不随仓库走, 所以换机器后新机器上关一次开关只回到它自己那份原文)。

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,管不到这个脚本,所以以后加开关记得三处都补。 ⚠ 「省缓存」不在这个文件里:它改的是 $DSH_HOME 里 standard preset 的那一行, 新机器导入后在设置页把总开关关一次再打开即可重新写入。

F. 换机后自查(30 秒)

node tools/verify-audit.mjs 2>$null; node tools/verify-host-width.mjs   # 期望全绿 / 无 FAIL
# 设置页应出现「会话策略」八个分区(标题无编号);对话页默认 80%(百分比,v1.5.0 起)

验证

回归与探针脚本都在本仓库 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 响应契约

「自动审查」(v1.11.0)没有独立套件,断言分挂在两处:payload 对齐(reviewSkillEnabled 必须 出现在主 PUT 载荷里 —— 这条正是 v1.6.0 hideResizer 那个"拨得动不落盘"bug 的守门人)与 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. 关闭省缓存总开关(把 standard 组装文件还原成接管前的原文 —— v1.6.1 起是逐字节还原, 不是删掉那一行);没有备份时手动删除组装文件里带 # 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.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 / data-cc-hide-divider), 各一条 CSS,互不牵连:关一个开关不会把另一类也撤了。dividerReady 与 resizerReady 同样是 独立能力位,旧 host 重启前只禁用新开关,不影响已有的。
    • 顺带补漏: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/// 等老字段一个不动,老客户端不受影响);界面标签与提示改成 「已截断:原 N B → 保留 M B」+「你的规则被截断了:原文 N 字节,实际注入 M 字节(超出 X 字节未进入提示词)」。

说明:本插件只写 $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 依赖换成仓库内最小夹具。这段留在这里是提醒:测试脚本与宿主导出脱节时,崩在中途的套件 会伪装成"只坏了前半截" —— 排除理由里写"既有失败"之前先数一下它到底跑到了第几行。
min(列宽×.55, 上限) + .8em
text
source
enabled
  • 同类第二处:全仓库再找了一遍"靠长度/结果反推状态"的写法,结论是只有这一处是缺陷。 保留的同类写法都有正当理由,逐条记录在这里: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 仅省缓存(压缩策略)单开关。