Skip to main content
T

dsh-auto-expand

thinkofrain1213/dsh-auto-expand

DSH plugin: keep the running turn's work visible — process groups, reasoning and tool results expand automatically and fold once the group ends, leaving history untouched.

Install

dsh plugin --profile web add github:thinkofrain1213/dsh-auto-expand

README

dsh-auto-expand

DSH 插件:让正在进行的工作过程默认可见。

目标版本 DSH 0.2.1(engines.dsh 限定在 0.2.1.x)。 插件通过原地包裹官方渲染组件工作,不重新注册任何 slot; 所有包裹失败都会静默降级为官方行为,绝不让 DSH 崩溃。

安装

需要 DSH >=0.2.1-rc.2 <0.2.2(0.2.1 这一条线)。

dsh plugin add dsh-auto-expand

engines.dsh 声明之后,DSH 只按这个区间判定兼容性,peerDependencies 被忽略。

解决什么问题

DSH 默认把工作过程折叠成三层:

  1. 整轮折叠(Turn 级别)
  2. 操作组折叠(process group,只剩标题)
  3. 思考折叠(只剩首行预览)

运行时看不到助手在做什么。本插件让当前正在运行的那一轮自动展开操作组、 思考内容与工具结果,并在该组工作结束后自动收起;历史轮次完全保持官方行为。

行为

维度行为
结构保留操作组标题与 400px 高度上限
操作组当前运行 Turn 内默认展开
操作组折叠group 关闭(中间回复或新工具边界)时折叠一次
思考过程默认展开(仅当前 Turn、仅所属操作组展开时)
思考折叠可配置,默认「下一批操作出现时」
工具结果当前运行的操作组内默认展开
工具折叠可配置,默认「下一批操作出现时」
历史轮次完全不动,遵从官方(含「手动展开历史操作组时内层行保持折叠」)

所有自动展开都是一次性的:用户手动开合之后,插件不再干预。

术语

词指什么
本条当前这一行(一段思考 / 一条工具调用)
操作操作组里的一个条目,对应一次工具调用或一次模型输出
批次一次模型输出派发的全部操作
操作组当前轮里连续的一段工作过程(process group)

「节点 / node」是实现层叫法,不出现在界面文案里——它在中文语境里同时指 DOM 节点、AST 节点,读者无从分辨。

四个收起时机

按收起由早到晚排列(也是展开范围由窄到宽)。思考与工具共用同一套:

时机含义判据范围
本条完成时这一行自己的工作做完就收(思考写完 / 工具返回结果)done本条
下一条操作出现时保持展开,直到组内出现下一条已经开始执行的操作自己是最新一条已开始的操作永远单条
下一批操作出现时(默认)保持展开,直到组内出现下一批操作(下一次模型输出)step(模型输出轮次)同批多条
随操作组收起保持展开,直到所属操作组结束恒真全部

「下一条操作出现时」的完整过程(一批串行 3 条命令 A→B→C):

时刻ABC
三条参数都在流式(未开始执行)不展开不展开不展开
A 开始执行展开不展开不展开
A 完成、B 未开始保持展开不展开不展开
B 开始执行收起展开不展开
B 完成、C 未开始收起保持展开不展开
C 开始执行收起收起展开
全部完成收起收起保持展开
下一次模型输出出现收起收起收起

注意最后两行:全部完成时只有最后一条保持展开,不会再整批弹开一次;下一次 模型输出出现时也只收一次。

为什么必须区分「已开始」:工具节点在参数流式阶段就已经创建(此时 phase 是 preparing),节点序号也在这个时刻定死。若把未执行的也算作「已出现」,一批 三条工具会在执行前就分出胜负——最后一条永远最大,其余几条永远不展开。所以操作 判据只统计已开始(走出 preparing)的成员,最新者才会随执行推进而前移。

「下一条操作」与「下一批操作」的差别:

场景下一条操作出现时下一批操作出现时
同一批串行 A→B→C逐条交替:A 亮→A 收 B 亮→B 收 C 亮三条一起展开,直到下一批出现
同一批真并发(如多个 read)同时开始 → 一起展开(先完成的被后开始的取代)三条一起展开,直到下一批出现

设置

「侧栏 → 插件 → dsh-auto-expand」详情页里有九行设置(改后立即生效,跨重启保留), 按 操作组 / 思考 / 工具 / 高度 四个一级分组排列:

分组行控件关掉/作用
操作组自动展开操作组开关process group body 回到官方默认折叠行为
思考自动展开思考过程开关思考块回到官方默认折叠行为
思考思考收起时机下拉四选一(见上表)
工具自动展开工具结果开关工具结果回到官方默认折叠行为
工具工具收起时机下拉四选一(与思考完全相同的四项)
工具隐藏查看按钮下拉三选一:不隐藏(默认)/ 仅运行中隐藏 / 全部隐藏
高度不限制高度开关开启时内容全部展开、随对话滚动,下面的百分比不生效
高度展开高度(%)数字官方高度的百分比;100 等于官方默认,接受小数
高度历史轮次也应用开关开启后已结束轮次的操作组也用上面的高度设置

展开高度

高度只有两个本质不同的状态:「有上限」和「无上限」。

100% 不是单独的档位——它就是「有上限 + 100%」,因为公式在 percent = 100 时 恰好等于官方值 min(400px, 50vh),逐像素相同。此时插件不写任何内联样式, 让 CSS 原样生效,所以默认状态与未安装插件完全一致。

输入实际高度
不限制高度 = 开无上限,内容随对话滚动
50min(200px, 25vh)
100(默认)不写内联样式,等同官方 min(400px, 50vh)
200min(800px, 100vh)
2.5min(10px, 1.25vh)(接受小数)

输入不过滤,照搬官方契约(numeric 只是移动端键盘提示,不改变接受范围, 控件「绝不静默改写用户输入」):

输入结果
150 / 2.5合法,失焦时写入
空写入默认值 100
abc / 150(全角)红字提示「请填数字」+ 红框,不写入
-5红字提示「请填非负数」+ 红框,不写入

低于 10% 时描述行右侧出现橙色提示「高度过小,内容几乎不可见」,但 不阻止保存——0 和 5 都是合法值。橙色是「太小」,红色是「输入出错」, 两者互斥。

偏离 100 时输入框标题行右侧出现「已覆盖 / 恢复默认」,点「恢复默认」写回 100。

门控只有一条:「不限制高度」开启时,百分比输入框变灰(无上限时那个数字没有意义)。 历史轮次开关不受任何门控——它表达的是「应用范围」,与「高度值是多少」正交。

「不限制高度」永不置灰:即使「自动展开操作组」关闭,用户「我想要无上限」的意图 仍然有效,重新打开即恢复。此时只用描述提示「暂不生效」,不锁行。

变灰的控件保留原来的值,重新打开上层后原样恢复。

门控总览

三层,全部是「上层不做这件事,下层就没有意义」:

上层下层表现
自动展开操作组思考、工具共四行 + 它们各自的时机行变灰——操作组折叠时那四行看不见
某层自己的开关它自己的时机行变灰并点名控制者
不限制高度百分比输入框变灰——无上限时那个数字没有意义

两条刻意不设门控:

  • 历史轮次也应用 —— 它表达「应用范围」,与「高度值是多少」正交。无论选了百分比还是无上限,都可以决定是否延伸到历史组。
  • 不限制高度 —— 高度组里唯一能独立决策的控件。总开关关掉时高度确实不生效,但那用文案说明「暂不生效」,不锁行。

「隐藏查看按钮」也是独立设置,不受上述任何一层影响。

五个开关全部关掉 = 完全等于未安装本插件。

隐藏查看按钮

官方工具行悬停时会出现一个「查看」按钮,点它跳转到该次调用的轨迹记录。 这个按钮只在官方设置「显示代码工作视图」打开时才有入口。

本插件可以把按钮连同它占用的那一行一起去掉:

档位行为
不隐藏(默认)官方行为,逐字不变
仅运行中隐藏工具行还展开着就藏按钮;这一行收起后不再控制,交回官方
全部隐藏任何情况都不显示

「运行中」= 这一行按收起时机是否还展开着。 判据与「这一行要不要展开」 用的是同一个函数(toolRowAutoOpen),所以按钮的消失与收起永远同步:

工具收起时机「仅运行中隐藏」的实际效果
本条完成时该工具一完成,按钮就出现
下一条操作出现时下一条开始执行时,按钮出现
下一批操作出现时下一批操作出现时,按钮出现
随操作组收起操作组结束前一直隐藏

因为「运行中」依赖自动展开,所以工具行开关或操作组开关关掉时,这一档 等同于「不隐藏」——设置卡片会在说明里点名是哪个开关,但该行不会变灰, 你仍然可以改。

去掉的是整个入口,不是只把按钮变透明——官方那个按钮平时 opacity: 0, 但仍然占着一行高度,所以只藏视觉会留下一条空行。插件在包裹层把 inspectCall 置空,官方的三个按钮、详情卡给按钮让位的 88px 内边距、 以及那一行高度会一起消失。

唯一已知的语义边界

若一个模型输出轮次里出现多段思考(reasoning → text → reasoning),它们属于 同一个 assistant-step 节点、共享同一个 anchorSeq,所以「下一条操作出现时」 判据无法把这几段思考区分开——它们同进同退。

这不是缺陷:它们本来就在同一个节点里,节点级判据区分不了它们。为「看起来更合理」 而额外加约束,只会让判据与真实结构脱节。

已知限制

自动收起会连带卸载行内的浮层。 例如 read_image 的缩略图点开大图后, 若那一行此刻被自动收起,大图会跟着消失——因为收起时不渲染行内容, 浮层组件的内部状态也随之销毁。

官方手动收起时同样会如此,但那是你主动点的;自动收起让它变成"看着看着被收走"。 修它需要跨包读取附件组件的内部状态,代价与收益不匹配,故记录为此。

实现方式

不重新注册任何 slot,而是原地包裹三个已注册的渲染组件:

  • conversation.chat.flow → AutoFlow(官方 ChatFlow 的克隆,只换两个 seat 组件)
  • conversation.chat.node 的 assistant-step → AutoAssistantNodeView
  • conversation.chat.node 的 tool-call → AutoToolCallTree(包裹官方 ToolCallTree, 只覆盖它从 props 接收的 useDisclosure;官方 toolview 分派与 GenericToolCard fallback 原样保留,不克隆 ui-tool 的任何代码)

原因见 spec/DESIGN.md 第 4 节。所有包裹失败都静默降级为官方行为。

文档

文件内容
spec/CAPABILITY-MATRIX.md能力矩阵:展开/收起/高度/动画的完整维度表、接管点矩阵、信号可得性
spec/DESIGN.md整体设计
spec/ARCHITECTURE.md技术架构与机制细节
spec/PLAN.md阶段计划
spec/SETTINGS-SPEC.md设置页规范(摘自 dsh-project-groups 实测)
spec/P8-PLAN.md思考 / 工具收起时机的实施计划(四个统一时机)
spec/P9-PLAN.md隐藏「查看」按钮的实施计划(三档范围)
spec/P7-PLAN.md工具结果自动展开的实施计划
spec/P4-ROLLBACK.md工具结果自动展开的第一次尝试为何回滚(第 9 节:路径已重新打开)

各阶段实施报告:spec/P1-REPORT.md ~ spec/P6-REPORT.md。

目标版本

  • DSH 0.2.1-rc.3(engines.dsh 为 >=0.2.1-rc.2 <0.2.2,rc.2 仍可用)
  • 依赖 @deepseek-ai/dsh-client-ui-chat 与 @deepseek-ai/dsh-client-ui-tool 的内部渲染结构;上游若改动需同步克隆组件(见各文件头部的上游版本记录)
  • 升级上游后先跑 pnpm drift:它逐文件比对克隆来源,0 漂移才能放心升级

开发

pnpm install
pnpm verify         # typecheck + build + test + smoke
pnpm pack:check     # 发布产物自检(files 覆盖、exports 可解析、产物体积)
pnpm verify:release # 发布就绪检查(版本一致、locale 文案、打包清单、lib 已入库)
pnpm drift          # DSH 升级后检查克隆组件是否与上游偏离
pnpm watch          # 增量构建

pnpm drift 需要一个上游源码 checkout(默认找同级的 ../deepseek-harness, 也可以用 --upstream <path> 或环境变量 DSH_SOURCE_CHECKOUT 指定)。 没有源码 checkout 时它会跳过并以退出码 2 结束。

上游依赖与漂移

本插件克隆了官方的 23 个源文件(清单见 scripts/upstream-manifest.mjs), 因为官方没有导出这些组件、且重新注册 slot 会被拒绝(见 spec/DESIGN.md)。 工具结果那一层没有新增克隆:它是包裹官方组件、只替换一个 prop 实现的。

DSH 升级后第一件事是跑 pnpm drift:

  • 它把每个克隆文件指回上游出处,用内容哈希判断上游是否变了;
  • verbatim(整份克隆)变了 → 必须逐行重新对照,可能漏掉了新分支;
  • partial(部分摘取)变了 → 复核被摘取的片段即可。

确认克隆已对齐后,用 pnpm drift:record 刷新 spec/upstream.json。

发布

lib/ 是提交进仓库的:npm 的 tarball 由它构成,CI 也用它发布, 这样发布的产物与仓库内容逐字节对应。改完源码记得 pnpm build 并一起提交, validate 工作流会断言 lib/ 与 src/ 同步。

# 1. 改版本号:package.json 的 version + CHANGELOG.md 加一段(必须带日期)
# 2. 全量门禁(verify:release 会守住版本号/CHANGELOG/locale/打包清单/lib 已入库)
pnpm verify && pnpm verify:release
# 3. 提交并打 tag
git commit -am "chore: release vX.Y.Z"
git tag -a vX.Y.Z -m "vX.Y.Z"
git push && git push --tags
# 4. 建 GitHub Release —— 之后由 CI 发布
gh release create vX.Y.Z --verify-tag --title "vX.Y.Z" --notes-file <说明.md>

首次发布必须在本机手工 npm publish:npm 没有「待生效的发布者」概念, 第一个版本无法由 CI 发出。发成功后到 npmjs.com 给这个包配置 trusted publisher (ThinkofRain1213 / dsh-auto-expand / publish.yml / 环境名留空 / 勾选 Allow npm publish),此后每个 Release 都由 .github/workflows/publish.yml 经 OIDC 发布,仓库里不存长期 token。

当前状态(2026-10-26):0.1.0 已由本机手工发布上线; trusted publisher 已创建(Permissions: npm publish + npm stage publish), 但状态为 Pending validation —— npm 要求在一次真实发布中验证该配置, 逾期(2026-10-28 11:35 UTC)未验证则会失效、需重新创建。 下次发新版本时若 CI 报 OIDC 认证失败,先回 npmjs.com 检查该配置是否仍在。

许可

MIT

Related plugins