Zum Hauptinhalt springen
Y

dsh-approval-card

ystian2k/dsh-approval-card

Approve tool requests from any session in place, without switching back to its conversation.

Installation

dsh plugin --profile web add github:ystian2k/dsh-approval-card

README

dsh-approval-card

npm version license

在右侧处理其他会话的审批

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 on 0.2.1-rc.5, earlier 0.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 才需要退出应用重启。

调试

默认不写文件、不发网络请求。需要看内部状态时:

  1. 浏览器控制台执行 localStorage.setItem('dsh-approval-card:debug', '1'),刷新——卡片会把自检信息 POST 给宿主;
  2. 以 DSH_APPROVAL_CARD_DEBUG=1 启动 DSH,宿主才会注册 /dsh-approval-card/report 并把回贴写进 ~/.dsh/approval-card-debug.log。

两个开关都关掉时,插件除了渲染卡片之外不产生任何副作用。

可调参数(设置 → 审批浮层)

设置面板

不同屏幕尺寸下默认值兜不住,所以都做成可调的:

参数默认范围作用
卡片宽度320px240–560卡片有多宽(右侧贴边,变宽向左扩)
顶部留白50px0–200卡片顶部内边距
底部留白280px0–400滚到底时最后一条与窗口底部的距离(避开右下角悬浮控件)
最大高度75vh40–100超过就出滚动条
顶部渐隐50px0–80上边缘淡出多少(只在可滚动时出现)
底部渐隐50px0–80下边缘淡出多少
字体大小14px10–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,方便复制备份。

Ähnliche Plugins