@dnalec/dsh-auto-approve 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DNAlec
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # dsh-auto-approve
2
+
3
+ English | [中文](README.zh.md)
4
+
5
+ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Cordis plugin: judge-model auto-approval, QQ official Bot human approval, and a settings page — on one `approval/request` waterfall. The settings UI calls the model the **judge model** (审核模型).
6
+
7
+ What needs approval is decided by the Auto-approve preset sandbox (in-workspace writes skip approval, or they also enter the judge pipeline). Keyword and criteria-table actions are configurable; out-of-workspace `danger-full-access` **does not short-circuit on the mode name**. Requests that need a human appear on both the Web UI and a configured QQ chat; **the first answer wins**.
8
+
9
+ Scope is approval only. The QQ bot is a notify-and-reply channel, not an agent chat.
10
+
11
+ ## Install
12
+
13
+ Pin a release tag (recommended). `main` is for published commits; in-progress work lives on other branches.
14
+
15
+ ```sh
16
+ dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.1.0
17
+ ```
18
+
19
+ From npm, after a tagged release:
20
+
21
+ ```sh
22
+ dsh plugin --profile web add @dnalec/dsh-auto-approve
23
+ ```
24
+
25
+ A local checkout is fine while developing:
26
+
27
+ ```sh
28
+ dsh plugin --profile web add /path/to/this-repo
29
+ ```
30
+
31
+ Restart `dsh web`. The first start writes the `auto-approve` permission preset into the profile patch (the preset table cannot be extended at runtime).
32
+
33
+ ## Setup
34
+
35
+ In Settings → **Auto-approve**:
36
+
37
+ 1. Click **Scan to create bot**, then scan with mobile QQ (official Open Platform flow). Or paste AppID / AppSecret and save.
38
+ 2. Friend the bot and send it any message. Pick the chat under **Recent incoming** (private chat recommended). Group chats must @ the bot and set `userId`. Scanning may prefill the scanner’s openid as `chatId` if none is set. This plugin is for a **private bot**: if no `chatId` is bound yet, any C2C reply of `是` / `确认` / `用作审批` / `yes` / `ok` binds that chat as the approval target. Do not add the bot where strangers can DM it.
39
+ 3. Under **Auto-approve mode**, choose in-workspace writes without approval (preset sandbox `workspace-write`, recommended) or send them through the judge (`read-only`). After saving, restart `dsh web` and re-select **Auto-approve** or start a new session.
40
+ 4. Set the session permission preset to **Auto-approve**. If it is missing after the first start, restart once more so the live patch reload can pick it up.
41
+
42
+ Credentials are stored only in `~/.dsh/approval-bridge/qqbot.json` (`0600`). Do not commit them.
43
+
44
+ Uninstall (`dsh plugin --profile web remove @dnalec/dsh-auto-approve`) drops this plugin from the bundle stack after a restart. The `auto-approve` preset may remain in the profile patch: without the plugin it is ordinary `workspace-write` or `read-only` + `ask`. Rules, audit logs, and QQ credentials under `~/.dsh/auto-approve/` and `~/.dsh/approval-bridge/` are not deleted.
45
+
46
+ ## Behavior
47
+
48
+ | Situation | Result |
49
+ |---|---|
50
+ | Preset sandbox `workspace-write`, in-workspace write | No approval (sandbox already allows it) |
51
+ | Keyword or criteria action is reject | Immediate `rejected`; no dialog, no QQ |
52
+ | Keyword or criteria action is allow | No dialog, no QQ; green strip + `ALLOW` audit |
53
+ | Keyword/criteria human, or judge failure | QQ gets `#N`; reply `批准 N` to continue |
54
+ | `danger-full-access` (outside workspace) | Same judge pipeline; does not short-circuit on the mode name |
55
+ | Web UI and QQ both prompt | First outcome wins; QQ answering first dismisses the Web dialog (forked signal, not `req.signal`) |
56
+ | QQ wait timeout (default 120s) | Web dialog stays open; QQ says continue in the browser |
57
+ | No credentials / not connected | Auto-approval still runs; humans only on the Web UI (`PUSH_SKIP`) |
58
+ | Judge model misconfigured / judge fails | Escalate to human (`FAILED`); do not fall back to the session model |
59
+ | Tool args missing (no command/path/content/code, …) | Human (`missing-payload`); never auto-allow |
60
+ | Captured field truncated | Keyword reject still applies; never auto-allow (`truncated-payload`) |
61
+ | Preset is not `auto-approve` | Do not answer, do not push (unless `onlyAutoApprovePreset` is off) |
62
+
63
+ QQ approval pushes include Allow / Reject buttons (custom keyboard in C2C and group chats). Text still works: `批准 17` / `#17 批准` / `yes 17` allow; `拒绝 17` rejects once. A bare `批准` / `拒绝` is allowed only when exactly one ticket is pending.
64
+
65
+ Do not abort `req.signal` to dismiss the Web dialog: that cancels the whole request.
66
+
67
+ ## Pipeline
68
+
69
+ Only when `permissionPresets.current(session) === 'auto-approve'` (unless that restriction is disabled). The preset sandbox is a gate, not a pipeline step: with `workspace-write`, in-workspace writes never reach approval.
70
+
71
+ 1. Keywords (reject > human > allow). Reject/human match **tool name + command + path + workdir**; allow keywords do not match the tool name. They do not match the model justification, `description`, or file bodies. Shipped phrases default to reject: catastrophic command shapes, auto-approve config paths, and credential paths (`.env`, `id_rsa`, …). Anything missed goes to the next step. If a captured field is truncated, keyword reject still applies; keyword allow and judge-safe do not — that request goes to a human.
72
+ 2. Criteria table: the judge model sees the same fields as the Web tool card (command, path, old/new text, write content, code/url/sql, …) and emits only a category id + reason; the program applies allow / reject / human. Parse failure or timeout → human and does **not** run `other.action`. Risk rows default to reject; `safe` defaults to allow; `other` defaults to human.
73
+
74
+ Default criteria (actions are editable; `other` cannot be deleted):
75
+
76
+ | id | Label | Default |
77
+ |---|---|---|
78
+ | deletion | Destructive delete/overwrite | reject |
79
+ | credential | Credentials / secrets / auth | reject |
80
+ | remote | Remote / production / database | reject |
81
+ | system | System paths / config | reject |
82
+ | bulk | Bulk irreversible ops | reject |
83
+ | approval-config | Auto-approve config | reject |
84
+ | safe | Safe / routine reversible | allow |
85
+ | other | Uncertain / none of the above | human |
86
+
87
+ Risk rows default to reject. Auto-allow only when the model can confirm `safe`. Uncertainty goes to `other` (human). Mis-filing a dangerous op as `safe` is still auto-allowed.
88
+
89
+ Settings can set **judge prompt language** to Chinese or English (default Chinese). That switches only the prompt framework and card labels; it does **not** rewrite the current criteria table. Restore default criteria loads the shipped pack for the selected language. The reason line uses the same language as the framework.
90
+
91
+ Host APIs use authenticated `connection.rpc` (`/api/dsh-auto-approve`), not unauthenticated HTTP.
92
+
93
+ ## Data
94
+
95
+ Under `$DSH_HOME` (default `~/.dsh/`). None of this belongs in git.
96
+
97
+ | Path | Purpose |
98
+ |---|---|
99
+ | `auto-approve/allowlist.json` | Keyword buckets / criteria / judge timeout (`0600`) |
100
+ | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `PUSH` / `PUSH_SKIP` / `PUSH_FAIL` / `OUTCOME` (`0600`) |
101
+ | `auto-approve/events.jsonl` | UI events (`ticket`, `sessionId`; trimmed to last 2000 lines after ~2MB, `0600`) |
102
+ | `approval-bridge/qqbot.json` | AppID / AppSecret, `0600` |
103
+ | `approval-bridge/config.json` | Judge model, notify target, and other plugin config (`0600`) |
104
+
105
+ No audit line means this plugin did not handle that request.
106
+
107
+ ## Configuration
108
+
109
+ ```yaml
110
+ - id: dsh-auto-approve
111
+ config:
112
+ onlyAutoApprovePreset: true
113
+ presetSandbox: workspace-write # or read-only: workspace writes also go through the judge
114
+ judge:
115
+ provider: ''
116
+ model: ''
117
+ reasoningEffort: ''
118
+ timeoutMs: 20000
119
+ notify:
120
+ enabled: true
121
+ chatId: ''
122
+ userId: ''
123
+ timeoutSecs: 120
124
+ ```
125
+
126
+ Empty judge fields follow the deployment default model. If there is no default and nothing is configured, or the route is invalid, that request goes to a human. There is no hardcoded fallback model. The settings **Judge timeout** writes `judgeTimeoutMs` in `allowlist.json` (that is what runtime uses); yaml `judge.timeoutMs` is only a default when allowlist does not exist yet.
127
+
128
+ If a JSON file cannot be parsed, this process uses in-memory defaults and **does not overwrite the file**. Settings will not write a corrupt allowlist except Restore defaults; a corrupt plugin config needs an explicit overwrite; corrupt QQ credentials need a re-save or QR scan.
129
+
130
+ ## Development
131
+
132
+ ```sh
133
+ node --test tests/*.test.mjs
134
+ npm run check
135
+ ```
136
+
137
+ Layout: `src/index.mjs` (host), `src/rules.mjs`, `src/tickets.mjs`, `src/qqbot.mjs`, `src/provisioning.mjs` (official QR), `src/preset-patch.mjs`, `src/util.mjs`, `client.js` (Web UI; React via `createElement`, no JSX), `locales.mjs` (zh/en Client copy). The Web UI follows the DSH language setting; QQ push text and audit logs stay Chinese. QR login uses `@tencent-connect/qqbot-connector` (optional) and generates the image locally with `qrcode`.
138
+
139
+ For agents working in this repository, follow [AGENTS.md](AGENTS.md).
140
+
141
+ ## Releasing
142
+
143
+ 1. Set `package.json` `version` (for example `0.1.0`) and merge to `main`.
144
+ 2. On npmjs.com, add a Trusted Publisher for this GitHub repo, workflow file `publish.yml` (once).
145
+ 3. Tag and push: `git tag v0.1.0 && git push origin v0.1.0`.
146
+
147
+ The tag must match `package.json` version. The workflow runs tests, then `npm publish`. The first publish of the package name may need a local `npm login` and `npm publish --access public` once; later tags are enough.
148
+
149
+ ## License
150
+
151
+
152
+ [MIT](LICENSE)
package/README.zh.md ADDED
@@ -0,0 +1,152 @@
1
+ # dsh-auto-approve
2
+
3
+ [English](README.md) | 中文
4
+
5
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的 Cordis 插件:把审核模型自动审批、QQ 官方 Bot 人工审批和设置页做在同一条 `approval/request` 瀑布上。
6
+
7
+ 需要审批的行为(由「自动审批」预设沙箱决定:工作区内不审批,或工作区也走判定)进入同一条判定管道。关键词和审核表的动作都可配置;越出工作区的 `danger-full-access` **不因模式名短路**。必须人工的请求同时出现在网页和配置的 QQ 聊天,**谁先答谁赢**。
8
+
9
+ 范围仅限审批。QQ 机器人是通知和批复通道,不是 agent 聊天。
10
+
11
+ ## 安装
12
+
13
+ 请钉死发行 tag(推荐)。`main` 只放已发布的提交,开发走其他分支。
14
+
15
+ ```sh
16
+ dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.1.0
17
+ ```
18
+
19
+ 打 tag 发布后也可从 npm 安装:
20
+
21
+ ```sh
22
+ dsh plugin --profile web add @dnalec/dsh-auto-approve
23
+ ```
24
+
25
+ 自己开发可以用本地路径:
26
+
27
+ ```sh
28
+ dsh plugin --profile web add /path/to/this-repo
29
+ ```
30
+
31
+ 重启 `dsh web`。首次启动会把 `auto-approve` 权限预设写入 profile patch(预设表冻结,不能运行时扩展)。
32
+
33
+ ## 设置
34
+
35
+ 设置页 → **自动审批**:
36
+
37
+ 1. 点「扫码接入机器人」,用手机 QQ 扫码(腾讯官方开放平台流程);也可以手动填 AppID / AppSecret 后保存连接。
38
+ 2. 加好友后给机器人发任意一句,在「最近来信」点选审批聊天(推荐私聊)。群必须 @机器人,并另配 `userId`。若尚未选定聊天,扫码者的 openid 会预填为 `chatId`。本插件按**私人 bot** 设计:未绑定 `chatId` 时,任意私聊回复「是」「确认」「用作审批」「yes」「ok」会把该聊天设为审批目标。不要把 bot 加到别人也能私聊的环境。
39
+ 3. 在「自动审批模式」里选择:工作区内不审批(预设沙箱 `workspace-write`,推荐),或工作区也走判定(`read-only`)。保存后重启 `dsh web`,并重新选择「自动审批」或开新会话。
40
+ 4. 会话权限选「自动审批」。若首次启动后下拉里还没有,再重启一次让 live patch 生效。
41
+
42
+ 凭据只存在 `~/.dsh/approval-bridge/qqbot.json`(`0600`),不要提交。
43
+
44
+ 卸载(`dsh plugin --profile web remove @dnalec/dsh-auto-approve`)后重启,插件随 bundle 层消失。`auto-approve` 预设可能仍留在 profile patch 里:没有本插件时它只是普通的 `workspace-write` 或 `read-only` + `ask`。`~/.dsh/auto-approve/` 与 `~/.dsh/approval-bridge/` 里的规则、审计和 QQ 凭据不会删除。
45
+
46
+ ## 行为
47
+
48
+ | 情况 | 结果 |
49
+ |---|---|
50
+ | 预设沙箱 `workspace-write`,工作区内写入 | 不进审批(沙箱已允许) |
51
+ | 关键词命中拒绝 / 审核表动作为拒绝 | 直接 `rejected`,无框、无 QQ |
52
+ | 关键词命中允许 / 审核表动作为允许 | 无框、无 QQ;绿条 + `ALLOW` 审计 |
53
+ | 关键词命中人工、审核表人工、判定失败 | QQ 收到 `#N`;回复 `批准 N` 后工具继续 |
54
+ | `danger-full-access`(越出工作区) | 进入同一条判定管道,不因模式名短路 |
55
+ | 网页与 QQ 同时亮 | 先到的 outcome 生效;QQ 先答会关掉网页框(只 abort 网页用的 fork signal,不碰 `req.signal`) |
56
+ | QQ 等待超时(默认 120s) | 网页框仍在;QQ 提示到网页继续 |
57
+ | 未配凭据 / 未连接 | 自动审批照常;人工只走网页(`PUSH_SKIP`) |
58
+ | 审核模型配错 / 判定失败 | 当次转人工(`FAILED`);不改用会话模型 |
59
+ | 未捕获工具参数(无命令/路径/内容/code 等) | 转人工(`missing-payload`),禁止自动放行 |
60
+ | 捕获字段被截断 | 仍可关键词拒绝;禁止自动放行(`truncated-payload`) |
61
+ | 预设不是 `auto-approve` | 不抢答、不推送(除非关闭 `onlyAutoApprovePreset`) |
62
+
63
+ QQ 审批推送带「批准 / 拒绝」按钮(单聊、群聊自定义按钮)。也可回复:`批准 17` / `#17 批准` / `yes 17` 放行;`拒绝 17` 只拒这一次。仅 1 条 pending 时允许裸 `批准` / `拒绝`。
64
+
65
+ 不要 abort `req.signal` 来关网页框:那会把整单变成 `cancelled`。
66
+
67
+ ## 判定管道
68
+
69
+ 仅当 `permissionPresets.current(session) === 'auto-approve'` 时介入(除非关闭「仅该预设」)。预设沙箱是底线、不是管道步骤:`workspace-write` 时工作区内写入根本进不了审批。
70
+
71
+ 1. 关键词(拒绝 > 人工 > 允许)。拒绝/人工匹配 **工具名 + command + 路径 + workdir**;允许词不匹配工具名。不匹配模型理由、`description`、文件正文。预置词默认拒绝,含灾难命令形态(`rm -rf`、`git push -f`、`drop table`、`dd of=` 等)、审批配置路径,以及 `.env` / `id_rsa` 等凭据路径。漏掉的交给下一步。捕获字段被截断时仍可关键词拒绝,禁止关键词允许或模型标 safe,转人工。
72
+ 2. 审核表:审核模型看到与网页工具卡片相同的字段(含命令、路径、原文/改成、写入内容、code/url/sql 等),只输出类别 id + 理由;程序按表执行允许 / 拒绝 / 人工。解析失败或超时转人工,**不**执行「其他」的动作。风险类默认拒绝;「安全」默认允许;「其他」默认人工。
73
+
74
+ 默认审核表(动作均可改;「其他」不能删除):
75
+
76
+ | id | 标签 | 默认动作 |
77
+ |---|---|---|
78
+ | deletion | 删除/覆盖不可再生数据 | 拒绝 |
79
+ | credential | 凭据/密钥/授权修改 | 拒绝 |
80
+ | remote | 远程系统/生产环境/数据库 | 拒绝 |
81
+ | system | 系统级路径/配置 | 拒绝 |
82
+ | bulk | 批量不可回补操作 | 拒绝 |
83
+ | approval-config | 自动审批配置 | 拒绝 |
84
+ | safe | 安全/常规可回补 | 允许 |
85
+ | other | 其他(拿不准) | 人工 |
86
+
87
+ 风险类默认拒绝。只有模型能确认安全才走 `safe` 自动放行;拿不准走「其他」转人。模型若把危险操作归进 `safe`,会被自动放行。
88
+
89
+ 设置里可把**审核提示词语言**设为中文或 English(默认中文)。这只换发给模型的框架和卡片标签,**不会**改当前审核表。点「恢复默认审核表」才按所选语言加载出厂包。理由与框架同语言。
90
+
91
+ Host API 走已鉴权的 `connection.rpc`(`/api/dsh-auto-approve`),不上无鉴权 HTTP。
92
+
93
+ ## 数据
94
+
95
+ 位于 `$DSH_HOME`(默认 `~/.dsh/`),均不进 git。
96
+
97
+ | 路径 | 用途 |
98
+ |---|---|
99
+ | `auto-approve/allowlist.json` | 关键词三桶 / 审核表 / 审核超时(`0600`) |
100
+ | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `PUSH` / `PUSH_SKIP` / `PUSH_FAIL` / `OUTCOME`(`0600`) |
101
+ | `auto-approve/events.jsonl` | UI 事件(含 `ticket`、`sessionId`;超约 2MB 只留最后 2000 条,`0600`) |
102
+ | `approval-bridge/qqbot.json` | AppID / AppSecret,`0600` |
103
+ | `approval-bridge/config.json` | 审核模型、推送目标等插件配置(`0600`) |
104
+
105
+ 没有 audit 行 = 本插件没有处理该请求。
106
+
107
+ ## 配置
108
+
109
+ ```yaml
110
+ - id: dsh-auto-approve
111
+ config:
112
+ onlyAutoApprovePreset: true
113
+ presetSandbox: workspace-write # 或 read-only:工作区写入也走判定
114
+ judge:
115
+ provider: ''
116
+ model: ''
117
+ reasoningEffort: ''
118
+ timeoutMs: 20000
119
+ notify:
120
+ enabled: true
121
+ chatId: ''
122
+ userId: ''
123
+ timeoutSecs: 120
124
+ ```
125
+
126
+ 审核模型字段为空则跟随部署默认模型。没有默认可跟随、也没手动配置,或路由配错,则当次转人工。不会再猜一个模型。设置页「审核超时」写入 `allowlist.json` 的 `judgeTimeoutMs`(运行时以此为准);yaml 里的 `judge.timeoutMs` 只在尚未生成 allowlist 时作缺省。
127
+
128
+ JSON 读失败(损坏)时本进程用内存默认,**不覆盖磁盘**。设置页除「恢复默认 / 覆盖损坏配置」外不会写坏 allowlist;插件配置损坏需明确覆盖;QQ 凭据损坏需重新保存或扫码。
129
+
130
+ ## 开发
131
+
132
+ ```sh
133
+ node --test tests/*.test.mjs
134
+ npm run check
135
+ ```
136
+
137
+ 结构:`src/index.mjs`(宿主)、`src/rules.mjs`、`src/tickets.mjs`、`src/qqbot.mjs`、`src/provisioning.mjs`(官方扫码)、`src/preset-patch.mjs`、`src/util.mjs`、`client.js`(网页半;React 用 `createElement`,无 JSX)、`locales.mjs`(网页 zh/en 字典)。网页文案跟随 DSH 语言设置;QQ 推送和审计日志仍为中文。扫码依赖可选包 `@tencent-connect/qqbot-connector`,二维码由本机 `qrcode` 生成。
138
+
139
+ 在本仓库改代码的 agent 请遵循 [AGENTS.md](AGENTS.md)。
140
+
141
+ ## 发布
142
+
143
+ 1. 把 `package.json` 的 `version` 改成目标版本(例如 `0.1.0`),合进 `main`。
144
+ 2. 在 npmjs.com 给本仓库配置 Trusted Publisher,工作流文件填 `publish.yml`(只需一次)。
145
+ 3. 打 tag 并推送:`git tag v0.1.0 && git push origin v0.1.0`。
146
+
147
+ tag 必须与 `package.json` 版本一致。工作流会先跑测试再 `npm publish`。这个包名第一次上架可能需要本机 `npm login` 后执行一次 `npm publish --access public`;之后推 tag 即可。
148
+
149
+ ## 许可证
150
+
151
+
152
+ [MIT](LICENSE)