Repository navigation
Releases
v0.6.1
dsh-compact-button v0.6.1
核心:两个纯显示层修复——dock 行的顺序撞车和过早出现。不改功能、不改通道、无破坏性变更。
PR:#7 | 已验证平台:DSH 0.2.1-rc.2(web)+ 0.2.0-rc.2(桌面版)
差异规模:9 files changed, +175 / −20
TL;DR
0.6.0 把两个按钮搬上官方 conversation.composer.dock 之后,实际用起来暴露出两处和平台自带药丸不一致的地方:
- 平台
0.2.1-rc.2把原来的单个stats药丸拆成了activity+usage两个占用者,usage正好占order: 1——和插件的order: 1并列同名次,两个条目抢同一个显示位置。 - 官方药丸在会话产生模型活动之前是不渲染的,而插件从第一帧就渲染,于是空白会话上会冒出两个还没有可作用对象的按钮。
修法分别是 order: 1 → order: 10,以及给 ContextActionRow 加一道和官方同源的活动门控。
问题一:order 撞上了官方 usage
平台 0.2.0-rc.2 只往 dock 注册一个占用者,插件用 order: 1 刚好排在它后面:
| 占用者 | order |
|---|---|
stats |
0 |
0.2.1-rc.2 把它拆成了两个:
| 占用者 | order |
|---|---|
activity |
0 |
usage |
1 |
于是插件和 usage 拿到同一个名次。list slot 按 order 升序排,同名次不保证稳定先后,结果是两个条目挤在同一显示位置而不是排成一行。
改成 order: 10:稳稳排在两个官方药丸之后,同时留出空位——后续官方再加占用者也不用回来改这个数。
- // Sit after the official `stats` pill (order 0) that
- // dsh-client-ui-chat registers into the same row.
- order: 1,
+ // Sit after every pill the platform registers into this row.
+ // dsh-client-ui-chat ships `activity` (order 0) and `usage`
+ // (order 1) as of platform 0.2.1-rc.2 — a low number here would
+ // tie with an official pill, so 10 leaves headroom for more.
+ order: 10,问题二:会话一开始就出现,而官方药丸要等首轮结束
官方那两个不是「晚一点注册」,而是组件自己 return null:
// ActivityPill
if (stats.steps === 0) return null
// UsagePill
if (usage === void 0 || billedInputTokens(usage) === 0 && usage.outputTokens === 0) return null它们一直注册着,靠 slot 下发的 useProjection 判断会话有没有活动。插件这边无条件渲染,所以空白会话上就冒出来了——而那时候两个按钮都没有可作用的对象:没有历史可压缩,当前会话本身也就是新会话。
关键发现是 useProjection 本来就下发给了我们的组件。renderer 的 standardKit 会把 session 作用域的每个 hooks 源绑成 use<Name> 选择器、随 kit 一起交给槽位占用者——ActivityPill 的 inject 只给了 hooks.performanceUsage,却能解构出 useChat / useProjection,靠的就是这个机制。
所以不需要新增任何服务依赖,接上就行:
export function ContextActionRow({ compact, newSession, t, useProjection }: ContextActionRowProps) {
// Both selectors are subscription hooks, so they run on every render and
// must not sit behind the early return below.
const stats = useProjection?.('sessionStats') as SessionStatsLike | undefined
const usage = useProjection?.('tokenUsage') as TokenUsageLike | undefined
if (!sessionHasActivity(stats, usage, useProjection !== undefined)) return null
...
}
⚠️ 两个 selector 都在早返回之前无条件调用。它们是订阅 hook,挂在条件后面会破坏 hook 调用顺序。
判据与三级降级
门控抽成纯函数 sessionHasActivity(stats, usage, haveSelector),便于单测覆盖每个分支:
| 优先级 | 判据 | 为什么 |
|---|---|---|
| 1 | sessionStats.steps > 0 |
与 ActivityPill 同源。steps 数的是 step/end 事件,字面就是「首轮已闭合」;而且从完整 durable log 折叠,翻页和压缩都动不了它 |
| 2 | tokenUsage.outputTokens > 0 |
dsh-session-stats 未装配、projection 缺席时的退路 |
| 3 | 都没有 → 显示 | 故意往这个方向降级:宁可按钮早出现,也不能判断失误让功能消失 |
第 1 条选 sessionStats 而不是 tokenUsage,是因为 steps 的语义就是「已闭合的 step 数」,和「首轮对话结束」字面对齐;tokenUsage 只在模型报出 token 后才有值,边界更松。
用户能感知到的变化
| 场景 | 0.6.0 | 0.6.1 |
|---|---|---|
| 新建空会话 | 输入框下方已有两个图标 | 无图标(与官方药丸同步出现) |
| 首轮回复结束后 | 图标在 | 图标在(位置不变) |
| dock 行顺序 | 与官方 usage 抢位置 |
activity → usage → 本插件两个图标 |
升级须知
无破坏性变更,没有需要还原的旧状态,没有新增前置动作。
dsh plugin --profile web add dsh-compact-button@0.6.1
# 重启 dsh web(仅硬刷新浏览器不够),重启后再硬刷新页面桌面版:插件由桌面 app 自己的插件管理界面装卸(dsh CLI 那句 profile "desktop" is managed exclusively by the Electron application 是平台设计)。更新后需要从托盘彻底退出再打开——桌面 app 有 background-close 行为,只关窗口不会重载 profile。
兼容性
- 不声明任何 DSH peer 依赖,这条没变。client bundle 只
requirereact、react/jsx-runtime、@deepseek-ai/dsh-client-ui-primitives,三者都是平台 module table 里的 external - 平台
0.2.1-rc.2下逐项核对过:conversation.composer.dock仍是kind: 'list'/scope: 'session';renderSlot("conversation.composer.dock", {})调用点未变;slots.inject/locale.register/sessions.binding/uiWorkspace.startSession签名均未变 sessionStats缺席时不会白屏。该 projection 由独立插件@deepseek-ai/dsh-session-stats提供,未装配时useProjection('sessionStats')返回undefined,门控自动退到tokenUsage;连 selector 都没拿到则直接显示- 降级行为温和。万一上游把
conversation.composer.dock移除或改语义,slots.inject等不到声明,结果只是「图标不渲染」——不抛错、不影响平台其余部分
已知限制
- 依赖官方槽位
conversation.composer.dock:由平台声明与渲染,上游若移除则图标停止渲染(静默降级) - dock 行是居中 flex 容器,官方
dsh-client-ui-chat已占用order: 0(activity)与order: 1(usage);本插件用order: 10排在它们之后,给后续官方条目留了空位 - 图标现在要等会话有活动才出现。这是刻意的,与官方药丸一致;如果你希望它常驻,这是可讨论的取舍
- 压缩按钮只负责提交
/compact,压缩的接纳与执行语义由 Host 侧 command-compact 插件拥有 - 状态提示 4 秒后自动复位,不提供压缩进度展示
- 新建会话沿用部署默认的 agent 预设与权限设置;若当前会话用的是非默认预设,新会话不会自动沿用
包信息与校验
dsh-compact-button-0.6.1.tgz 26,811 bytes
sha256 3152d9d31a86015f24f77bb81b76ad066d345390a918221c864741b6f8e6e6cb
sha1 0f0d776c802ff052e039ba8accb9740e3e63a2b5
包内清单(19 个文件,与 0.6.0 完全同构):
LICENSE README.md README.en.md cordis.patch.yml dsh.plugin.json package.json
lib/index.js lib/client.js lib/client-registry.js
src/client/{CompactButton,ContextActionRow,NewSessionButton,index}.tsx
src/client/{compact-button.module.css,css-modules.d.ts,locales.ts,primitives.d.ts}
src/context-types.ts src/index.ts
| 产物 | 通道 |
|---|---|
lib/index.js |
Host 半边(惰性 apply(),维持 bundle 挂载契约) |
lib/client.js |
官方 profile 通道(bundle id = 包名 dsh-compact-button) |
lib/client-registry.js |
插件注册表通道(bundle id = manifest id dsh-external/dsh-compact-button) |
包内不含 patch-*.cjs——lib/index.js 里唯一一处 app.asar 是文档注释里那句「不再改它」,不是补丁代码。
验证结果
| 项 | 结果 |
|---|---|
pnpm typecheck |
✅ 无错误 |
pnpm test |
✅ 6 files / 59 tests passed(0.6.0 是 51,+8 全在门控) |
pnpm build |
✅ 三个 bundle 齐全 |
产物含 order: 10 |
✅ 1 处 |
产物含 sessionStats / tokenUsage |
✅ 各 3 处 |
产物含 conversation.composer.dock |
✅ 3 处 |
产物含 dshCompactPulse keyframes |
✅ 2 处 |
@deepseek-ai/dsh-client-ui-primitives 保持 external(未被 bundle 进来) |
✅ |
| 装进 web + desktop 两个 profile,核对实际下发的 bundle | ✅ 同上全部命中 |
桌面版 app.asar 无旧补丁残留 |
✅ conversation.context.actions 命中 0 处(官方 conversation.composer.dock 在位) |
lib/index.js 惰性 apply() 冒烟 |
✅ typeof === 'function'、length === 0、返回 undefined、不抛错 |
新增的 8 条测试覆盖:门控三个分支(steps === 0 / steps > 0 / tokenUsage 退路)、无 selector 的显示降级、空会话不渲染、有 step 后渲染、以及 blank→active 的 rerender 转换。
升级 TL;DR(给赶时间的人)
dsh plugin --profile web add dsh-compact-button@0.6.1- 重启
dsh web,硬刷新页面 - 桌面版从托盘彻底退出再打开
- 空会话上输入框下方没有图标;发一轮消息后,
activity→usage→ 两个图标依次排开
v0.6.0
dsh-compact-button v0.6.0
核心:这两个按钮过去要靠给平台打补丁才能拿到落脚的槽位。从这一版起,它们搬到平台自己声明、自己渲染的官方槽位 conversation.composer.dock 上——本插件不再修改任何平台文件。
PR:#6 | 已验证平台:DSH 0.2.0-rc.2(web + 桌面版)
差异规模:27 files changed, +443 / −1179(净删 736 行,删掉的基本就是补丁机制本体)
TL;DR
以前每次 dsh web 启动,插件的 host 半边都要去就地改写已安装的平台 bundle(桌面版还要动 resources/app.asar),才能塞进一个官方没声明的 conversation.context.actions 槽位。现在 conversation.composer.dock 是平台公开的 list 型扩展点,官方 dsh-client-ui-chat 的 stats 药丸就注册在那一行——我们排到它后面就行了。
于是那套补丁机器整个删掉:不改 npm 里的平台 bundle,不改桌面版归档,桌面版自更新后零操作。
顺带把 UI 改成纯图标 + 平台原生 Tooltip,观感与旁边的上下文圆环对齐。
⚠️ 这一版有破坏性变更(删了公开入口、换了槽位名、按钮不再带文字),升级前请看下面的「破坏性变更」与「升级须知」两节。
新长什么样
按钮从「带文字的方块」变成两个图标,并排在上下文圆环左侧,共用输入框下方那条居中 dock 行。
| 状态 | Tooltip(中 / EN) | 图标表现 |
|---|---|---|
| 待命 | 压缩上下文 / Compact context | 常态(三级标签色,与圆环同色系) |
| 提交中 | 压缩中… / Compacting… | 呼吸闪烁(1s 循环),按钮禁用防重复提交 |
| 已提交 | 已提交压缩 / Compaction submitted | 业务蓝 |
| 未匹配 | 命令未匹配 / Command not matched | 常态 |
| 失败 | 提交失败 / Submission failed | 错误红 |
按钮没有文字后,状态改由两条通道共同承载:Tooltip 文本 + 图标颜色。任何时候悬停 200ms 都能看到「这个按钮是干什么的」以及「上一次点击的结果是什么」,4 秒后自动复位。
aria-label 与 Tooltip 气泡同源,读屏和鼠标悬停得到的是同一句话。
⚠️ 破坏性变更
1. 删除的入口点
| 文件 | 原来是什么 |
|---|---|
patch-run.cjs |
手动打补丁的 CLI 入口 |
patch-context-meter.cjs |
补丁模块本体(435 行,导出 patchSource / patchInstalledTargets / findTargetFiles / describeResult / resolveDshHome 等) |
scripts/patch-desktop-asar.cjs |
桌面版 app.asar 改写脚本 |
src/patch-module.d.ts |
上述模块的类型声明 |
任何引用这些路径的脚本、CI 步骤、文档链接都会失效。 package.json 的 files[] 里对应条目也一并移除,装完包后这些文件不存在。
2. 槽位名变了
conversation.context.actions(我们自己注入的)→ conversation.composer.dock(平台官方的)。
旧槽位名在本插件里彻底消失,构建产物中 conversation.context.actions 命中数为 0。如果有别的东西依赖过那个名字,需要跟着改。
3. 按钮不再有可见文字
从「图标 + 文字」变成纯图标。依赖按钮文本做端到端测试的选择器(getByText('压缩上下文') 之类)需要改成 aria-label。
4. locales.ts 删了两个 key
tooltip 与 newSessionTooltip 在图标化之后没有任何组件读取,已删除。由于 package.json 的 exports 暴露了 ./src/*,dsh-compact-button/src/client/locales.ts 属于可达路径,所以这算一处对外可见的收窄:
// 0.5.x 可以,0.6.0 类型不通过
t('tooltip')现在的活 key 集合被一条测试钉住,共 6 个:label / pending / submitted / rejected / failed / newSession。
桌面版:从此不需要碰 app.asar
桌面版和 web 版走完全相同的路径——插件装进 desktop profile 即可。
平台把 bundle 打进 resources/app.asar 这件事不再构成障碍:插件代码从 profile 读,槽位由归档内的平台代码自己渲染,双方都不需要被修改。
🔄 桌面版自更新后无需任何额外操作。更新只替换
app.asar,而app.asar本来就没被动过;profile 里的插件也不受更新影响。
(dsh CLI 仍不能管理 desktop profile——那句 profile "desktop" is managed exclusively by the Electron application 是平台设计,桌面版插件的装卸由桌面 app 自己的插件管理界面负责。这条没变。)
#4 那一整类问题,结构性消失了
#4 是「DSH_HOME 迁移导致补丁扫不到平台 bundle,插件静默失效」。0.5.1 靠镜像平台的 resolveDshHome 优先级把它修好了。
0.6.0 让这类问题不可能再发生——因为根本不再有 home 解析和文件扫描:
$ grep -rn "DSH_HOME\|resolveDshHome\|homedir" src test
(no matches)
不往平台树里写东西,也就不需要知道那棵树在哪。同理,TESTED_PLATFORMS 这个「已验证平台清单」也随补丁模块一起删了——兼容性不再由锚点是否命中决定,而是由官方槽位是否存在决定。
架构变化
client 半边(功能全在这里)
ctx.slots.inject('conversation.composer.dock', ...)→slots.register({ ..., order: 1 }),排在官方stats(order: 0)之后- 单个 entry 内并排渲染
ContextActionRow(左CompactButton、右NewSessionButton),保证左右顺序确定,不受 list slot 自身排序影响 - 两个图标都用平台
Tooltipprimitive(side="top"、delayMs={200}),与圆环触发器行为一致 - 提交通道没变:
session.command('/compact')与手敲/compact完全同路;uiWorkspace.startSession()在同一 workspace 开新会话
host 半边(改为惰性)
apply() 是空实现——功能完全在 client 侧,Node 侧无事可做。
但导出必须保留:cordis.patch.yml 会把本模块挂到 Node 树并在 profile boot 时调用它,缺入口点会直接破坏挂载,而 no-op 不会。这条契约由测试守着(apply 存在、零参、返回 undefined、不抛错、不打日志)。
注意
cordis.patch.yml仍然存在,但它是 cordis 的 bundle 挂载声明层——只负责「把本插件声明进 profile」,不涉及任何对平台代码的改写。名字里的 patch 容易误会,语义上跟旧的那套补丁机器无关。
类型与测试基建
src/context-types.ts:本地slots.register面补上order字段——真实 registry 的列表显示顺序走order,不是priority,这个区别是排到stats后面的关键src/client/primitives.d.ts:用本地 ambient 声明描述Tooltip,不引入@deepseek-ai/*依赖。原因有两条:该包在 registry 上的发布版本与实际安装版本不同步,加 devDependency 会引入一个装不出来的范围;更要命的是第二份 renderer = 第二份 slot registry,会直接破坏槽位注册vitest.config.ts+test/stubs/dsh-client-ui-primitives.ts:把 primitives specifier alias 到本地 stub。Vite 在 transform 期解析 import,vi.mock顶替不了一个根本没安装的包
升级须知
从 0.5.x 升级——旧补丁不会自动撤销
0.6.0 自己不补丁了,但它不会回滚 0.5.x 已经打进去的改动。如果你之前用过 0.5.x,平台文件里可能还留着旧的槽位注入。
Web(npm 副本):0.5.x 每次启动会就地改写 @deepseek-ai/dsh-client-ui-conversation/lib/client.js。想让平台文件恢复原样,覆盖重装即可:
pnpm install @deepseek-ai/dsh-client-ui-conversation --force
# 或重装对应版本的 dsh桌面版:如果你跑过 scripts/patch-desktop-asar.cjs,那份 app.asar 里的注入还在。做一次桌面版自更新(或重装桌面 app)即可还原。
不处理会怎样:旧注入的 conversation.context.actions 渲染位现在没人注册了,会留一个空容器;不会抛错、不影响新图标工作,但视觉上可能有一条多余的空隙。建议还原。
升级步骤
dsh plugin --profile web add dsh-compact-button@0.6.0
# 重启 dsh web(仅硬刷新浏览器不够),重启后再硬刷新页面从更早版本升级
0.4.x 及更早的用户,除了上面这条,还要对照 0.5.0 / 0.5.1 的 release note 看有没有遗漏的前置动作。
兼容性
- 不声明任何 DSH peer 依赖。client bundle 只
requirereact、react/jsx-runtime、@deepseek-ai/dsh-client-ui-primitives——三者都是平台 module table 里的 external(见tsdown.config.ts的CLIENT_EXTERNALS),服务全靠 cordis 注入。包管理器不会为了满足一个范围而拉出第二份 renderer - 降级行为温和。万一上游把
conversation.composer.dock移除或改语义,slots.inject等不到声明,结果只是「图标不渲染」——不抛错、不影响平台其余部分 Tooltip签名漂移。本地 ambient 声明若与真实签名不符,多出来的 prop 只是被忽略;组件测试(stub 版)继续盯住 label / anchor 接线
已知限制
- 依赖官方槽位
conversation.composer.dock:由平台声明与渲染,上游若移除则图标停止渲染(静默降级) - dock 行是居中 flex 容器,官方
stats药丸占order: 0,本插件用order: 1。将来若出现更多占用者,视觉顺序可能需要再调 - 压缩按钮只负责提交
/compact,压缩的接纳与执行语义由 Host 侧 command-compact 插件拥有 - 状态提示 4 秒后自动复位,不提供压缩进度展示
- 新建会话沿用部署默认的 agent 预设与权限设置;若当前会话用的是非默认预设,新会话不会自动沿用(client 侧
uiWorkspace.startSession不暴露预设 / 权限选择)
包信息与校验
dsh-compact-button-0.6.0.tgz 24,765 bytes
sha256 695e981b1fc679d92de33522c4f5ea8bebc4363a062775f6e7f3513a59a574e0
包内清单(19 个文件):
LICENSE README.md README.en.md cordis.patch.yml dsh.plugin.json package.json
lib/index.js lib/client.js lib/client-registry.js
src/client/{CompactButton,ContextActionRow,NewSessionButton,index}.tsx
src/client/{compact-button.module.css,css-modules.d.ts,locales.ts,primitives.d.ts}
src/context-types.ts src/index.ts
| 产物 | 通道 |
|---|---|
lib/index.js |
Host 半边(惰性 apply(),维持 bundle 挂载契约) |
lib/client.js |
官方 profile 通道(bundle id = 包名 dsh-compact-button) |
lib/client-registry.js |
插件注册表通道(bundle id = manifest id dsh-external/dsh-compact-button) |
包内不含 patch-*.cjs,files[] 里也无对应条目。
验证结果
| 项 | 结果 |
|---|---|
pnpm typecheck |
✅ 无错误 |
pnpm test |
✅ 6 files / 51 tests passed |
pnpm build |
✅ 三个 bundle 齐全 |
产物含 conversation.composer.dock |
✅ 3 处 |
产物含 order: 1 |
✅ |
产物含 dshCompactPulse keyframes |
✅ 4 处 |
@deepseek-ai/dsh-client-ui-primitives 保持 external(未被 bundle 进来) |
✅ |
产物含旧 conversation.context.actions |
✅ 0 处 |
| 产物携带已删除的死 key 字符串 | ✅ 0 处 |
lib/index.js 惰性 apply() 冒烟 |
✅ typeof === 'function'、length === 0、返回 undefined、不抛错 |
| 平台保持原样加载 | ✅ web 的 npm 副本与桌面版 app.asar 加载后均 marker=0(未被改写) |
升级 TL;DR(给赶时间的人)
dsh plugin --profile web add dsh-compact-button@0.6.0- 重启
dsh web,硬刷新页面 - 从 0.5.x 升上来的话,覆盖重装一次
@deepseek-ai/dsh-client-ui-conversation把旧补丁还原掉 - 输入框下方、圆环左侧应该出现两个图标;悬停 200ms 能看到说明
v0.5.1
dsh-compact-button v0.5.1
修复:#4 — bug:DSH_HOME 目录迁移造成的错误
已验证平台:0.1.2-rc.1、0.1.5-rc.1、0.1.5-rc.2、0.1.5-rc.3、0.1.7-alpha.2、0.1.7-rc.1、0.1.7-rc.2
TL;DR
把 DSH 的 harness home 搬到非默认位置(例如 DSH_HOME=D:\AppData\DSH)的用户,从这一版起按钮会正常渲染。此前补丁只认 os.homedir()/.dsh,迁移后的 profiles 树根本扫不到,插件装上了也是静默失效。
顺带把 DSH 0.1.7 纳入已验证范围(0.1.7-rc.1 / 0.1.7-rc.2),这是 #4 里一并提出的诉求。
纯补丁层修复,无功能变更、无破坏性变更:压缩按钮与新建会话按钮的行为、样式、文案与 0.5.0 完全一致。
修复了什么
1. $DSH_HOME 迁移后补丁找不到平台 bundle(#4 主因)
补丁脚本靠扫描 <home>/profiles 与 <home>/profiles/<name> 来定位 @deepseek-ai/dsh-client-ui-conversation(host 半边每次启动自愈走的就是这条路)。0.5.0 里这个 <home> 硬编码为 os.homedir()/.dsh,一旦 harness home 被 DSH_HOME 重定位,扫描就落空——不报错、不打补丁、按钮不渲染,用户端表现为"装了没反应"。
现在插件镜像平台自己的 resolveDshHome 语义来解析 home,补丁会跟着迁移走。
2. 显式配置的 harness home 同样跟随(#4 补充)
平台的 resolveDshHome(configured, env) 在 $DSH_HOME 之上还有一层显式配置(各 home-aware 服务收到的 config.dshHome)。0.5.1 把这一层也补齐,并给手动 CLI 加了 --dsh-home 入口。
3. 适配 DSH 0.1.7
0.1.7-rc.1、0.1.7-rc.2 已加入 TESTED_PLATFORMS。补丁锚点本身是格式无关的正则语义锚,没有为 0.1.7 改过任何锚点——只是把"验证过"这件事写实了。
4. 文档一致性
两个 README 的"已验证平台"列表上个版本停在 0.1.7-alpha.2,而代码里的 TESTED_PLATFORMS 已经走到 0.1.7-rc.2,现已对齐。
home 解析优先级
| 优先级 | 来源 | 示例 |
|---|---|---|
| 1 | 显式配置(对应平台 config.dshHome) |
--dsh-home D:\AppData\DSH |
| 2 | 环境变量 | DSH_HOME=D:\AppData\DSH |
| 3 | 默认 | ~/.dsh |
- 任一层都支持
~/~//~\展开,结果一律绝对化。 - 空白值视为未设置:
DSH_HOME=" "不会把 home 挪走。 - 只加不减:显式主目录是并集而非替换。平台的
profiles树由 env 解析,而config.dshHome是按服务的覆盖,两者可能指向不同根;若让显式层直接取代 env,就会把 #4 刚修好的那条路重新弄断。所以探测顺序是[configured, env/默认]去重,补丁幂等,重叠零成本。
一处有意偏离平台
平台用 ??,空字符串的 configured 会赢并把 home 解析到 cwd。补丁脚本是要往树里写东西的,因为一个空串就开始探测当前目录并不安全,所以本插件让空 configured 继续穿透到 env / 默认。已在 JSDoc 中写明。
新增 CLI
# 手动提前打补丁,并指定一个非默认的 harness home
node node_modules/dsh-compact-button/patch-run.cjs --dsh-home D:\AppData\DSH
# 也支持 --dsh-home=D:\AppData\DSH找不到目标时,告警现在会列出实际探测过的 home,并提示用 --dsh-home / $DSH_HOME 指路:
[dsh-compact-button patch] no @deepseek-ai/dsh-client-ui-conversation install found; ...
[dsh-compact-button patch] homes probed: C:\Users\me\.dsh — point at a relocated harness with --dsh-home <path> or $DSH_HOME
升级
dsh plugin --profile web add dsh-compact-button@0.5.1
# 重启 dsh web,然后硬刷新页面(仅刷新浏览器不够)不需要手动重打补丁——host 半边每次 dsh web 启动都会自愈。本包依旧不含任何 install 脚本(不依赖 postinstall)。
从 0.5.0 升级无需改任何配置:没迁移过 home 的用户走的是同一条默认路径,行为不变。
验证记录
| 项目 | 结果 |
|---|---|
pnpm test |
✅ 7 files / 81 passed(较 0.5.0 新增 10 条 home 解析与 CLI 用例) |
pnpm typecheck |
✅ exit 0 |
pnpm build |
✅ lib/index.js、lib/client.js、lib/client-registry.js 全新构建 |
| 与平台实现对齐 | ✅ 7 组 configured / env 组合逐一比对真实 @deepseek-ai/dsh-home-paths,全部一致 |
| 锚点未漂移 | ✅ 对 npm 上 pristine 的 0.1.5-rc.3 / 0.1.7-rc.1 / 0.1.7-rc.2,5 个锚点各恰好命中 1 次 |
| 迁移 home 端到端 | ✅ DSH_HOME=A + --dsh-home B → A、B 两处 bundle 均打上补丁;二次运行 already;取消 env 后 A 不再被发现(证明真由 env 驱动) |
| 打包产物端到端 | ✅ 解包 tarball 后重跑上述场景,6/6 通过(非源码目录验证) |
| 包内容 | ✅ 21 个文件,与 files 白名单一致,无 test / 多余产物泄漏 |
| 失败降级 | ✅ 找不到目标时退出码仍为 0,不打断 dsh 启动 |
已知限制
- 插件不解析 cordis 配置文件里的
config.dshHome。要做到这点就得引入平台依赖,与本包"零 DSH peer 依赖、不跟 DSH 发版走"的设计冲突。因此"显式配置"这一层通过 CLI--dsh-home表达。若你的部署里config.dshHome与$DSH_HOME指向不同根,两处都会被探测并打补丁——宁可多打一处也不漏。 0.2.0-rc.1/0.2.0-rc.2尚未验证,未纳入 tested set。届时若锚点仍命中会正常打补丁并标注(not in the tested set);若漂移则只是按钮不渲染 + 一条明确日志,不会污染平台文件。- 迁移后若仍看不到按钮,先看启动日志里的
homes probed:一行确认探测范围,再核对 profile 下是否真的装了@deepseek-ai/dsh-client-ui-conversation。
产物校验
dsh-compact-button-0.5.1.tgz 36,063 bytes
sha256 a56eb7420d758026a229a442cbf11de9e609f02928017fee442f65b6d55f6564
English
dsh-compact-button v0.5.1
Released: 2026-09-30 · PR: #5 · Fixes: #4
Verified platforms: 0.1.2-rc.1, 0.1.5-rc.1/2/3, 0.1.7-alpha.2, 0.1.7-rc.1, 0.1.7-rc.2
TL;DR
Users who relocated the DSH harness home (e.g. DSH_HOME=D:\AppData\DSH) now get their buttons rendered. The patch used to probe only os.homedir()/.dsh, so a relocated profiles tree was never discovered — the plugin installed silently and did nothing. DSH 0.1.7 is now in the verified set too.
Patch-layer only: no functional or breaking change. Compact and New Session buttons behave exactly as in 0.5.0.
What changed
$DSH_HOMErelocation (the #4 root cause) — the standard-root probe now resolves the harness home with the platform's ownresolveDshHomesemantics instead of a hardcoded~/.dsh.- Explicitly configured home is honored too — the layer above
$DSH_HOME(the platform'sconfig.dshHome), plus a--dsh-home <path>flag on the manual CLI. - DSH 0.1.7 verified —
0.1.7-rc.1/0.1.7-rc.2added toTESTED_PLATFORMS; no anchor was touched for 0.1.7. - Docs consistency — both READMEs' verified-platform list caught up with
TESTED_PLATFORMS.
Home precedence
| # | Source | Example |
|---|---|---|
| 1 | Explicit config (platform config.dshHome) |
--dsh-home D:\AppData\DSH |
| 2 | Environment | DSH_HOME=D:\AppData\DSH |
| 3 | Default | ~/.dsh |
~ / ~/ / ~\ are expanded, results are absolute, and a blank value counts as unset at every layer. The explicit home adds a probe rather than replacing one: the platform resolves its profiles tree from the env while config.dshHome is per-service, so narrowing to the explicit home would re-break #4. Probes run over [configured, env/default], de-duplicated; patching is idempotent.
One deliberate divergence: the platform uses ??, so an empty configured wins and resolves the home to the cwd. A patcher that writes into the tree should not start probing cwd because of an empty string, so a blank configured falls through to env/default here.
New CLI
node node_modules/dsh-compact-button/patch-run.cjs --dsh-home D:\AppData\DSHWhen nothing is found, the warning now lists the homes it actually probed and hints at --dsh-home / $DSH_HOME.
Upgrade
dsh plugin --profile web add dsh-compact-button@0.5.1
# restart dsh web, then hard-refresh the pageNo manual re-patch needed — the host half self-heals on every start, and the package still ships no install scripts. Users who never relocated their home follow the exact same default path as before.
Verified
81 tests pass (+10 new), typecheck clean, fresh build. Parity with the real @deepseek-ai/dsh-home-paths across 7 configured/env combinations. All five anchors match exactly once on pristine 0.1.5-rc.3 / 0.1.7-rc.1 / 0.1.7-rc.2 from npm. End-to-end: DSH_HOME=A + --dsh-home B patches both, second run is already, unsetting the env stops finding A. The packed tarball was re-tested after extraction (6/6), ships 21 files matching the files whitelist, and still exits 0 when nothing is found so dsh boot is never broken.
Known limitations
- The plugin does not parse
config.dshHomeout of cordis config files — that would require a platform dependency, against this package's zero-platform-dependency design. The explicit layer is expressed via--dsh-home. If the two point at different roots, both get patched. 0.2.0-rc.1/0.2.0-rc.2are not yet verified and are outside the tested set.- Still no button after relocating? Check the
homes probed:line in the startup log, then confirm the profile actually has@deepseek-ai/dsh-client-ui-conversationinstalled.
v0.5.0
v0.5.0 — 不再跟着 DSH 发版走
发布日期:2026-09-23
npm:dsh-compact-button@0.5.0
上一版:v0.4.1(6e0080c)
TL;DR
DSH 出新版本时,本插件不需要再改版本号或依赖范围了。补丁锚点改成格式无关的语义正则,兼容性由结构决定而不是由 semver 范围决定。已实测通过 0.1.2-rc.1 → 0.1.7-alpha.2 共 5 个平台版本。
为什么需要这个版本
之前 package.json 把四个 DSH 包 pin 在 ~0.1.2-rc.1。在 rc 阶段这意味着每发一个 rc 就得改一次本插件:
semver.satisfies('0.1.5-rc.1', '~0.1.2-rc.1') // false
semver.satisfies('0.1.5-rc.1', '*') // false ← 连 * 都不匹配 prerelease
semver.satisfies('0.1.5', '^0.1.2') // true ← 只有 stable 能跨
semver 只在同一个 [major.minor.patch] tuple 内承认 prerelease,所以 ~0.1.2-rc.1 天然拒绝 0.1.5-rc.1。
更糟的是这个 pin 还会主动制造故障:包管理器为了满足范围,会在 profile 里额外拉一份 dsh-client-ui-renderer——两份 renderer = 两个 slot registry = 槽位注册直接失效。而本插件运行期根本不 import 任何平台包。
本次变更
1. 移除全部 DSH peer 依赖(BREAKING-adjacent,但对使用者无感)
从 peerDependencies / devDependencies 中移除:
@deepseek-ai/dsh-api-session-controller@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-workspace
保留 react(真实 peer)与 optional 的 cordis。
为什么安全:client bundle 只 require("react"),host 半边只用 node:*,所有平台服务都通过 cordis 注入拿到,类型由 src/context-types.ts 的结构镜像描述。pnpm install 后依赖树少了 66 个包。
2. 补丁锚点改为格式无关的正则语义锚
patch-context-meter.cjs 的 5 条锚点不再是「精确 tab 缩进的源码字面量」,而是语义正则,容忍:
| 上游构建差异 | 是否仍命中 |
|---|---|
| css module 重新哈希 | ✅(前缀从 bar 条目现场捕获,从不写死) |
| tab ↔ 空格(整文件重排) | ✅ |
var → const |
✅ |
| 对象 key 去引号 | ✅ |
| jsx helper 改名 | ✅ |
| 函数签名压缩成一行 | ✅ |
安全模型完全保留:每条锚点仍要求「命中恰好 1 次」,否则整体不写入;已打补丁的文件靠 marker 跳过。真出问题时是「按钮不渲染 + 一条指名锚点的日志」,绝不污染平台文件、绝不阻塞 dsh 启动。
3. 启动日志自检
[dsh-compact-button] platform patch applied: .../dsh-client-ui-conversation/lib/client.js @0.1.5-rc.2
未测过的版本会额外标注 (not in the tested set)。TESTED_PLATFORMS 只是文档,从不卡补丁——出新版本时什么都不用改,启动一次看日志即可。
4. 新增英文文档
README.en.md,与中文 README 逐节对应(含版本兼容策略章节),两个文件顶部互链。已加入 npm files 随包发布。
5. 修正「新建会话」tooltip 的误导文案
tooltip 原文承诺 "the same agent preset and permissions",但 uiWorkspace.startSession() 不暴露预设/权限选择——当前会话若用非默认预设,新会话不会沿用。现改为「沿用部署默认设置」(zh/en 同步)。
兼容性验证
对真实 npm 安装的每个版本跑完整检查(非推测):
| 平台版本 | 补丁命中 | node --check |
槽位声明 | 服务 API | 幂等 |
|---|---|---|---|---|---|
0.1.2-rc.1 |
✅ patched | ✅ | ✅ | ✅ | ✅ |
0.1.5-rc.1 |
✅ patched | ✅ | ✅ | ✅ | ✅ |
0.1.5-rc.2 |
✅ patched | ✅ | ✅ | ✅ | ✅ |
0.1.5-rc.3(latest/next) |
✅ patched | ✅ | ✅ | ✅ | ✅ |
0.1.7-alpha.2(alpha) |
✅ patched | ✅ | ✅ | ✅ | ✅ |
服务 API 覆盖:slots.inject / slots.register / sessions.binding().session.command() / uiWorkspace.startSession() / locale.register()。
注意:dsh@0.1.5-rc.1 的 ^0.1.5-rc.1 实际会解析到 0.1.5-rc.2 的 client bundle(同 tuple 内更新的 rc,符合 semver)。所以 CLI 版本号 ≠ 实际 bundle 版本,这也正是「不跟版本走」的价值所在。
升级方式
dsh plugin --profile web add dsh-compact-button@0.5.0
# 重启 dsh web,然后硬刷新浏览器(Ctrl/Cmd+Shift+R)file: 装过同名版本,pnpm 会认为依赖未变而跳过刷新,需先卸再装:
dsh plugin --profile web remove dsh-compact-button
dsh plugin --profile web add dsh-compact-button@0.5.0已打过补丁的平台 bundle 会被 marker 识别并跳过(already patched),无需手动还原。
没有变的东西
- 仍然不含任何安装脚本(不依赖 postinstall,任何包管理器安装都不会报错或被拦截)
- 补丁仍然幂等 + 漂移即安全中止
- 按钮行为、状态流转、i18n 全部不变
- 仍然依赖
conversation.context.actions这个由本插件注入的子槽位
已知限制(延续)
- 补丁只作用于
@deepseek-ai/dsh-client-ui-conversation的lib/client.js;上游若拆分/改名该 bundle,findTargetFiles会找不到目标,需要跟进 - 压缩按钮只提交
/compact,接纳与执行语义由 Host 侧 command-compact 插件拥有 - 状态提示 4 秒后自动复位,不提供压缩进度展示
- 新建会话沿用部署默认 agent 预设与权限,不沿用当前会话的非默认预设
English
v0.5.0 — no longer tied to DSH releases
Every new DSH release used to force a bump here. The peer range is gone and the patch anchors are now format-agnostic semantic regexes: compatibility is decided by structure, not by a semver range. Verified against 5 platform versions from 0.1.2-rc.1 to 0.1.7-alpha.2.
Why the old pin was actively harmful: ~0.1.2-rc.1 rejects 0.1.5-rc.1 (semver admits a prerelease only inside its own [major.minor.patch] tuple — even * rejects prereleases), so every rc needed a change. Worse, satisfying that range made package managers pull a second copy of dsh-client-ui-renderer into the profile — two renderers means two slot registries, which breaks slot registration outright. The plugin imports nothing from the platform at runtime.
What changed
- Removed all four DSH peer/dev dependencies. The client bundle requires only
react; the host half uses onlynode:*; every service arrives via cordis injection. 66 fewer packages in the dependency tree. - Format-agnostic patch anchors. The 5 anchors tolerate indentation changes, quoting,
var/let/const, the css-module hash (captured live from thebarentry, never hardcoded) and jsx helper renames. The exactly-once assertion and marker-based idempotence are fully preserved — a real structural change still aborts without writing, degrading to "buttons not rendered + one clear log line" and never polluting platform files or blocking dsh. - Startup self-reporting. The log prints the detected platform version, with
(not in the tested set)for versions not exercised.TESTED_PLATFORMSis documentation only and never gates the patch. - English README (
README.en.md), shipped in the npm package, cross-linked from both files. - Fixed a misleading new-session tooltip. It promised "the same agent preset and permissions", but
uiWorkspace.startSessionexposes no preset or permission selection — a non-default preset is not inherited. Now says the deployment default.
Install / upgrade
dsh plugin --profile web add dsh-compact-button@0.5.0
# restart dsh web, then hard-refresh (Ctrl/Cmd+Shift+R)If you previously installed the same version via file:, pnpm treats the dependency as unchanged and skips the refresh — remove first, then add. Bundles already patched are detected by marker and skipped (already patched).
Unchanged: still no install scripts, still idempotent and abort-on-drift, no change to button behaviour, state transitions or i18n.
v0.4.1
dsh-compact-button v0.4.1
适配 DSH 0.1.2-rc.1 — 平台补丁锚点更新 + 依赖版本 bump,功能代码零改动。
✨ 更新内容
- 适配 DSH 0.1.2-rc.1:peer / dev 依赖全部从
0.1.2-alpha.2更新到0.1.2-rc.1 - 平台补丁锚点更新(
patch-context-meter.cjs):rc.1 给conversation.composer.bar新增了 4 个子槽位(input.overlay/input.overlay.left/input.overlay.right/composer.dock),第 5 条补丁的锚点从conversation.input.model条目改挂到新末项conversation.composer.dock;其余 4 条锚点与 rc.1 真实 bundle 逐字节一致,未改动 - 确认 rc.1 仍不原生声明
conversation.context.actions槽位,自动应用的平台补丁继续必要 - 客户端功能代码零改动:「压缩上下文」「新建会话」按钮、状态流转、中英文实时切换行为与 0.3.2 一致
- README、测试注释中的版本引用同步更新
🔧 升级方式
# npm(发布后)
dsh plugin --profile web add dsh-compact-button@0.4.1- 本版本要求 DSH 0.1.2-rc.1 平台;仍在使用 0.1.1-rc2 平台的请继续用 0.3.2
- 平台版本不匹配时补丁逐条断言失败、安全中止:按钮安静地不渲染,平台其余部分不受影响;host 半边在每次
dsh启动时会自动重新打补丁(自愈)
✅ 验证
- 补丁作用于 npm 上 0.1.2-rc.1 真实
ui-conversationbundle:5 条替换全部命中,打补丁后的 bundle 通过 ESM 语法检查(node --check) - 幂等性保持:对已打补丁的 bundle 重跑会跳过(marker 检测)
⚠️ 注意
- 平台补丁仍是针对平台 bundle 的字符串级替换:若上游(rc.2 / stable)再次调整
composer.bar槽位表结构,锚点需再次更新(README「已知限制」有说明) - 已知限制无变化,详见 README
v0.3.2
v0.3.2 — 自动平台补丁:任何机器上按钮都能直接出现
背景
此前有用户反馈 Windows 环境下插件按钮不可见。排查确认根因:官方 @deepseek-ai/dsh-client-ui-conversation@0.1.1-rc.2 并未声明 conversation.context.actions 槽位(GitHub 源码、npm 产物、全量槽位清单三重验证均为 0 匹配),而按钮依赖该槽位渲染。之前开发机可见,是因为本机打过一次手工补丁——这条路径无法分发给用户。
核心变化:补丁随包分发,全自动应用
本版本把平台补丁产品化并纳入插件包:
- 🔩 启动自愈:每次
dsh web启动时,host 半边自动定位已安装的平台 bundle(dsh-client-ui-conversation/lib/client.js),就地注入conversation.context.actions槽位的声明与渲染 - 🔁 幂等且漂移安全:已打过补丁的文件按标记跳过;平台版本漂移(非 0.1.1-rc.2 布局)时逐条断言匹配失败即安全中止,不写入任何半补丁文件,按钮安静地不渲染,插件其余部分不受影响
- 📦 零安装脚本:不依赖
postinstall——pnpm ≥ 10 默认屏蔽依赖构建脚本(ERR_PNPM_IGNORED_BUILDS),实测证明启动自愈已完整覆盖补丁链路,因此本包不含任何安装脚本,任何包管理器安装都干净无警告 - 🛠️ 可选手动 CLI:如需在启动前提前打好补丁:
node node_modules/dsh-compact-button/patch-run.cjs
其他改进
- CSS 防御:所有
var(--dsw-alias-*)增加 fallback 值,平台变量缺失时按钮依然可见 - 构建依赖清理:移除硬编码的
@rolldown/binding-darwin-arm64(平台特定原生绑定按平台自动安装) - 测试:55 个单元测试通过,新增补丁幂等/漂移/定位/语法安全回归测试
版本说明
| 版本 | 状态 |
|---|---|
0.2.0 |
|
0.3.2 |
✅ 当前版本,latest 已指向此版本 |
升级方式
dsh plugin --profile web add dsh-compact-button@latest
# 重启 dsh web,硬刷新浏览器(Cmd/Ctrl+Shift+R)v0.2.0
v0.2.0 — 新增「新建会话」按钮
在上下文计量面板里,「压缩上下文」按钮右侧新增了一个「新建会话」按钮:一键在当前 workspace 开启新会话并跳转过去。
✨ 新特性
🆕 新建会话按钮:点击即调用 workspaces.startSession(),在当前会话所在的 同一 workspace 中开启一个新会话并自动跳转;新会话沿用部署默认的 agent 预设与权限设置
🖱️ 防误连点:点击后按钮短暂锁定约 1.5 秒,避免重复创建会话
🌏 中英文文案:新增 newSession / newSessionTooltip 词条,跟随 DSH 语言设置实时切换
🔧 变更
面板槽位条目从单个按钮改为并排容器 ContextActionRow:左「压缩上下文」、右「新建会话」,顺序由组件内部保证,不受 list 槽位条目排序影响
client 半边新增 workspaces 服务注入(inject 现为 ['slots', 'sessions', 'workspaces', 'locale'])
CSS 新增 .row 布局:两个按钮等宽并排,面板样式保持清爽
README 更新:功能说明、使用方式、已知限制同步,并加入动态演示
🧪 质量
新增 Vitest 单测体系:6 个测试文件、46 个用例,覆盖按钮相位机、新建会话锁定逻辑、组合布局与 client 接线
安装/升级后需重启 dsh web 并硬刷新浏览器才能生效
已知限制:若当前会话使用非默认 agent 预设,新会话不会自动沿用(client 侧 session.create 不暴露预设/权限选择)
📦 安装
dsh plugin --profile web add dsh-compact-button@latest
v0.1.0
v0.1.0 — 首个正式版
✨ 新功能
🔘 一键压缩上下文:在 DSH Web 输入框旁的上下文计量面板(ContextMeter)中新增「压缩上下文」按钮,点击即向当前会话提交 /compact,把较早的对话历史压缩成摘要,为后续对话腾出上下文空间
🔁 与 /compact 完全同通道:按钮调用 session.command('/compact'),和手敲斜杠命令走的是同一条命令通道,压缩结果照常以命令行形式出现在对话流中,无任何特殊行为
📊 提交状态实时反馈:按钮文案随状态流转——待命 → 提交中 → 已提交 / 命令未匹配 / 提交失败,4 秒后自动回到待命
🌏 中英文实时切换:文案跟随 DSH 语言设置(zh / en),字典注册在 compactButton 命名空间
📦 一键安装:支持 dsh plugin --profile web add "file:<路径>/dsh-compact-button-0.1.0.tgz";包内自带 bundle patch,安装后自动写入 dsh.profile.bundles 并自动挂载,聚合包重复挂载时自动退让
🪶 零侵入:纯 client 半边插件(host 半为空 apply);槽位不存在时按钮安静地不渲染,不影响平台其余部分
🚀 安装
pnpm install && pnpm build && pnpm pack
dsh plugin --profile web add "file:<你的本地目录>/dsh-compact-button-0.1.0.tgz"
装完重启 dsh web 并硬刷新浏览器(Cmd/Ctrl+Shift+R)即可生效。
依赖平台为上下文计量面板声明 conversation.context.actions 子槽位(需随附的平台补丁);槽位缺失时按钮不渲染,插件其余部分不受影响
本插件安装或更新后需重启 dsh web 才能生效,仅硬刷新浏览器不够
压缩的接纳与执行语义由 Host 侧 command-compact 插件拥有,本插件仅负责提交命令
🔧 环境要求
DSH 0.1.1-rc.1(@deepseek-ai/dsh-client-* ^0.1.1-rc.1)
Node.js ≥ 20、pnpm ≥ 10