Repository navigation
Releases
v0.9.4
一句话
两个新入口都开在 DSH 界面里:侧边栏底部的「插件体检」浮层面板,和会话头部的「审批历史」浮层面板;完整历史按需跳默认浏览器。
v0.8.0:插件体检面板
侧边栏底部(设置上方)新增 🩺「插件体检」按钮,点开是浮层报告,内容直接读宿主的 /dsh-approval/diag?raw=1:
- 结论胶囊:全部正常 / N 项需要注意 / N 项问题,一眼可见
- 检查项:每条带状态、说明与「该做什么」
- 计数器:
approvalSeen/generated/failed/timeouts等,判断插件是否真的执行过 - 浏览器半区:是否进了启动图、当前 rev、页面侧上报过的错误与阶段路标
- 崩溃与自愈:崩溃报告份数与最新文件名、
cordis.patch.yml.bak-*遗迹 - profile 配置:补丁块数量、模型选择是否在位、已启用 bundle
- 相关页面 / 日志 / 原始 JSON:可点、可折叠、可复制
打开时每 10 秒自动刷新;↻ 重查;↗ 在应用内打开完整页;Esc 或点外面关闭。判定只在宿主算一次,面板只负责展示;读不到宿主时给出原因与重试,不白屏。
v0.9.0–v0.9.4:审批历史面板
会话头部——也就是「对话 / 轨迹」那一带——新增「审批历史」按钮。点开是在对话上方浮出的面板(和体检面板同一套交互):
- 头部:记录条数、重新读取、关闭
- 筛选:关键字(工具 / 路径 / 理由 / 注释正文)、决策(允许一次 / 已拒绝 / 已取消 / 待定)、风险(低 / 中 / 高)——本地即时过滤
- 列表:每条一行摘要(时间 · 工具 · 风险徽章 · 决策徽章 · 会话),展开看注释全文、风险点、实参摘要
- 完整页面:跳默认浏览器(清空历史等高级操作在那里)
这个入口的形态改了三轮,如实说明:
| 版本 | 形态 | 结果 |
|---|---|---|
| 0.9.0 | 相对路径开新窗口 | 点不动:桌面壳页面跑在 dsh-app://app/,相对路径被解析成 dsh-app://app/...,主进程只放行 http(s),其余静默 deny |
| 0.9.1 | 拼宿主绝对 URL 再开新窗口 | 能开,但弹到系统浏览器 |
| 0.9.2 | 应用内内置浏览器标签 / 同窗口兜底 | 还在 DSH 内,但不是浮层 |
| 0.9.3 | 对话上方浮层面板 | 日常查看在面板里完成 |
| 0.9.4 | 浮层面板 + 完整页面外跳 | 「完整页面」按需求跳默认浏览器 |
经验:桌面壳里相对路径开新窗口一定失败(且没有返回值可判断);面板数据走相对 fetch(由桌面壳代理到宿主)则完全没问题。
升级方式
本项目从 git 源安装,升级 = 先移除再安装:
dsh plugin --profile desktop remove dsh-approval-explainer
dsh plugin --profile desktop add github:ArisuYuuko/dsh-approval-explainer
dsh --profile desktop --dump-config # 确认多了一个配置层生效规则:浏览器半区(lib/client.js)刷新页面即可;宿主半区(lib/index.js)必须重启 DSH,/dsh-approval/diag 里的版本号也要重启后才会变。
兼容性
engines.dsh:>=0.2.0-rc.1 <0.3.0-0- 桌面壳(
dsh-app://app/)与浏览器(dsh web)两种模式都支持 - 与同座位插件共存:审批详情是 single 座位(本插件用
priority: -1遮蔽官方渲染器);会话头部动作行与侧边栏底部是 list 座位(各用各的id,不顶掉别人) - 所有数据只落本机 profile
验证
- 31 个套件 / 887 项断言 / 0 失败(含真渲染面板、座位契约、畸形输入、静态防外跳断言)
safe-edit六项门禁全过;prepublish-final58 项;npm pack出的 tarball 通过verify-tarball- 版本四处一致:
package.json/PLUGIN_VERSION/ 发布副本package.json/ 发布副本lib/*.js
已知限制
- 本插件不拥有授权决定:只旁听
approval/request,不改变谁能被授权 - 面板文案是中文;注释语言跟随素材语言
- 历史面板一次最多显示 200 条;更多、清空等操作走「完整页面」
- 面板显示的是运行中宿主半区的版本,升版后需重启 DSH 才会变
- 体检面板里的跳转仍在应用内(内置浏览器标签);只有历史面板的「完整页面」按需求走默认浏览器
English summary
Two in-app entry points, no routing needed.
- v0.8.0 — Plugin health check: a 🩺 button at the sidebar foot opens a floating report built from
/dsh-approval/diag?raw=1(verdict, checks, counters, browser-half status, crash/self-heal evidence, profile state, related pages, raw JSON). Auto-refreshes every 10 s while open. - v0.9.0–0.9.4 — Approval history: a button in the session header opens a floating panel above the conversation (record count, local keyword/decision/risk filters, one expandable row per approval). Its “full page” button opens the host page in the default browser for advanced actions such as clearing the history.
The history entry took four revisions because the desktop shell serves the UI under dsh-app://app/ and its main process only hands http(s) targets to the system browser: relative new-window URLs are denied silently. The final shape is a native floating panel (data fetched relatively through the shell’s proxy) plus a default-browser jump for the full page.
Upgrade: dsh plugin --profile desktop remove dsh-approval-explainer then ... add github:ArisuYuuko/dsh-approval-explainer; reload the page for the browser half, restart DSH for the host half.
Verified: 31 suites / 887 assertions, safe-edit gate, prepublish checks, packed tarball.
v0.7.1
修注释解析:JSON 后跟多余内容导致 "Unexpected non-whitespace character"
用户报的错
「这句注释没能生成:Unexpected non-whitespace character after JSON at position 700
(line 1 column 701)」
根因
extractJsonObject 取的是「第一个 { 到最后一个 }」:
const start = raw.indexOf('{')
const end = raw.lastIndexOf('}') // ← 最后一个
return JSON.parse(raw.slice(start, end + 1))
模型只要在注释对象之后又输出了任何带 } 的内容(第二个 JSON 对象、
或一段带大括号的说明),切出来就是两段拼在一起 → JSON.parse 报上面那个错。
position 700 正好是第一个对象结束处。
修法
括号配平扫描 + 逐个起点重试:
- 从某个
{开始配平到 0,得到一个候选片段 - 试着 JSON.parse;成功即返回
- 失败就换下一个
{起点
两个细节都必须有:
- 字符串感知:字符串里的
{}不计数,且要处理\"转义
(否则"这段是 if (x) { y } 结构"会提前结束) - 逐个起点重试:正文里可能先有个裸
{
("我来分析(这里有个 { 只是文字)"),从它开始永远配不平
我第一版漏了第 2 点,测试当场抓到(⑥ 前置废话那条)。
顺带
错误信息现在带原文开头 200 字符,以前只说"解析失败",事后没法回查模型到底输出了什么。
新增 __testing 导出
把 extractJsonObject 导出来供测试用。
之前测试是从源码里"截函数",但函数前的 JSDoc 注释里有未配对的引号
(\\"),朴素的括号配平会被带偏 —— 测试自己踩了两次。
import 真实模块比截文本可靠得多,而且测的就是实际运行的代码。
新增 verify-json-extract.mjs(25 项)
① JSON 后跟第二个对象(用户那个场景)
② JSON 后跟带大括号的说明
③ 字符串里含 } 不提前结束
④ 字符串里含转义引号
⑤ markdown 围栏
⑥ 前置废话里有裸大括号
⑦ 嵌套对象与数组
⑧ 不完整 JSON 报错且信息可回查
⑨ 真实注释形状(含 Windows 路径反斜杠)
版本
0.7.0 → 0.7.1(bugfix)。
验收
30 个套件 / 730 项断言 / 0 失败。
v0.7.0
v0.7.0:历史授权查询
新增:一键查询历史授权
http://127.0.0.1:<dsh-web-port>/dsh-approval/history
每一次真实提权都会被记下来:模型要做什么、当时的 AI 注释说了什么、
你当时选了允许还是拒绝,可搜索、可筛选、可追溯,重启后仍在。
| 查询参数 | 含义 |
|---|---|
| (无) | HTML 页面 |
?raw=1 |
JSON |
?q= |
关键字:工具 / 路径 / 注释 / 申请理由 |
?decision=allow |
allow / deny / cancel / pending |
?risk=high |
low / medium / high |
?session= |
只查某个会话 |
?since= / ?until= |
ISO 时间范围 |
?limit= |
默认 100,上限 1000 |
?refresh=1 |
绕过 revision 缓存 |
POST /dsh-approval/history/clear 清空注释文件。
设计:只存注释,决策读宿主
插件只存自己的注释(annotations.jsonl)。
决策——允许 / 拒绝 / 取消——从 DSH 自己的会话日志读,那里本来就有成对的
approval/asked + approval/decided 事件。两者按 callId 关联。
为什么不自己存决策:宿主才是"谁批准了什么"的权威。插件再存一份,
只会制造第二个真相,而它迟早与宿主漂移。
绝不写/删宿主的会话日志。 未知事件类型会让旧版 harness fail-closed
拒读整份日志,那是不可恢复的破坏。
关联的三级兜底
正常审批 100% 有 callId(实测 73/73)。契约上它是可选字段,所以有兜底:
| 级 | 键 | 窗口 |
|---|---|---|
| ① | sessionId + callId |
— |
| ② | sessionId + toolName + reason |
非对称 [锚点-10min, 锚点+60s] |
| ③ | 同会话同 toolName,选离锚点最近的 | 同上 |
锚点是 at - tookMs(生成开始时刻 = 审批被请求的时刻)。
窗口为什么非对称:注释是在 approval/request 触发的瞬间开始生成的,
所以一次审批不可能显著晚于那个时刻——未来的审批在那一刻根本不存在。
用对称窗口会把「别人的、稍后发生的审批」匹配进来,导致张冠李戴。
歧义宁可 unlinked:同一 reason 命中多条、或两条等距时,不猜。
决策列显示「未关联」而不是显示一个错的——错的比缺的更糟。
健壮性
- 宿主读取有超时:每会话 4 秒 + 总预算 20 秒。宿主挂起时页面仍能返回,
带着已读到的部分,而不是白屏挂死。 - 读取失败可见:
list()失败 → 页面显示降级横幅;逐会话失败 → 提示
「有 N 个会话读取失败,决策列可能缺失」。 - revision 门控 + 缓存 + 并发合并:读全部会话实测 601ms(18 个),
二次读走缓存;100 会话量级靠总预算兜底。 - 非法参数返回 400,不 500。
- XSS:模型输出全部转义,22 项载荷验证;另加
nosniff/ CSP /no-referrer。 - 清空只删插件自己的文件,GET 返回 405。
新增:监听器失效自检
历史页会比对「宿主里本进程启动后的审批数」与「插件实际收到的次数」。
宿主更多就显示严重警告:
审批监听器可能已失效 —— 宿主里本进程启动后有 3 次审批,但插件只收到 0 次。
处理办法:重启 DSH。
这条是为了解决一类没有任何信号的故障:插件监听器失效时不会崩、不会报错、
/diag 也会说"工作正常"(它拿的是旧计数)。用户只会觉得"这插件没用了"。
为什么历史页不用前端框架
服务端渲染、零客户端依赖。这是诊断类页面:它的存在意义是
"出问题时还能打开看看",所以它必须比它诊断的东西更结实——
不依赖浏览器半区、不发 fetch、不用 React。
升级注意
- 宿主半区有改动,必须重启 DSH。 只刷新页面不够。
- 运行时端口可能变(由 DSH 选择),文档里用
<dsh-web-port>指代。 engines.dsh: ">=0.2.0-rc.1 <0.3.0-0"- 若宿主不提供
sessionPersistence(旧版本),历史页仍能打开并说明原因,
注释可看,只是决策列不可用。
v0.6.2
v0.6.2
为 DSH 从 0.2.0-rc.2 升级到 0.21 线做准备。
修复
- 巡检日志无限增长:
installRoutes每 30 秒巡检一次 webServer,并且每次都写日志;
加上server !== current恒为真(ctx.get('webServer')每次返回的包装对象身份不同),
每 30 秒写两行。实测一周累积 9155 行、activity.log涨到 1.5 MB(约 1.5 MB/天)。
现在只在状态真的变化时写日志,稳态下完全安静。 - 顺带给
activity.log加体积上限(1 MB,超出保留最后 2000 行),作为兜底。
新增
engines.dsh: ">=0.2.0-rc.1 <0.3.0-0"—— 市场卡片会展示宿主范围,
版本不匹配能在安装前被标出来。范围写法考虑了 node-semver 的预发布版语义,
所以写的是>=0.2.0-rc.1而不是笼统的>=0.1.0。
测试
- 把三处硬编码
0.6.1的断言改成交叉核对:
package.json↔index.js的PLUGIN_VERSION↔ 运行进程上报的 version。
版本上涨不必改测试,而"三处不一致"这种真问题仍会被抓到。
22 个套件 448 项断言 0 失败。
安装
dsh plugin --profile desktop add github:ArisuYuuko/dsh-approval-explainerv0.6.1
首次公开发布。
DSH 审批弹窗 AI 注释器:在模型请求权限外操作时,弹窗里除了那行命令,
再加一份由模型自己推理生成的注释 —— 这段代码在做什么、模型想达成什么、
风险多大以及为什么、两个选项各自意味着什么。
安装:
dsh plugin --profile desktop add github:ArisuYuuko/dsh-approval-explainer本附件是预构建 tarball,安装时不需要任何构建授权。