- Главная
- Плагины
- Сеансы и сообщения
- dsh-approval-card
dsh-approval-card
ystian2k/dsh-approval-card
Approve tool requests from any session in place, without switching back to its conversation.
Установка
dsh plugin --profile web add github:ystian2k/dsh-approval-cardREADME
dsh-approval-card

Surface pending tool approvals, plan reviews and user questions from any session as a card on the right side of the DSH web UI — and answer them in place, without switching back to the session that asked.
Unlike reminder-only plugins, the buttons here actually answer the request: they call the official pendingInteraction.answer('allowed-once' | 'rejected').
Design note — it does not hijack the approval waterfall. The plugin only reads uiSession.sessionStatus and calls the official answer(). It never registers its own approval/request listener, so if it fails to load, throws, or its overlay is torn down by an SPA route change, the built-in approval card keeps working. Approvals can never be stranded.
- Compatible with
@deepseek-ai/dsh-desktop>=0.2.1-rc.3 <0.3.0— verified on0.2.1-rc.5, earlier0.2.1-rc.*declared but untested - Client-side only — the host half (
index.js) is intentionally near-empty - Five color themes, seven tunable parameters, named presets, usage stats
- MIT
English documentation: README.en.md
在当前对话里就能处理其他对话排队中的审批,不用切回去点卡片。
它怎么工作(重要)
插件不接管 approval/request waterfall。
官方 @deepseek-ai/dsh-client-ui-approval 会把每条待审批登记成一个 PendingApproval
对象,放进客户端 uiSession 服务的 sessionStatus 快照里:
uiSession.sessionStatus.getSnapshot() → Map<sessionId, { running, pendingInteraction, completionUnread }>
pendingInteraction 自带 answer('allowed-once' | 'rejected'),就是官方按钮调的那个方法。
本插件订阅这份快照,把各处待审批渲染成右侧卡片,点按钮时直接调 answer()。
因此:
- 官方审批卡照常出现,当前会话的体验与原生一致;
- 本插件加载失败、服务名变化、渲染出错 —— 都只是卡片不出现, 审批本身仍由官方 UI 完成,不可能再把审批卡死。
历史教训:早先的实现是
remote.$on('approval/request', ...)并返回自己解决的 Promise。 waterfall 是「谁先作答谁赢」,官方卡片于是拿不到请求;一旦卡片没能显示出来, 这笔审批就彻底没有 UI(表现为「审批根本出不来」)。
呈现方式
注册到官方的 shell.overlay 槽位(覆盖整帧的浮层,位置自己定):
position: fixed; right: 16px; top: 50%; transform: translateY(-50%)
flex-direction: column; gap: 8px; max-height: 80vh; overflow-y: auto
- 右侧、竖直居中:多笔审批时每笔一张卡纵向堆叠,整体包围盒的中点落在屏幕竖直中线上;
- 卡片默认宽 320px(可调),浅色(白米底 + 琥珀描边 + 深色实心「允许一次」),与官方审批卡同一套语言;
- 内容:
● 等待审批+ 会话名 / 工具名 / 原因(最多 3 行)/拒绝允许一次。
三类待办都支持
按 pendingInteraction.kind 分岔,同一份快照里混着也能一起显示:
| kind | 卡片 | 提交 |
|---|---|---|
approval | 等待审批 / 工具名 / 原因 / 拒绝·允许一次 | answer('allowed-once' | 'rejected') |
plan-review | 需要回答 / 计划正文(可滚动)/ 批准·拒绝 | { answers: [{ id, selected: [label] }] } |
question | 需要回答 / 题目 + 选项(单选·多选)+ 输入框 | 同上,可带 custom |
自定义输入按官方规则拼 payload:
- 没填 →
{ id, selected: [labels…] } - 填了 + 单选 →
{ id, selected: [], custom: "…" } - 填了 + 多选 →
{ id, selected: [labels…], custom: "…" }
plan-review 不是独立类型:PendingQuestion 的 kind 由 planReviewOf(questions)
决定(单题、单选、options ≤ 2、能匹配 intent.approve),questions[0].detail 是计划正文。
组件只 createQuestionPane() 一次。每次渲染都新建一个组件函数会让 React
认为类型变了、卸载重挂,useState 里的选中状态每 800ms 轮询都被清空
(表现为"点好几下也选不中")。
已知限制
同一会话里的多笔审批,官方只保留一个 pendingInteraction 槽位
(publishPendingInteractions 按 sessionId 合并),所以同会话并发只显示最新一笔;
跨会话才能同时看到多张卡。
适用范围
- 只在 Windows +
@deepseek-ai/dsh-desktop 0.2.1-rc.5上实测过;peerDependencies声明的是>=0.2.1-rc.3 <0.3.0,rc.3/rc.4未经测试——如果看不到卡片,官方审批卡仍照常工作 (插件不接管 waterfall)。版本跨度更大时请先确认官方 API 没变(关键依赖是uiSession.sessionStatus与shell.overlay槽位)。 - 浏览器侧插件,DSH 窗口最小化时看不到卡片(审批不会丢,恢复窗口后仍在)。
- 同会话并发只显示最新一笔(见上)。
为什么不用自己量位置
早先版本用 position: fixed + 量 composer 的 getBoundingClientRect() 来对齐,
在审批接管 composer 时会量到 0×0(官方是「把默认 composer 盖在下面」,不是卸载),
于是横条被缓存值钉死。官方文档对此有明确禁令:
Do not read another plugin's DOM, stylesheet, or component source to estimate placement; choose a slot that already allocates space. ——
dsh-agent-preset/skills/cordis-plugin-development/references/ui-plugin.md
现在的做法就是「选一个已经分配好空间的槽位」。
安装
作为用户
cd ~/.dsh/profiles/desktop
npm i dsh-approval-card
然后编辑同一个目录下的 package.json,把它加进 dsh.profile.bundles:
"dsh": {
"profile": {
"bundles": ["...", "dsh-approval-card"]
}
}
完全退出 DSH 再启动(改了 package.json 需要重启,不是刷新)。
装好后右侧平时是空的——只有存在待审批/待回答时卡片才出现,处理完自动消失。 参数在「设置 → 审批浮层」里调。
作为开发者
三个文件的分工:
package.json # dsh.client.inject / dsh.bundle.patch
cordis.patch.yml # id 必须等于 client.js 里 __ModuleLoader__.load({ id })
index.js # 宿主半边:默认空实现
client.js # 客户端:注册 shell.overlay 卡片的 React 组件
用 link: 方式挂本地源码时,部署到两处(必须 md5 一致):
~/.dsh/plugins/dsh-approval-card/client.js
~/.dsh/profiles/desktop/node_modules/dsh-approval-card/client.js
改客户端代码后 Ctrl+R 即可(控制台会出现
[dsh-approval-card] mounted mode-ready build=<版本>);只有改 index.js / package.json
才需要退出应用重启。
调试
默认不写文件、不发网络请求。需要看内部状态时:
- 浏览器控制台执行
localStorage.setItem('dsh-approval-card:debug', '1'),刷新——卡片会把自检信息 POST 给宿主; - 以
DSH_APPROVAL_CARD_DEBUG=1启动 DSH,宿主才会注册/dsh-approval-card/report并把回贴写进~/.dsh/approval-card-debug.log。
两个开关都关掉时,插件除了渲染卡片之外不产生任何副作用。
可调参数(设置 → 审批浮层)

不同屏幕尺寸下默认值兜不住,所以都做成可调的:
| 参数 | 默认 | 范围 | 作用 |
|---|---|---|---|
| 卡片宽度 | 320px | 240–560 | 卡片有多宽(右侧贴边,变宽向左扩) |
| 顶部留白 | 50px | 0–200 | 卡片顶部内边距 |
| 底部留白 | 280px | 0–400 | 滚到底时最后一条与窗口底部的距离(避开右下角悬浮控件) |
| 最大高度 | 75vh | 40–100 | 超过就出滚动条 |
| 顶部渐隐 | 50px | 0–80 | 上边缘淡出多少(只在可滚动时出现) |
| 底部渐隐 | 50px | 0–80 | 下边缘淡出多少 |
| 字体大小 | 14px | 10–18 | 正文基准,小标签 −1、最小字 −2 |
每项都有滑块可拖(拖动即生效);也可以直接在数字框里输入,失焦或回车才保存。
存放在 localStorage[dsh-approval-card:settings];
ShellBar 每 800ms 重读一次,所以改完不用刷新(页面底部有「已保存 / 有未保存的修改」提示)。
使用统计
设置页底部显示「本插件已处理:允许 N 次 · 拒绝 M 次 · 回答 K 次」,以及最近一次的时间。
只统计通过本插件完成的动作(点卡片按钮 / 提交回答),用官方卡片处理的不计。
存在 localStorage[dsh-approval-card:stats],字段 allowed / rejected / answers / lastAt。
配色
五套色相:琥珀(默认,与官方审批卡一致)、靛蓝、松绿、紫藤、樱粉。
设置页里点色点即换,卡片底色、描边、选中态、主按钮会一起换。存在 hue 字段。
审批卡详细度
两档:详细(会话名 + 工具名 + 原因)与 简略(只留一行「会话名 · 工具名」)。
存在 detail 字段,默认 full。
参数模板
设置页可把当前整套参数(含配色、详细度)命名存下来,列表里点「应用」即恢复,点「删除」移除。
存在 localStorage[dsh-approval-card:templates],格式 { 模板名: { ...settings } }。
「恢复默认值」回到代码里的 DEFAULTS;「查看当前参数」显示一行 JSON,方便复制备份。
Похожие плагины
dsh-web-ui (dsh-chat-recovery)
zhu1090093659/dsh-web-ui
billion-context
ranxianglei/billion-context
dsh-synapse
liangmianya/dsh-synapse
dsh-archive-manager
michengai/dsh-archive-manager