@dnalec/dsh-auto-approve 0.1.3 → 0.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,85 @@
1
+ # Changelog
2
+
3
+ ## 0.2.1
4
+
5
+ > 如果你用 0.2.0 装过、且 profile patch 还是出厂模板(注释 + `[]`),请升级:0.2.0 会把预设块追加在 `[]` 之后,写出 DSH 解析不了的 YAML,下次 `dsh web` 起不来。
6
+ > 0.2.1 还修掉了沙箱模式静默失效、非 web 组合不挂载、判定可被卡片回显诱导放行等问题。
7
+
8
+ ### Fixes(本仓库 review 后的修复)
9
+
10
+ - **全新安装不再写坏 profile patch**:空 patch 的判定改为「剥掉注释后为空数组」,`注释 + []` 这种 DSH 出厂模板会被整段替换,不再产出 `[]` 后面跟条目的非法 YAML(那会让下次 `dsh web` 启动直接解析失败)。同时修掉「注释里提过 `auto-approve:` 就以为已配置」的子串假阳性。
11
+ - **沙箱模式不再静默失效**:`danger-full-access` 之类手改值会被改写;块里没有 `sandbox:` 行时返回 `err.presetSandboxMissing`,不再假装成功(以前 UI 说只读、实际全权限且不提示重启)。
12
+ - **profile 路径不再硬编码 `profiles/web`**:从 `ctx.baseUrl`(app-boot 锚在 profile 目录)推导,可用插件配置 `profilePatch` 覆盖;写入失败会打印具体文件与原因。
13
+ - **`inject` 去掉 `webServer`**:它是 fiber 的必需服务而 `webserver` 行只在 web-app bundle 里,之前 headless / acp / sdk 组合下整个审批门控都不会挂载。
14
+ - **判官卡片加围栏**(`<<<TOOL_CARD` … `TOOL_CARD>>>`):出厂提示词明确声明围栏内是不可信数据;类别解析改为取**最后一个**「类别: id」行,卡片回显不能覆盖结论。
15
+ - **取消即停**:判定过程观察 `req.signal`,请求取消后不再跑完模型调用、也不再重试(返回 `cancelled`,不弹人工框)。
16
+ - **凭据关键词边界**:`.env` / `.netrc` 增加「路径干草」放宽匹配(`prod.env`、`x.env` 命中;命令里的 `process.env` 仍不误伤);`id_rsa.pub` / `id_ed25519` 后跟 `.pub` 不再当凭据。
17
+ - **迁移只增不删**:不再从用户文件里删掉 `shutdown` / `reboot` 等旧预置词(分不清出厂继承与用户手写);v10/v11 的出厂文案刷新改为逐字段比对出厂中/英原文,只刷新仍是原文的字段,不覆盖用户自定义 label/description。
18
+ - **缓存参数只按会话键取**:有 sessionId 时不再回落裸 `callId`,避免跨会话串味;裸键仍会在命中时清理。
19
+ - **推理档位留输出预算**:带 `reasoningEffort` 时 `maxTokens` 从 256 提到 1024,避免推理 token 吃光预算导致全量转人工。
20
+ - **访问模式徽标可撤销**:切走「自动审批」后盾牌+A 会被摘掉;扫描忽略文本节点,不再对每次文本变化做整篇 `querySelectorAll`。
21
+ - 新增:`CI`(push/PR 跑 test + check)、`npm run locales:sync`(client.js 内联字典由 `locales.mjs` 生成,`npm run check` 校验同步)。
22
+
23
+ ### 复审补丁(修复本身的问题)
24
+
25
+ - 空 patch 判定再收紧:`[] # empty`(行尾注释)、`---` / `...` 文档标记也算空;`description: |` 之类**块标量**里的同名行不再被当成「已配置」。
26
+ - 预设插入位置改为按 `presets:` 的相对缩进计算,不再写死 4/6/8 空格:`- insert:` 形式(缩进的 `- id: permission`)、CRLF、`presets:` 下还没有子键、行尾带注释的 permission 行都能正确落位,不会追加出第二个 permission 行。
27
+ - `profilePatchFromBaseUrl` 用 URL 的 pathname 判断目录(`file:///x/?a=1` 不再少切一段),根目录 / 非法 URL 一律回落。
28
+ - `save-plugin` 改成事务式:沙箱写不进 patch 就回滚 `config.json`,避免「配置说只读、patch 是全权限」。
29
+ - 判官超时/重试的取消链接加了防御(`addEventListener` 不存在时不炸)、并补了竞态窗口;新增用例覆盖取消、卡片回显注入、决策事件、关键词改名、跨会话事件过滤。
30
+
31
+ ### 复审第二轮(实机截图反馈 + 预设表冻结)
32
+
33
+ - **访问模式下拉里的「自动审批」现在也有盾牌+A**:React 先插入空按钮、再把标签塞进去,
34
+ 旧扫描只看 `addedNodes`,那条记录的 target 是标签自己,按钮永远不会被重新评估。
35
+ 现在对 childList 记录额外沿 target 向上找最近的触发器/menu 项重扫(不是扫整棵子树,
36
+ 流式输出时不会全量查询)。用 jsdom 复现并验证:静态渲染、分步提交、切走撤销、流式 20 次追加只触发 3 次子树查询。
37
+ - **下拉项徽标颜色对齐 DSH**:菜单行的注入图标改用 `var(--dsw-alias-label-tertiary)`(DSH `.itemIcon` 用的同一个 token),
38
+ 否则会继承菜单按钮的 `label-primary`,比旁边三个图标明显更黑;触发器保持 `color:inherit`(DSH `.trigger` 就是 `label-secondary`,与 `.triggerIcon` 一致)。
39
+ - **预设表冻结新增检测**:插件写进 profile patch 的 `permission` 行会整块替换 base 的 config
40
+ (patch 语义不做深合并),DSH 新增出厂预设不会自动出现。启动时读 `@deepseek-ai/dsh-base`
41
+ 的 `cordis.patch.yml` 比对,缺哪些预设就打印日志并在设置页显示一张卡片(只提示,不自动改写用户文件)。
42
+
43
+ ### 发布前复审(0.2.1 定稿)
44
+
45
+ - **文档结束标记不再写坏 patch**:列 0 的 `...`(以及 `---`)在写入前一律去掉。以前在 `...` 后面追加条目会产出**多文档** YAML,DSH 的 parsePatchList 直接抛错。
46
+ - **没有 `presets:` 的 permission 行不再变成死路**:行里有块状 `config:` 时把出厂表插进去(保留用户已有的 `defaultPreset` 等键);连 `config` 都没有时追加整块;只有行内 flow config 才明确报 `err.noPresetsKey`(文本插入不安全,不猜)。
47
+ - **判定不再能被卡片回显诱导放行**:解析前先剥掉 `<<<TOOL_CARD … TOOL_CARD>>>` 围栏(未闭合的开围栏之后一律丢弃)。以前模型整段复述卡片时,卡片里的 `类别: safe` 会成为「最后一个匹配」。
48
+ - 「是否已配置」只看 permission 行自己的 `presets` 块:别的插件 presets 里的同名键不再被误认。
49
+
50
+ ### Added
51
+
52
+ - Keyword layer: `.pem` matches as a file extension (`certs/server.pem`).
53
+ - Approval history shows empty write/edit bodies as `(empty)` / `(空)`, plus extra tool-card fields (input/text/body/message/pattern/selector/workdir).
54
+ - Settings: criteria label/description remount independently; add-forms clear only after RPC success; sandbox mode is not optimistic; judge catalog/info ignore stale responses.
55
+ - 关键词:`.pem` 按扩展名匹配(`certs/server.pem`)。
56
+ - 审批历史展示空写入/替换为 `(空)` / `(empty)`,并补齐工具卡片其它字段。
57
+ - 设置页:审核表标签/说明各自换 key;添加表单失败不清空;模式点选失败不改本地状态。
58
+ - Settings: mode applies on click (no extra Save). Judge model, prompt, and timeout share one Save. Status flashes at the top.
59
+ - 设置页:模式点选即写入;审核模型 / 提示词 / 超时共用一个保存。反馈条置顶。
60
+ - Settings: editable judge prompt per language, with restore-default. Empty `judgePrompts.zh` / `en` uses the shipped template (`{{criteria}}` inserts the current table).
61
+ - Shipped judge prompt is table-agnostic: generic classify rules only; no default criterion ids. Table-specific exceptions live in row descriptions.
62
+ - 设置页可按语言改审核提示词并恢复默认;空则用该语言出厂模板。
63
+ - 出厂提示词与审核表解耦,不再点名默认类别 id。
64
+ - Access chip: plugin client paints a shield+A on Auto-approve (no DSH patch).
65
+ - 访问模式芯片:插件客户端给「自动审批」补盾牌+A,不改 DSH。
66
+
67
+ ## 0.2.0
68
+
69
+ ### Breaking
70
+
71
+ - Human review is **Web-only**. QQ / WeChat / Feishu / Telegram channels are removed.
72
+ - Plugin config lives in `~/.dsh/auto-approve/config.json`. A readable 0.1.x `~/.dsh/approval-bridge/config.json` is migrated once (judge / preset / language only). QQ credentials are unused.
73
+
74
+ ### Pipeline
75
+
76
+ - Keyword reject/human hay includes `session.header.cwd` (not `session.cwd`) and joins relative `file_path`/`path` onto cwd/workdir. Allow keywords still do not match the tool name or the session directory.
77
+ - Empty write/edit bodies are kept and shown on the judge card as `(empty)` / `(空)`. Missing payload still goes to a human.
78
+ - Connection RPC failures always include `{ code, message, details }` (`rpcFail`), matching the host `ConnectionRpcFailure` contract.
79
+ - Settings criteria label/description remount when the snapshot changes, so restore-defaults is not undone on blur.
80
+
81
+ ### 中文
82
+
83
+ - **破坏性**:人工只走网页;去掉 QQ / 微信 / 飞书 / Telegram。插件配置改到 `~/.dsh/auto-approve/config.json`(可读的 0.1.x `approval-bridge/config.json` 会迁一次判定字段)。
84
+ - 关键词拒绝/人工干草含 `session.header.cwd`,相对路径会拼到 cwd/workdir;允许桶不含工具名和会话目录名。
85
+ - 空写入/替换仍进审核卡片(`(空)` / `(empty)`)。RPC 失败带 `message`。审核表恢复默认后输入框会随 snapshot 重挂。
package/README.md CHANGED
@@ -2,153 +2,103 @@
2
2
 
3
3
  English | [中文](README.zh.md)
4
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.
5
+ A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that auto-approves or rejects tool calls with **keywords** and a **judge-model criteria table**. Uncertainty goes to the original Web approval dialog.
10
6
 
11
7
  ## Install
12
8
 
13
- Pin a release tag (recommended). `main` is for published commits; in-progress work lives on other branches.
14
-
15
9
  ```sh
16
- dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.1.3
10
+ dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.2.1
17
11
  ```
18
12
 
19
- `v0.1.1` and `v0.1.2` fail to load the Web client (`loaded without registering`). Use **v0.1.3** or later.
20
-
21
- From npm, after a tagged release:
13
+ Or from npm, after a tagged release:
22
14
 
23
15
  ```sh
24
16
  dsh plugin --profile web add @dnalec/dsh-auto-approve
25
17
  ```
26
18
 
27
- A local checkout is fine while developing:
28
-
29
- ```sh
30
- dsh plugin --profile web add /path/to/this-repo
31
- ```
32
-
33
- Restart `dsh web`. The first start writes the `auto-approve` permission preset into the profile patch (the preset table cannot be extended at runtime).
19
+ Restart `dsh web`. The first start adds the **Auto-approve** permission preset to the current profile's `cordis.patch.yml`; if that write fails, the log line names the file and the reason.
34
20
 
35
21
  ## Setup
36
22
 
37
- In Settings → **Auto-approve**:
38
-
39
- 1. Click **Scan to create bot**, then scan with mobile QQ (official Open Platform flow). Or paste AppID / AppSecret and save.
40
- 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.
41
- 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.
42
- 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.
43
-
44
- Credentials are stored only in `~/.dsh/approval-bridge/qqbot.json` (`0600`). Do not commit them.
45
-
46
- 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.
23
+ Settings → **Auto-approve**:
47
24
 
48
- ## Behavior
25
+ 1. **Mode** — in-workspace writes skip approval (`workspace-write`, recommended), or send them through the judge (`read-only`). Clicking a mode writes it; restart `dsh web`, then re-select **Auto-approve** or start a new session.
26
+ 2. **Keywords** and **criteria table** as needed. Restore default criteria loads the shipped pack for the selected judge-prompt language.
27
+ 3. **Judge model** — empty follows the deployment default. Saved together with the prompt and timeout.
28
+ 4. **Judge prompt** — language-specific built-in default, editable; restore default writes that language’s shipped template. Use `{{criteria}}` to insert the current criteria table.
29
+ 5. Set the session permission to **Auto-approve**.
49
30
 
50
- | Situation | Result |
51
- |---|---|
52
- | Preset sandbox `workspace-write`, in-workspace write | No approval (sandbox already allows it) |
53
- | Keyword or criteria action is reject | Immediate `rejected`; no dialog, no QQ |
54
- | Keyword or criteria action is allow | No dialog, no QQ; green strip + `ALLOW` audit |
55
- | Keyword/criteria human, or judge failure | QQ gets `#N`; reply `批准 N` to continue |
56
- | `danger-full-access` (outside workspace) | Same judge pipeline; does not short-circuit on the mode name |
57
- | Web UI and QQ both prompt | First outcome wins; QQ answering first dismisses the Web dialog (forked signal, not `req.signal`) |
58
- | QQ wait timeout (default 120s) | Web dialog stays open; QQ says continue in the browser |
59
- | No credentials / not connected | Auto-approval still runs; humans only on the Web UI (`PUSH_SKIP`) |
60
- | Judge model misconfigured / judge fails | Escalate to human (`FAILED`); do not fall back to the session model |
61
- | Tool args missing (no command/path/content/code, …) | Human (`missing-payload`); never auto-allow |
62
- | Captured field truncated | Keyword reject still applies; never auto-allow (`truncated-payload`) |
63
- | Preset is not `auto-approve` | Do not answer, do not push (unless `onlyAutoApprovePreset` is off) |
64
-
65
- 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.
66
-
67
- Do not abort `req.signal` to dismiss the Web dialog: that cancels the whole request.
68
31
 
69
- ## Pipeline
32
+ ## How it works
70
33
 
71
- 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.
34
+ Runs only when the session preset is Auto-approve. With `workspace-write`, in-workspace writes never reach approval. `danger-full-access` uses the same pipeline. Missing or truncated tool args go to a human and are never auto-allowed.
72
35
 
73
- 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.
74
- 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.
36
+ 1. **Keywords** (reject > human > allow). Match tool name, command, path, and working directory. Allow words do not match the tool name or the session directory. Shipped reject words cover disaster commands (`rm -rf`, `git push -f`, `drop table`, `dd of=`), approval-config paths, and credential paths such as `.env` / `id_rsa`. Dotfiles are matched loosely against path fields, so `prod.env` counts while `process.env` in a command does not; `id_rsa.pub` is not treated as a credential.
37
+ 2. **Criteria table**. The judge model sees the same fields as the Web tool card, fenced as untrusted data; it outputs a category and the plugin runs that row’s action. Parse failure or timeout goes to a human. The category is read from the **last** `Category:` line, so a tool card echoed back into the answer cannot override the verdict.
75
38
 
76
- Default criteria (actions are editable; `other` cannot be deleted):
39
+ Default criteria (actions are editable; **Other** cannot be deleted):
77
40
 
78
41
  | id | Label | Default |
79
42
  |---|---|---|
80
- | deletion | Destructive delete/overwrite | reject |
81
- | credential | Credentials / secrets / auth | reject |
82
- | remote | Remote / production / database | reject |
83
- | system | System paths / config | reject |
84
- | bulk | Bulk irreversible ops | reject |
85
- | approval-config | Auto-approve config | reject |
86
- | safe | Safe / routine reversible | allow |
87
- | other | Uncertain / none of the above | human |
43
+ | deletion | Delete/overwrite irreplaceable data | reject |
44
+ | credential | Credentials/keys/auth changes | reject |
45
+ | remote | Remote/production/database | reject |
46
+ | system | System paths/config | reject |
47
+ | bulk | Bulk irreversible operations | reject |
48
+ | approval-config | Auto-approve configuration | reject |
49
+ | safe | Safe/routine reversible | allow |
50
+ | other | Other (unsure) | human |
88
51
 
89
- 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.
90
-
91
- 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.
92
-
93
- Host APIs use authenticated `connection.rpc` (`/api/dsh-auto-approve`), not unauthenticated HTTP.
52
+ Judge prompt language (Chinese or English) only changes the framework, card labels, and that language’s default prompt, not the current table. The shipped prompt is generic classify rules and does not name default category ids; table-specific exceptions live in row descriptions. Custom prompts are stored per language.
94
53
 
95
54
  ## Data
96
55
 
97
- Under `$DSH_HOME` (default `~/.dsh/`). None of this belongs in git.
56
+ Under `$DSH_HOME` (default `~/.dsh/`), never committed:
98
57
 
99
58
  | Path | Purpose |
100
59
  |---|---|
101
- | `auto-approve/allowlist.json` | Keyword buckets / criteria / judge timeout (`0600`) |
102
- | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `PUSH` / `PUSH_SKIP` / `PUSH_FAIL` / `OUTCOME` (`0600`) |
103
- | `auto-approve/events.jsonl` | UI events (`ticket`, `sessionId`; trimmed to last 2000 lines after ~2MB, `0600`) |
104
- | `approval-bridge/qqbot.json` | AppID / AppSecret, `0600` |
105
- | `approval-bridge/config.json` | Judge model, notify target, and other plugin config (`0600`) |
60
+ | `auto-approve/allowlist.json` | Keywords, criteria, judge timeout |
61
+ | `auto-approve/config.json` | Judge model, prompt language/custom prompts, and plugin settings |
62
+ | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `OUTCOME` |
63
+ | `auto-approve/events.jsonl` | Approval tab events |
106
64
 
107
- No audit line means this plugin did not handle that request.
65
+ Corrupt files are not overwritten. Uninstalling the plugin does not delete this directory.
108
66
 
109
- ## Configuration
67
+ ## Config
110
68
 
111
69
  ```yaml
112
70
  - id: dsh-auto-approve
113
71
  config:
114
72
  onlyAutoApprovePreset: true
115
- presetSandbox: workspace-write # or read-only: workspace writes also go through the judge
73
+ presetSandbox: workspace-write # or read-only
74
+ judgePromptLang: zh # or en: prompt framework, card labels, shipped pack
75
+ judgePrompts: # optional per-language template override; '' = shipped
76
+ zh: ''
77
+ en: ''
116
78
  judge:
117
79
  provider: ''
118
80
  model: ''
119
81
  reasoningEffort: ''
120
82
  timeoutMs: 20000
121
- notify:
122
- enabled: true
123
- chatId: ''
124
- userId: ''
125
- timeoutSecs: 120
83
+ # profilePatch: /home/me/.dsh/profiles/web/cordis.patch.yml # optional explicit override
126
84
  ```
127
85
 
128
- 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.
86
+ Empty judge fields follow the deployment default. With no usable route, that request goes to a human. Settings “judge timeout writes `allowlist.json` and wins at runtime.
87
+
88
+ The auto-approve preset is written into the running profile's patch file, resolved from the profile directory (`ctx.baseUrl`); `profilePatch` pins it explicitly when a deployment keeps profiles elsewhere.
129
89
 
130
- 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.
90
+ A patch replaces a row's whole `config`, so the permission table this profile uses is the copy the plugin wrote (the shipped presets plus `auto-approve`). If a DSH upgrade adds or renames a shipped preset, startup compares against `@deepseek-ai/dsh-base`'s patch, logs the missing names, and the settings page shows a card telling you to update the plugin or merge that row by hand. The plugin never rewrites your file on its own.
131
91
 
132
92
  ## Development
133
93
 
134
94
  ```sh
135
95
  node --test tests/*.test.mjs
136
- npm run check
96
+ npm run check # syntax + locale sync check
97
+ npm run locales:sync # regenerate client.js inline dictionaries from locales.mjs
137
98
  ```
138
99
 
139
- 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`.
140
-
141
- For agents working in this repository, follow [AGENTS.md](AGENTS.md).
142
-
143
- ## Releasing
144
-
145
- 1. Set `package.json` `version` (for example `0.1.3`) and merge to `main`.
146
- 2. On npmjs.com, add a Trusted Publisher for this GitHub repo, workflow file `publish.yml` (once).
147
- 3. Tag and push: `git tag v0.1.3 && git push origin v0.1.3`.
148
-
149
- 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.
100
+ See [AGENTS.md](AGENTS.md) and [CHANGELOG.md](CHANGELOG.md).
150
101
 
151
102
  ## License
152
103
 
153
-
154
104
  [MIT](LICENSE)
package/README.zh.md CHANGED
@@ -2,80 +2,42 @@
2
2
 
3
3
  [English](README.md) | 中文
4
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 聊天。
5
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:用**关键词**和**审核模型审核表**自动允许或拒绝工具调用。拿不准就交给原来的网页审批框。
10
6
 
11
7
  ## 安装
12
8
 
13
- 请钉死发行 tag(推荐)。`main` 只放已发布的提交,开发走其他分支。
14
-
15
9
  ```sh
16
- dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.1.3
10
+ dsh plugin --profile web add github:DNAlec/dsh-auto-approve#v0.2.1
17
11
  ```
18
12
 
19
- `v0.1.1` / `v0.1.2` 无法加载网页客户端(`loaded without registering`)。请用 **v0.1.3** 或更新。
20
-
21
13
  打 tag 发布后也可从 npm 安装:
22
14
 
23
15
  ```sh
24
16
  dsh plugin --profile web add @dnalec/dsh-auto-approve
25
17
  ```
26
18
 
27
- 自己开发可以用本地路径:
28
-
29
- ```sh
30
- dsh plugin --profile web add /path/to/this-repo
31
- ```
32
-
33
- 重启 `dsh web`。首次启动会把 `auto-approve` 权限预设写入 profile patch(预设表冻结,不能运行时扩展)。
19
+ 重启 `dsh web`。首次启动会把「自动审批」权限预设写进当前 profile 的 `cordis.patch.yml`;写失败时日志会指出文件和原因。
34
20
 
35
21
  ## 设置
36
22
 
37
23
  设置页 → **自动审批**:
38
24
 
39
- 1. 点「扫码接入机器人」,用手机 QQ 扫码(腾讯官方开放平台流程);也可以手动填 AppID / AppSecret 后保存连接。
40
- 2. 加好友后给机器人发任意一句,在「最近来信」点选审批聊天(推荐私聊)。群必须 @机器人,并另配 `userId`。若尚未选定聊天,扫码者的 openid 会预填为 `chatId`。本插件按**私人 bot** 设计:未绑定 `chatId` 时,任意私聊回复「是」「确认」「用作审批」「yes」「ok」会把该聊天设为审批目标。不要把 bot 加到别人也能私聊的环境。
41
- 3. 在「自动审批模式」里选择:工作区内不审批(预设沙箱 `workspace-write`,推荐),或工作区也走判定(`read-only`)。保存后重启 `dsh web`,并重新选择「自动审批」或开新会话。
42
- 4. 会话权限选「自动审批」。若首次启动后下拉里还没有,再重启一次让 live patch 生效。
43
-
44
- 凭据只存在 `~/.dsh/approval-bridge/qqbot.json`(`0600`),不要提交。
25
+ 1. **模式** 工作区内不审批(`workspace-write`,推荐),或工作区也走判定(`read-only`)。点选即写入;然后重启 `dsh web`,再重新选择「自动审批」或开新会话。
26
+ 2. 按需改**关键词**和**审核表**。恢复默认会按所选审核提示词语言加载出厂包。
27
+ 3. **审核模型** 空则跟随部署默认。与提示词、超时一起保存。
28
+ 4. **审核提示词** 按语言内置默认文案,可改;恢复默认写回该语言出厂模板。用 `{{criteria}}` 插入当前审核表。
29
+ 5. 会话权限选 **自动审批**。
45
30
 
46
- 卸载(`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 凭据不会删除。
47
-
48
- ## 行为
49
-
50
- | 情况 | 结果 |
51
- |---|---|
52
- | 预设沙箱 `workspace-write`,工作区内写入 | 不进审批(沙箱已允许) |
53
- | 关键词命中拒绝 / 审核表动作为拒绝 | 直接 `rejected`,无框、无 QQ |
54
- | 关键词命中允许 / 审核表动作为允许 | 无框、无 QQ;绿条 + `ALLOW` 审计 |
55
- | 关键词命中人工、审核表人工、判定失败 | QQ 收到 `#N`;回复 `批准 N` 后工具继续 |
56
- | `danger-full-access`(越出工作区) | 进入同一条判定管道,不因模式名短路 |
57
- | 网页与 QQ 同时亮 | 先到的 outcome 生效;QQ 先答会关掉网页框(只 abort 网页用的 fork signal,不碰 `req.signal`) |
58
- | QQ 等待超时(默认 120s) | 网页框仍在;QQ 提示到网页继续 |
59
- | 未配凭据 / 未连接 | 自动审批照常;人工只走网页(`PUSH_SKIP`) |
60
- | 审核模型配错 / 判定失败 | 当次转人工(`FAILED`);不改用会话模型 |
61
- | 未捕获工具参数(无命令/路径/内容/code 等) | 转人工(`missing-payload`),禁止自动放行 |
62
- | 捕获字段被截断 | 仍可关键词拒绝;禁止自动放行(`truncated-payload`) |
63
- | 预设不是 `auto-approve` | 不抢答、不推送(除非关闭 `onlyAutoApprovePreset`) |
31
+ ## 怎么判定
64
32
 
65
- QQ 审批推送带「批准 / 拒绝」按钮(单聊、群聊自定义按钮)。也可回复:`批准 17` / `#17 批准` / `yes 17` 放行;`拒绝 17` 只拒这一次。仅 1 条 pending 时允许裸 `批准` / `拒绝`。
33
+ 仅当会话预设是「自动审批」时介入。`workspace-write` 下,工作区内写入不会进审批。`danger-full-access` 走同一条管道。缺参或字段被截断会转人工,禁止自动放行。
66
34
 
67
- 不要 abort `req.signal` 来关网页框:那会把整单变成 `cancelled`。
35
+ 1. **关键词**(拒绝 > 人工 > 允许)。匹配工具名、命令、路径和工作目录。允许词不匹配工具名,也不匹配会话目录名。预置拒绝词覆盖灾难命令(`rm -rf`、`git push -f`、`drop table`、`dd of=`)、审批配置路径,以及 `.env` / `id_rsa` 等凭据路径。点文件按路径字段放宽匹配:`prod.env` 命中,命令里的 `process.env` 不误伤;`id_rsa.pub`(公钥)不算凭据。
36
+ 2. **审核表**。审核模型看到与网页工具卡片相同的字段(并明确标记为不可信数据),只输出类别;程序按该行动作执行。解析失败或超时转人工。类别取**最后一个**「类别:」行,卡片回显不能覆盖结论。
68
37
 
69
- ## 判定管道
38
+ 默认审核表(动作可改;**其他**不能删除):
70
39
 
71
- 仅当 `permissionPresets.current(session) === 'auto-approve'` 时介入(除非关闭「仅该预设」)。预设沙箱是底线、不是管道步骤:`workspace-write` 时工作区内写入根本进不了审批。
72
-
73
- 1. 关键词(拒绝 > 人工 > 允许)。拒绝/人工匹配 **工具名 + command + 路径 + workdir**;允许词不匹配工具名。不匹配模型理由、`description`、文件正文。预置词默认拒绝,含灾难命令形态(`rm -rf`、`git push -f`、`drop table`、`dd of=` 等)、审批配置路径,以及 `.env` / `id_rsa` 等凭据路径。漏掉的交给下一步。捕获字段被截断时仍可关键词拒绝,禁止关键词允许或模型标 safe,转人工。
74
- 2. 审核表:审核模型看到与网页工具卡片相同的字段(含命令、路径、原文/改成、写入内容、code/url/sql 等),只输出类别 id + 理由;程序按表执行允许 / 拒绝 / 人工。解析失败或超时转人工,**不**执行「其他」的动作。风险类默认拒绝;「安全」默认允许;「其他」默认人工。
75
-
76
- 默认审核表(动作均可改;「其他」不能删除):
77
-
78
- | id | 标签 | 默认动作 |
40
+ | id | 标签 | 默认 |
79
41
  |---|---|---|
80
42
  | deletion | 删除/覆盖不可再生数据 | 拒绝 |
81
43
  | credential | 凭据/密钥/授权修改 | 拒绝 |
@@ -86,25 +48,20 @@ QQ 审批推送带「批准 / 拒绝」按钮(单聊、群聊自定义按钮
86
48
  | safe | 安全/常规可回补 | 允许 |
87
49
  | other | 其他(拿不准) | 人工 |
88
50
 
89
- 风险类默认拒绝。只有模型能确认安全才走 `safe` 自动放行;拿不准走「其他」转人。模型若把危险操作归进 `safe`,会被自动放行。
90
-
91
- 设置里可把**审核提示词语言**设为中文或 English(默认中文)。这只换发给模型的框架和卡片标签,**不会**改当前审核表。点「恢复默认审核表」才按所选语言加载出厂包。理由与框架同语言。
92
-
93
- Host API 走已鉴权的 `connection.rpc`(`/api/dsh-auto-approve`),不上无鉴权 HTTP。
51
+ 审核提示词语言(中文 / English)只换框架、卡片标签和该语言的默认提示词,不改当前审核表。出厂提示词是通用归类规则,不绑死默认类别;表相关特例写在各行说明里。自定义提示词按语言分别保存。
94
52
 
95
53
  ## 数据
96
54
 
97
- 位于 `$DSH_HOME`(默认 `~/.dsh/`),均不进 git
55
+ 位于 `$DSH_HOME`(默认 `~/.dsh/`),不进 git
98
56
 
99
57
  | 路径 | 用途 |
100
58
  |---|---|
101
- | `auto-approve/allowlist.json` | 关键词三桶 / 审核表 / 审核超时(`0600`) |
102
- | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `PUSH` / `PUSH_SKIP` / `PUSH_FAIL` / `OUTCOME`(`0600`) |
103
- | `auto-approve/events.jsonl` | UI 事件(含 `ticket`、`sessionId`;超约 2MB 只留最后 2000 条,`0600`) |
104
- | `approval-bridge/qqbot.json` | AppID / AppSecret,`0600` |
105
- | `approval-bridge/config.json` | 审核模型、推送目标等插件配置(`0600`) |
59
+ | `auto-approve/allowlist.json` | 关键词、审核表、审核超时 |
60
+ | `auto-approve/config.json` | 审核模型、提示词语言/自定义提示词与插件配置 |
61
+ | `auto-approve/audit.log` | `ALLOW` / `REJECT` / `HUMAN` / `FAILED` / `OUTCOME` |
62
+ | `auto-approve/events.jsonl` | 审批 tab 事件 |
106
63
 
107
- 没有 audit 行 = 本插件没有处理该请求。
64
+ 损坏文件不会被覆盖。卸载插件不会删除这个目录。
108
65
 
109
66
  ## 配置
110
67
 
@@ -112,43 +69,35 @@ Host API 走已鉴权的 `connection.rpc`(`/api/dsh-auto-approve`),不上
112
69
  - id: dsh-auto-approve
113
70
  config:
114
71
  onlyAutoApprovePreset: true
115
- presetSandbox: workspace-write # 或 read-only:工作区写入也走判定
72
+ presetSandbox: workspace-write # 或 read-only
73
+ judgePromptLang: zh # 或 en:提示词框架、卡片标签、出厂审核表
74
+ judgePrompts: # 可选:按语言覆盖提示词模板;'' = 用出厂模板
75
+ zh: ''
76
+ en: ''
116
77
  judge:
117
78
  provider: ''
118
79
  model: ''
119
80
  reasoningEffort: ''
120
81
  timeoutMs: 20000
121
- notify:
122
- enabled: true
123
- chatId: ''
124
- userId: ''
125
- timeoutSecs: 120
82
+ # profilePatch: /home/me/.dsh/profiles/web/cordis.patch.yml # 可选:显式指定 patch 文件
126
83
  ```
127
84
 
128
- 审核模型字段为空则跟随部署默认模型。没有默认可跟随、也没手动配置,或路由配错,则当次转人工。不会再猜一个模型。设置页「审核超时」写入 `allowlist.json` 的 `judgeTimeoutMs`(运行时以此为准);yaml 里的 `judge.timeoutMs` 只在尚未生成 allowlist 时作缺省。
85
+ 审核模型字段为空则跟随部署默认。没有可用路由时当次转人工。设置页「审核超时」写入 `allowlist.json`,运行时以此为准。
86
+
87
+ 「自动审批」预设写进当前运行 profile 的 patch 文件(路径从 profile 目录 `ctx.baseUrl` 推导);profile 不放在默认位置时可用 `profilePatch` 显式指定。
129
88
 
130
- JSON 读失败(损坏)时本进程用内存默认,**不覆盖磁盘**。设置页除「恢复默认 / 覆盖损坏配置」外不会写坏 allowlist;插件配置损坏需明确覆盖;QQ 凭据损坏需重新保存或扫码。
89
+ patch 是按 id **整块替换** `config` 的,所以这个 profile 用的权限表就是插件写进去的那份副本(出厂预设 + auto-approve)。DSH 升级后如果出厂预设表新增/改名,插件启动时会和 `@deepseek-ai/dsh-base` patch 比对,把缺的名字打进日志,并在设置页显示一张提示卡片(更新插件或手工合并那一行);插件不会自动改写你的文件。
131
90
 
132
91
  ## 开发
133
92
 
134
93
  ```sh
135
94
  node --test tests/*.test.mjs
136
- npm run check
95
+ npm run check # 语法检查 + 文案同步校验
96
+ npm run locales:sync # 按 locales.mjs 重新生成 client.js 内联字典
137
97
  ```
138
98
 
139
- 结构:`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` 生成。
140
-
141
- 在本仓库改代码的 agent 请遵循 [AGENTS.md](AGENTS.md)。
142
-
143
- ## 发布
144
-
145
- 1. 把 `package.json` 的 `version` 改成目标版本(例如 `0.1.3`),合进 `main`。
146
- 2. 在 npmjs.com 给本仓库配置 Trusted Publisher,工作流文件填 `publish.yml`(只需一次)。
147
- 3. 打 tag 并推送:`git tag v0.1.3 && git push origin v0.1.3`。
148
-
149
- tag 必须与 `package.json` 版本一致。工作流会先跑测试再 `npm publish`。这个包名第一次上架可能需要本机 `npm login` 后执行一次 `npm publish --access public`;之后推 tag 即可。
99
+ 见 [AGENTS.md](AGENTS.md) [CHANGELOG.md](CHANGELOG.md)。
150
100
 
151
101
  ## 许可证
152
102
 
153
-
154
103
  [MIT](LICENSE)