← 返回列表
⚠ 装前注意
受够了几十条工具调用占满屏幕?
基本兼容但装前注意:未发布到 npm registry,仅可从源码安装 · 最近上游提交 2026/9/17 · 已提供中文文档
综合分
37.7
GitHub 分
37.7
用户评分
—
★ Stars
12
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add Winter-And-You-Gone/dsh-turn-fold未发布到 npm registry,仅可从源码安装,改用 GitHub 源安装
数据截至 2026/9/18(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
安装兼容性检查⚠ 装前注意
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✗npm 包@winteries/dsh-turn-fold(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20.3 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
未发布到 npm registry,仅可从源码安装
验证方式:npm registry 存在性 + package.json 静态校验 · 最后验证 2026/9/18 09:26:57
用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
dsh-turn-fold
简体中文(默认) | English
受够了几十条工具调用占满屏幕?
也眼馋隔壁 Codex 的自动折叠?
那这个插件就是为你准备的。
DeepSeek Harness(DSH)纯插件,只负责折叠:
1. 步骤分组自动折叠:两个 text 之间的所有工具调用和 Think(含纯 Think 段)收成一个步骤折叠栏,默认折叠;运行中步骤折叠栏动态显示「正在运行 图标 工具名 · 描述 / 正在思考 图标 Think · 内容」(文字带 shimmer 光泽动效),下一个 text 出现后按工具类型分组显示详细标题(如「运行了pwsh」「读取了client.js」「编辑了index.js [ +12 -3 ]」),纯 Think 段闭合后显示「思考了N次」。
2. 运行中回合折叠栏:发消息即出现(0 秒占位,不等第一个 response),折叠栏实时显示耗时/首字/消耗token/tok/s/缓存命中率/待折叠步数,最右侧右对齐显示「第x轮」;折叠栏与内容之间有分隔线。
3. 整回合折叠:一轮回复完成后自动收成一个回合折叠栏(默认收起),最终总结只显示正文。
4. 手动展开/收起:点击折叠栏切换。
5. 新版本更新说明:每个新版本首次加载时右下角弹出一次更新说明(本地记录已读版本,不重复打扰)。
不修改任何 @deepseek-ai/dsh- 源码。
功能一:步骤分组自动折叠
text:先看仓库状态和改动规模: ← text 直接显示
┌────────────────────────────────────────────────────┐
│ › 正在运行 ⬢ Pwsh · Commit 1: core +tests │ ← 运行中:图标 + 工具名 + 参数摘要
└────────────────────────────────────────────────────┘
text:…… ← 下一个 text 出现
┌────────────────────────────────────────────────────┐
│ › 编辑了client.js [ +7 -7 ] 运行了2条命令 │ ← 段闭合:按工具类型分组显示
└────────────────────────────────────────────────────┘
- 段 = 两个 text 之间的内容:连续的工具调用与 Think 混排成一段(Think 不再打断分组),
含 text 的消息是段边界;text 正文始终在步骤折叠栏下方直接显示(官方渲染,唯一一份,
不参与折叠——DSH 把 think 和 text 放在同一节点,think 部分收进段、text 部分留在段外)。
- 默认折叠:步骤折叠栏始终默认收起(运行中也不例外)——运行中只显示 text 和步骤折叠栏行,
工具卡片/Think 内容点击步骤折叠栏才展开。
- 运行中动态标题:段未闭合(下一个 text 还没出现)时,步骤折叠栏显示段内最后一个节点——
工具调用显示「正在运行 图标 工具名 · 参数摘要」(工具图标复用官方 VARIANT_ICONS 映射,
如 Pwsh → API 图标、Read → 浏览图标、Grep → 搜索图标),
Think 显示「正在思考 图标 Think · 最新一行」(前缀 + 官方 Think 图标 + 摘要,摘要取
最新一行、横向自动滚动跟随末尾,内容随流式逐字推进);运行中标题文字带shimmer 光泽
扫过动画(灰色基调 + 高光流动,节奏为流动 1.8s + 停顿 2s,亮/暗主题各自配色)。
- 段闭合标题(按工具类型分组):下一个 text 出现后,按段内工具类型分组显示——
仅命令:运行了pwsh(单次显示工具名)/ 运行了3条命令(多次显示次数);
仅读取:读取了client.js(同一文件显示文件名)/ 读取了2份文件(多文件显示数量);
仅编辑:编辑了index.js [ +12 -3 ](单文件附加行数变更,从官方 diffs 数据读取)/
编辑了3份文件;搜索:搜索了2次;
混合时按「读取 → 编辑 → 搜索 → 命令」排序且命令始终在最后,
如 读取了client.js 编辑了App.tsx 运行了2条命令;纯 Think 段(段内无工具)闭合后显示
「思考了N次」(N = 段内 Think 次数)。
- 手动可展开/收起:点击步骤折叠栏切换;手动选择会覆盖自动规则。
- 失败命令标红:组内已有命令执行失败(工具结果 isError,含中断)时,折叠栏文字变红,
并在标题后追加失败数——仅单条工具调用失败显示「 —— 执行失败」(无条数),
多条工具调用时 1 条失败也显示「 —— 1条执行失败」、多条显示「 —— y条执行失败」。
失败统计涵盖段内所有工具类型(read/edit/search/命令都算)。
效果示意
段闭合标题:按工具类型汇总 + 编辑行数统计([ +11 -11 ],悬停括号 +N 变绿 / -N 变红):
![段折叠栏:编辑了 client.js [ +11 -11 ]](docs/images/segment-edit-stats.png)
标题里的文件名可点击复制完整路径:悬停变 DeepSeek 蓝 + 白色下划实线:
文件名悬停
功能二:运行中回合折叠栏 + 整回合折叠成一个回合折叠栏
[用户消息]
[▸ 耗时5分12秒 · 首字1.2s · 消耗12345token · 34tok/s · 缓存命中80.00% · 待折叠6步 第13轮] ← 回复开始即出现的回合折叠栏
───────────────────────────────────────────── ← 分隔线
[Think / 工具调用逐条加载…] ← 运行中默认折叠成步骤折叠栏
[最终总结正文] ← 无 Think 行,只有正文
[耗时 · token 脚注] ← 官方 turn-tail
- 发消息即出现回合折叠栏(0 秒占位):用户发送消息后立即出现回合折叠栏(耗时从 0 开始计时),
不等第一个 response——占位栏渲染在 user 消息正下方(官方「状态描述」行
"Deep diving..." 之上,与正式回合折叠栏同位置、交接无位移),第一条中间节点到达后
占位消失、正式回合折叠栏接替显示;
- 指标实时更新:回合折叠栏中的耗时秒数每秒走动(从回合 turn/start 起计时),
"消耗token"按随机间隔(默认 125~250ms)刷新且持续增长,tok/s 按已输出 token / 已耗时实时估算,
缓存命中率显示两位小数(如 80.00%),首字(TTFT)在第一个请求完成
(step settle)后即显示官方值(assistant-step 的 finalNode.timing:
firstTokenTime - stepStartTime),回合结束后切换为官方持久化聚合值
(turn-tail 携带的 ttftMs,来自事件日志,刷新页面不丢);仅当首个请求仍在流式时
用渲染时刻近似(回合启动到首个 assistant-step 渲染);
回合结束后全部指标切换为官方权威值(turn-tail 的 tok/s、turn/end 的精确耗时);
消耗token 亦切换为 turn-tail 携带的官方 tokenUsage 精确值(见下);
- 回合折叠栏最右侧右对齐显示"第x轮"(如 第13轮 / Turn 13,英文随 DSH 语言切换);
- "消耗token"持续增长动画:真实 usage 只在每个请求完成时到达,两次之间数字会
停住——运行中在真实基线之上叠加纯展示用的动画偏移,偏移按实际 tick 次数推进
(+1/+11 交替:个位每 tick +1、十位每 2 tick +1、更高位随进位自然走动),
tick 间隔 = liveTickMs × 随机数(liveTickJitter ~ 1,默认 125~250ms),
数字跳动节奏不规律,更像真实生成速率而不是节拍器;真实 usage 到达时只把基线校
正为真实值,偏移继续累计、数字只增不减。偏移封顶:上限 = max(500, 真实基线
× 10%)(CONFIG.liveTokenAnimMaxRatio / liveTokenAnimMaxFloor,比例与下限都置 0
即关闭增长)——长时间工具执行不会把虚构数字堆到万级、失真不可信,真实值到达时
上限随基线一起抬高。基准间隔和抖动分别通过 CONFIG.liveTickMs
和 CONFIG.liveTickJitter 调整;
- 消耗token 与官方统计同口径:回合结束后优先采用官方 turn-tail 携带的
tokenUsage(官方 deriveTurnTokenUsage 在持久化事件日志上折叠全部计费 attempt
的精确值,含被重试的请求;缓存命中率分母同为 prompt 侧总量 totalTokens - outputTokens);
节点累加路径(运行中基线、无 tokenUsage 时的回退)通过 nodes.values() 补采
visibility:hidden 的 assistant-step——纯 tool-call 的中间步骤(无可见 reasoning/text)
官方以隐藏节点结算、不进 locations.getTurn,只遍历可见节点会漏计(实测案例:
官方统计 175,844 vs 修复前 117,301,差值恰为一个隐藏步骤的 58,543);
- 滚轮式数字动画:运行中数值变化时,每一位数字独立"滚动"到新值(里程表/滚轮效果,
回弹缓动;动画时长按变化频率自适应:token 个位这类快速变化用略短于刷新周期的短动画
保证每拍完整走完,耗时秒数等慢速变化用 350ms 回弹滚动)——数字拆成逐位视窗、内部
竖排 0-9,视觉上像计数滚筒;完整文案另有 sr-only 副本,读屏/无障碍不受影响,
系统开启「减少动态效果」时自动退化为静态数字;
- 折叠栏下方常驻分隔线:回合折叠栏文字下方始终有一条 1px 水平细线
(颜色取官方 --dsw-alias-line-secondary token,随主题明暗自动适配),
收起/展开都显示,展开时同时充当折叠栏与内容的视觉分界;
- 一轮回复完成(输出最终总结、回合结束)后,回合折叠栏自动收起,本回合内所有 Think、
工具调用和上下文注入收进回合折叠栏,只保留最终总结消息和官方耗时/token 脚注可见;
(手动展开过的回合保持展开状态)
- 无工具调用也折叠:回合内只有上下文注入 / Think、没有任何工具调用时,同样收成
一个回合折叠栏(折叠栏显示耗时/token 指标,不显示命令数);
- 回合折叠栏显示本轮指标:耗时x时x分x秒(不足 1 小时只显示分秒,不足 1 分钟只显示秒),
首字x.xs,消耗xxx token,xxx tok/s,缓存命中xx.xx%,待折叠/已折叠N步(>0 时才显示;
运行中为「待折叠N步」,回合结束后为「已折叠N步」)」;某几项缺失时自动省略,
全部缺失才回退为「运行了 N 条命令」;字段之间用 · 分隔,右侧附「第x轮」;
- 点击回合折叠栏展开/收起整轮内容;重新打开历史会话时,已完成的回合同样保持整回合折叠;
- 折叠作用域不越过用户消息:回合折叠栏只折叠「用户消息之后、agent 回复之间」的内容。
锚定在用户消息上方的上下文行(如审批策略变更通知)不属于本回合输出区间,
始终保持原样可见,绝不参与折叠,也不会被当作折叠栏锚点——避免回合折叠栏「跨过」用户消息
去折叠其上方的内容;
- 最终总结只显示正文:回合结束后,最终总结消息内部自带的 Think 行也一并隐藏;
- 状态标签:非正常结束的回合(用户停止 / 中断)在回合折叠栏前置状态文本,
如「已停止 | 耗时5分12秒…」,正常完成不显示额外标签;
- 单条也分组:两个 text 之间只有 1 条命令(或 1 个 Think)时同样套步骤折叠栏,
运行中显示「正在运行 图标 工具名 · …」、text 出现后显示「运行了pwsh」;
回合结束整回合折叠时它收进回合折叠栏,展开回合折叠栏后步骤折叠栏行可见。
效果示意
回合进行中/手动展开:回合折叠栏实时显示耗时、首字、token、tok/s、缓存命中率、
已折叠步数(右端对齐轮次),过程内容按步骤分组折叠、工具卡片与 Think 行原样可读:
回合展开态
回合结束后(或手动收起):整回合收进回合折叠栏,只保留最终总结正文与用量脚注:
整回合折叠
组件样式与行距
- 折叠栏即官方样式:折叠栏直接复用官方 DisclosureRow 原语(@deepseek-ai/dsh-client-ui-primitives)
渲染——24px 行高、16px 前导、官方 14px chevron(收起右向 / 展开下向)、14px/24px 标题,
与 Think / 工具卡片的折叠行逐像素一致;
- 回合折叠栏分隔线:折叠栏下方常驻一条 1px 水平细线(.ccg-turn-divider,颜色按
var(--dsw-alias-line-secondary, var(--dsw-alias-border-l1, #d1d5db)) 链式回退——
截至目前(0.1.1 ~ 0.1.5-rc.1)DSH 均未定义 --dsw-alias-line-secondary,实际生效的
是两版共有的 --dsw-alias-border-l1,随主题明暗自动适配),收起/展开都显示,
上下留白 4px / 8px;
- 紧凑行距:折叠组只占一行(24px);被折叠的成员节点整行 display:none,不会残留空行,
行距与官方消息完全一致(column 的 16px 节奏),折叠再多也不会越空越大;
- 过渡动画:展开时内容从 0 高度平滑展开到真实高度(grid 轨道 0fr→1fr 过渡 + 淡入,280ms,
起始帧用 useLayoutEffect 同步提交保证过渡稳定播放);收起时播放收缩动画(280ms)后卸载内容;
系统开启「减少动态效果」时自动禁用动画;回合运行中(直播模式)内容高度自适应,
不裁切不断增长的流式内容;
- 运行中标题 shimmer 动效:步骤折叠栏运行中标题(「正在运行…」「正在思考…」)的文字带
shimmer 光泽扫过动画——渐变背景 + background-clip: text + 背景位移动画,整行一个渐变
统一流动(高光节奏:流动 1.8s + 停顿 2s);暗色/亮色主题各有配色,图标不受影响;
- 滚轮数字:运行中回合折叠栏的数字(耗时/首字/token/tok/s/缓存命中)按数位拆成 1ch 宽的
滚动视窗,数值变化时逐位滚动(350ms 回弹缓动);回合结束后回退纯文本;
- 多语言:界面文案跟随 DSH 界面语言实时切换(读取 document.documentElement.lang,
简体中文 / 英语),浏览器语言仅作回退;
- 无障碍:折叠栏带 aria-label / aria-expanded,键盘可操作(Enter / Space 切换)。
安装
方式一(推荐):从 npm 安装
本插件已发布到 npm registry:@winteries/dsh-turn-fold
(旧包名 dsh-turn-fold 仍会随每次发布同步更新,供已安装旧包的用户持续获取更新;新安装请使用 @winteries/dsh-turn-fold。)
官方命令(推荐)
dsh plugin --profile web add @winteries/dsh-turn-fold
或从 GitHub 源码安装
dsh plugin --profile web add github:Winter-And-You-Gone/dsh-turn-fold
dsh plugin 会将包加入 profile 的 pnpm 依赖并自动追加到组合包层(dsh.profile.bundles),无需手动改任何文件。验证方式:
dsh --profile web --dump-config # 确认输出中能看到 "@winteries/dsh-turn-fold" 层
然后完全退出 DSH 进程并重启。
方式二:手工 install.ps1
把插件目录放到你已有的插件目录,然后:
.\install.ps1 -PluginSource ""
例如:.\install.ps1 -PluginSource "C:\dsh-plugins\dsh-turn-fold"
不传参数时默认用脚本自身所在目录作为插件源
脚本会:
1. 在 ~/.dsh/profiles/node_modules/@winteries/dsh-turn-fold 建 Junction 指向插件目录;
2. 在 ~/.dsh/profiles/web/cordis.patch.yml 追加一行 - insert: 注册;
3. 校验 require.resolve 可解析。
然后完全退出 DSH 进程并重启。
卸载
官方方式:同时移除依赖和插件层
dsh plugin --profile web remove @winteries/dsh-turn-fold
手工方式(曾用 install.ps1 安装时):
Remove-Item "$env:DSH_HOME\profiles\node_modules\@winteries\dsh-turn-fold" -Force # 删 Junction
手动删掉 cordis.patch.yml 里对应的 insert 块
测试
npm install # 首次:安装 jsdom / react / react-dom(devDependencies)
npm test # node --test 运行 tests/ 下的全部测试
npm run check # 语法检查 client.js / index.js
测试套件(tests/)直接加载真实 client.js(经 __ModuleLoader__ 注入 + __test
导出,无复制粘贴漂移),分六层(7 个文件):
| 文件 | 覆盖 |
| --- | --- |
| unit.logic.test.mjs | 纯函数:computeGroup 步骤分组、computeTurnFold 整回合折叠、computeTurnMetrics / turnHeaderLabel 指标文案、turnNumber 定位;含历史 verify-fix 的全部场景与真实会话数据(TURN13) |
| unit.render.test.mjs | React 渲染:初始折叠 → 点击回合折叠栏展开 → 再收起 的完整交互;委托渲染内置组件时官方 inject 面 hook 的逐名透传(useConnectionGeneration / useHostInfo,由 chatNodeEntryInject 探测合并);条目注册契约(inject 声明) |
| unit.css.test.mjs | CSS :has() 隐藏规则在真实 DOM 上的生效(含“展开→收起”往返) |
| regression.test.mjs | 历史 bug 回归:节点对象替换(Bug1)、inject 缺失崩溃/abdicate(Bug2)、无工具调用回合折叠(v0.2.3)、折叠作用域不越过用户消息(v0.2.2)、步骤分组手动展开/收起 |
| unit.gear.test.mjs / unit.settings-row.test.mjs | 齿轮字段弹窗与 shadow 官方 transcript-view 的设置行(界面标题为官方原文「对话显示」,选项 Normal / Compact / Turn-Fold) |
| unit.compat.test.mjs | DSH 双版本兼容:0.1.1 useSession(.chat + 顶层 turnEnds/turnTimings) 与 0.1.2 useChat + chat.legacy 两条快照路径、折叠模式切换 hooks 顺序回归、官方 diffs 读取链(meta.diffs / resultView / callView) |
在 Windows 沙箱等无法 spawn 子进程的环境下需要 --test-isolation=none(已在
npm test 中内置);普通 Linux/macOS CI 同样可用该参数(Node ≥ 22.9)。
折叠图标(扑克牌)
步骤/回合折叠栏的前导图标默认为动态扑克牌(回合折叠栏右侧 ⚙ 齿轮 → 弹窗里的
「折叠图标」选择器可切回官方 chevron):
- 完成态:收起为牌堆(段内工具 ≤3 用 3 张、>3 用 5 张),点击展开变扇形;
- 牌面池:♠ ♥ ♦ ♣ + DeepSeek 鲸鱼 Logo 五选一,每个折叠栏按 leaderKey
随机记忆(重渲染不变);
- 运行态:步骤栏播放五牌面轮换动画、回合栏播放对角线轴翻牌(四花色循环、
Logo 背面),均为 SVG 原生动画;
- 遮挡:luminance mask 按上层牌变换动态挖空下层覆盖区,牌身透明(壁纸/
透明背景下正确);
- 设置预览:4 个静态形态每秒轮换牌面(相位错开,同一时刻 4 种不同牌面)+
两个运行态动画预览;
- 数据源:icons/default.json(花色路径、卡牌几何、扇形/牌堆变换表、动画
模板),改完 npm run sync:icons 注入、npm run icons:check 校验。
设置弹窗(回合折叠栏字段显隐 + 折叠图标选择;预览项悬浮 2x 放大):
设置弹窗
自定义图标(Agent Skill)
想改折叠栏图标的用户不用手动操作——本插件随包注册了一个 agent skill
dsh-turn-fold-customize-icons(host 半边 index.js 通过 ctx.skills 注册,
DSH 0.1.2+ 装配了 @deepseek-ai/dsh-skill 时自动生效)。对 AI 助手说“帮我把
扑克牌图标改成××样式”,助手会自动加载该 skill,得到完整自定义流程:
- 数据源:icons/default.json(唯一数据源,含花色路径、牌堆/扇形几何、动画)
- 改完同步:npm run sync:icons 注入 client.js → npm run icons:check 校验
- 快速预览:写 localStorage['dsh-turn-fold:icons'] 可免改代码覆盖
- 避坑指南:该环境特有的 SVG 渲染坑(fill var 属性不生效、defs fill 覆盖不掉、
clip-rule 无效、transform-origin 不可靠等)
skill 正文在 assets/dsh-turn-fold-customize-icons.md,随 npm 包 files 一起发布。
CI 与发布
GitHub Actions 会在每次 PR / push 到 main 时自动运行语法检查、npm test 全套测试和
npm pack --dry-run 打包预检;推送 v tag 时自动发布到 npm(OIDC Trusted Publishing,
无需长期 token)并创建 GitHub Release。一次性配置(把 npm 包绑定到本仓库的 release workflow):
npx npm@^11.15.0 trust github @winteries/dsh-turn-fold \
--repo Winter-And-You-Gone/dsh-turn-fold \
--file release.yml \
--allow-publish
也可以改为在 npmjs.com 网站账户设置里配置 Trusted Publishing。
之后每次发版只需两步:
npm version patch # 或 minor / major:bump 版本并自动打 v* tag
git push --follow-tags
提示:npm version 要求工作区干净,先把待发布的改动提交;tag 名必须与
package.json 的 version 一致(workflow 会校验,不一致即失败)。
工作原理(为什么不用改源码)
- DSH 会话 UI 是 Cordis 插件 + Slot 插槽系统拼出来的;聊天流每个块经
conversation.chat.node(keyed slot)按类型分发渲染器。
- Slot 注册器官方支持 不同 priority 覆盖(register at a different priority to shadow it, lowest renders)。
本插件用 priority: -1 覆盖内置的 tool-call / assistant-step / context 渲染器;
user 格(0 秒占位条)注册在 -2(顺序无关下限,绝不占 -1)——dsh-easyrewrite
硬编码 -1,本插件若先加载占了 -1、它后注册就会撞车抛错(真机事故:bundle 顺序
turn-fold 在 easyrewrite 前);固定 -2 后无论谁先加载都不冲突(easyrewrite 永远
-1、本插件永远 -2 或更低,注册表层面零碰撞)。仅当 -2 也被第三方占用(极罕见)
才继续下探到最低占用位 -1。并把第三方条目(如 dsh-easyrewrite 的撤回/重编辑气泡)
的组件链式委托渲染(整包 props 转发、其 inject 面的扁平 props 并入注入面)——
占位条与 user 消息专用插件共存、功能互不丢失。⚠️ 其他想占用 user 格的插件请避开
-2 或使用探测式优先级:本插件从不主动撞已注册者(探测到更低占用即下探),但
后注册且硬编码同优先级者会自撞(slot 模型固有,责任在硬编码者)。
- 注册冲突自动让位:注册前探测同 key/id 的 priority: -1 是否已被占用
(ctx.slots.entries),被占则自动让位到第一个不冲突的值(官方 0 恒预留,绝不
落回官方档)并打 console.warn——本插件后加载时不再与先占者冲突。
conversation.chat.node 三格(tool-call/assistant-step/context)与
settings.general.item 的 transcript-view 行都走该逻辑;user 格例外(占位条必须
渲染在 user 消息正下方,让位即弃权,且不能用探测-1 方案)——固定 -2 下限并链式
委托共存。
- 注册异常软降级(绝不带崩 DSH):slots 注入回调若让异常外泄,延迟执行路径
(目标 slot 声明晚于插件加载时,回调跑在官方声明者的调用栈里 / 声明订阅里
uncaught re-throw)会打断官方 UI 激活、web 整页无法启动。因此本插件所有 slot
注册(chat.node 四格、设置行)统一走一个注册管道:inject 声明等待
与回调内 register 各自兜异常(return undefined 即“无可清理资源”,官方
cachedSlotInject 对 falsy 返回无害),单个条目注册失败仅跳过该条目,console.warn
留排查线索并弹一次中性措辞的降级 Toast 告知用户(不指涉冲突方——旧版宿主未声明
slot 的版本缺口也走同一条降级路径);宿主半边的 skill 注册同样双层防护。DSH 启动
不受本插件任何注册异常影响。
- 展开时通过 ctx.slots.entries('conversation.chat.node') 取到内置组件引用做委托渲染,
工具卡片/Think 行/上下文注入的内容与样式与内置完全一致。
- 整回合折叠通过会话快照的 turnEnds(turn/end 事件驱动)判定回合完成,配合
chat.locations.getTurn() 计算折叠栏/成员/最终消息,再以 CSS :has() 隐藏成员 flowItem。
回合运行中由 turnTimings(turn/start 事件给出 startTime)判定回合已开始,
回合折叠栏即出现:耗时用随机间隔时钟(每 CONFIG.liveTickMs × 0.5~1,默认 125~250ms)
补 Date.now() 实时走动,"消耗token"在真实值之上叠加每 tick +1/+11 交替的动画
偏移持续增长(真实 usage 到达时校正基线),全部指标在 turn/end 后切换为权威值。
- 消耗token 口径(对齐官方统计):回合结束后优先取 turn-tail 携带的官方
tokenUsage(官方 deriveTurnTokenUsage 在持久化事件日志上折叠全部计费 attempt:
totalTokens = 精确 prompt+output、含被重试请求,缓存命中率分母 = prompt 侧总量);
节点累加路径(运行中基线、无 tokenUsage 回退)在 locations.getTurn() 之外用
nodes.values()(返回 visible+hidden 全部已物化节点,旧版缺方法自动跳过)按节点
引用去重补采本回合隐藏的 assistant-step——纯 tool-call 步骤官方以 visibility:hidden
结算、不进 order/locations,只遍历 getTurn 会漏计其 usage。
- 会话快照双版本读取层:DSH 0.1.1 与 0.1.2 的快照契约不同——0.1.2 把快照拆分成
useSession(会话级状态)与 useChat(chat 数据),turnEnds/turnTimings 收进
chat.legacy。组件统一经 useChatSnapshotData 适配:有 useChat(0.1.2+)就读
useChat 快照本体,否则从 useSession(s).chat 取;turnEnds/turnTimings 优先读
chat.legacy、顶层兼容字段兜底。所有 hooks 无条件调用(数据计算与订阅和“是否接管
折叠”解耦),折叠模式切换(接管 ↔ 委托内置)不改变 hook 数量,条目不会崩。
- 0 秒占位(user 消息正下方):GroupedUserView 注册 conversation.chat.node 的
user key,优先级固定 -2(顺序无关下限,绝不占 -1——与 easyrewrite 硬编码
-1 零碰撞,本插件先加载也不会让它后注册撞车;-2 被第三方占用时才继续下探),
在「会话运行中且该 user 是最后一条消息」时于 user 消息正下方渲染占位回合
折叠栏(耗时从运行中回合的 startTime 计时),第一条中间节点到达后自动交接给正式
回合折叠栏(占位栏补 16px 上间距与官方 flow gap 对齐,交接无位移)。第三方 user
条目(dsh-easyrewrite)的组件链式委托渲染、整包 props 转发;chat.node 是核心 slot
恒声明,无需 try/catch 兜底。位置说明:占位栏在聊天流列内、官方 TurnStatus
("Deep diving..." 状态描述行)之上——2026-08-30 至 0.5.x 曾挂输入区 dock,会跑到
状态描述行下面(输入框左上角),位置错误,故恢复 user 格方案。
- 首字(TTFT)三来源(官方优先):① step settle 后即实时读取官方值——
assistant-step 节点的 data.finalNode.timing(官方在 assistant/message 事件后写入
{ stepStartTime, firstTokenTime, completedTime }),取回合内 step 号最小者(第一个
请求)的 firstTokenTime - stepStartTime(与官方 deriveTurnMetrics 同款语义);
② 回合结束后优先用 turn-tail 携带的聚合 ttftMs(同值、来自持久化事件日志、
刷新页面不丢);③ 仅当无任何 step 完成(首个请求仍在流式)时回退渲染时刻近似
(Date.now() - turnTimings.startTime,误差约一帧渲染延迟,幂等记录、回合内只记一次)。
- 段闭合标题缓存:段闭合后标题不再变化,按 leaderKey + 节点 keys + 语言 + 工具指纹
(名称/isError/argsRaw 长度,不解析内容)记忆,避免每次渲染重复解析 argsRaw;
工具行数变更优先读取官方 diffs 数据(oldText/newText 块行数;0.1.2 在结算 metadata
root.meta.diffs、0.1.1 在 wire 视图 root.resultView.diffs / root.callView.diffs),
无 diffs 时才回退解析 argsRaw(单次解析同时提取路径与行数)。
- 会话切换清理:segmentLabelCache(段闭合标题缓存,每段一条字符串、长会话可达数百 KB)、
liveTokenCache(每回合 1-2 条)与手动展开状态(overrides / turnOverrides)在切换
会话时清理——手动状态回到自动规则(已结束回合默认收起);ttftCache 保留(每回合一个
数字,量级可忽略)。切换回原会话仅"已结束回合回到默认收起 + 段标题重新计算一次"。
- 多语言跟随:文案读取 document.documentElement.lang(DSH 切换界面语言时由
dsh-client-locale 设置),随 DSH 语言实时切换,浏览器语言仅作回退。
注意事项
- 兼容 DSH 0.1.1-rc.2 ~ 0.1.6-alpha.1(会话快照契约差异由插件内适配层消化、官方
渲染 hook 面自动跟随,见工作原理;0.1.5-rc.1 与 0.1.6-alpha.1 上均已逐条核对槽位/
快照/设置行/节点数据契约)。DSH 升级若改变上述槽位契约或内置组件 props,本插件可能
需要随版本小改(属插件维护,非改源码)。
- 宿主要求已声明:package.json 的 engines.dsh = >=0.1.1-rc.2 =0.1.1-rc.2 <=0.1.6-alpha.1,并在更新前拦下确定不满足的版本(undeclared/未知
一律放行;DSH 本体不读该字段,不影响加载)。区间是闭区间、锁到已核验的宿主版本:
每次 DSH 升级后重新核对契约,再抬上限并随新版本发布。
- 折叠栏文案在 client.js 顶部 CONFIG 可调。
- 耦合点清单(DSH 升级时对照排查;任一失效均优雅降级——回退内置渲染 / 文案兜底 +
console.warn 提示,不会白屏):
- 会话快照字段:0.1.1 走 useSession 快照的 s.chat.order / nodes / locations、
locations.getTurn()、顶层 turnEnds / turnTimings、chat.timeline.turns;
0.1.2 快照拆分后改走框架注入的 useChat(扁平 ChatSnapshot),turnEnds /
turnTimings 在 chat.legacy(适配层自动选择,见工作原理)——用于段/回合分组、
结束判定、耗时与状态标签;
- 节点数据结构:tool-call 的 data.root(call.name / argsRaw;diffs 按版本在
root.meta.diffs(0.1.2)或 resultView / callView 视图(0.1.1))、
assistant-step 的 blocks(reasoning / text)与 usage、turn-tail 的
tokensPerSecond 与 tokenUsage(用于折叠栏文案、think 摘要、token/缓存命中指标;
tokenUsage 为 0.1.2+ 官方每回合精确统计,缺失时回退节点累加)、
ChatNodeStore.values()(隐藏 assistant-step 补采;缺失时自动跳过);
- CSS 选择器:[data-chat-flow-kind]、[data-variant="think"](隐藏折叠成员 flowItem
与最终总结的 Think 行);
- Slot 系统:conversation.chat.node 内置条目(priority: 0)、
slotsService.entriesOfSlot()(委托渲染与 tool.call.toolview 子视图分发);
- Locale 命名空间:条目的 locale: 声明决定注入的 t 词典,且同一个 slot 上官方
条目混用两种命名空间——tool-call(ui-tool 注册)声明 'conversation'(工具标题词
tool.title.read=读取 等),assistant-step/context/user(ui-chat 注册)声明
'chat'(message.think=思考 等);旧版全在 'conversation'。插件注册时按条目 key
对应复制同 key 官方条目的声明(detectChatLocale,无对应时 ctx.locale 试查后回退
'conversation'),转发给官方组件的 t 一律过 wrapLocaleT 兜底(查不到 key 时用
内嵌的官方词典合并本——chat + conversation + common 共 282 词条——做 {占位符}
插值兜底,不再裸显 "message.think" / "message.contextInjection" /
"tool.title.read" 等任何原始 key)。扫码进群