- Home
- Plugins
- Sessions & Messages
- dsh-auto-expand
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-expandREADME
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 默认把工作过程折叠成三层:
- 整轮折叠(Turn 级别)
- 操作组折叠(process group,只剩标题)
- 思考折叠(只剩首行预览)
运行时看不到助手在做什么。本插件让当前正在运行的那一轮自动展开操作组、 思考内容与工具结果,并在该组工作结束后自动收起;历史轮次完全保持官方行为。
行为
| 维度 | 行为 |
|---|---|
| 结构 | 保留操作组标题与 400px 高度上限 |
| 操作组 | 当前运行 Turn 内默认展开 |
| 操作组折叠 | group 关闭(中间回复或新工具边界)时折叠一次 |
| 思考过程 | 默认展开(仅当前 Turn、仅所属操作组展开时) |
| 思考折叠 | 可配置,默认「下一批操作出现时」 |
| 工具结果 | 当前运行的操作组内默认展开 |
| 工具折叠 | 可配置,默认「下一批操作出现时」 |
| 历史轮次 | 完全不动,遵从官方(含「手动展开历史操作组时内层行保持折叠」) |
所有自动展开都是一次性的:用户手动开合之后,插件不再干预。
术语
| 词 | 指什么 |
|---|---|
| 本条 | 当前这一行(一段思考 / 一条工具调用) |
| 操作 | 操作组里的一个条目,对应一次工具调用或一次模型输出 |
| 批次 | 一次模型输出派发的全部操作 |
| 操作组 | 当前轮里连续的一段工作过程(process group) |
「节点 / node」是实现层叫法,不出现在界面文案里——它在中文语境里同时指 DOM 节点、AST 节点,读者无从分辨。
四个收起时机
按收起由早到晚排列(也是展开范围由窄到宽)。思考与工具共用同一套:
| 时机 | 含义 | 判据 | 范围 |
|---|---|---|---|
| 本条完成时 | 这一行自己的工作做完就收(思考写完 / 工具返回结果) | done | 本条 |
| 下一条操作出现时 | 保持展开,直到组内出现下一条已经开始执行的操作 | 自己是最新一条已开始的操作 | 永远单条 |
| 下一批操作出现时(默认) | 保持展开,直到组内出现下一批操作(下一次模型输出) | step(模型输出轮次) | 同批多条 |
| 随操作组收起 | 保持展开,直到所属操作组结束 | 恒真 | 全部 |
「下一条操作出现时」的完整过程(一批串行 3 条命令 A→B→C):
| 时刻 | A | B | C |
|---|---|---|---|
| 三条参数都在流式(未开始执行) | 不展开 | 不展开 | 不展开 |
| 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 原样生效,所以默认状态与未安装插件完全一致。
| 输入 | 实际高度 |
|---|---|
| 不限制高度 = 开 | 无上限,内容随对话滚动 |
50 | min(200px, 25vh) |
100(默认) | 不写内联样式,等同官方 min(400px, 50vh) |
200 | min(800px, 100vh) |
2.5 | min(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→AutoAssistantNodeViewconversation.chat.node的tool-call→AutoToolCallTree(包裹官方ToolCallTree, 只覆盖它从 props 接收的useDisclosure;官方 toolview 分派与GenericToolCardfallback 原样保留,不克隆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
dsh-web-ui (dsh-chat-recovery)
zhu1090093659/dsh-web-ui
billion-context
ranxianglei/billion-context
dsh-synapse
liangmianya/dsh-synapse
dsh-chat-import
nwflower/dsh-chat-import