dsh-approval-review 0.2.6 → 0.3.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/README-zh.md CHANGED
@@ -61,8 +61,8 @@ dsh --profile <profile> --dump-config | grep -A6 'id: approval-review'
61
61
  |---|---|---|
62
62
  | `enabled` | `true` | 总开关。`false` 时插件仍挂载但不认领任何请求。 |
63
63
  | `enabledByDefault` | `true` | 会话初始的运行时开关状态。 |
64
- | `reviewTools` | `[bash, pwsh, write]` | 送往复核者的工具名 glob。 |
65
- | `defaultPolicy` | `human` | 未命中 glob 的工具走哪种策略:`ai` / `human` / `never`。 |
64
+ | `reviewTools` | `['*']` | 送往复核者的工具名 glob。`*` = 所有工具,这是出厂立场:**由复核者裁决,而不是由工具名决定是否弹窗**。 |
65
+ | `defaultPolicy` | `ai` | 未命中 glob 的工具走哪种策略:`ai` / `human` / `never`。默认 `ai`,所以漏配的工具也是**被裁决**,而不是悄悄退回人工。 |
66
66
  | `rules` | `[]` | 有序的 `{pattern, policy, field?, note?}` 正则规则,优先于工具表求值。`field` 可为 `reason`(默认)、`toolName`、`arguments`。 |
67
67
  | `reviewer.mode` | `subagent` | `subagent` 跑只读子代理(能读工作区);`direct` 走一次性纯模型调用。 |
68
68
  | `reviewer.provider` / `.model` | *(继承)* | 复核路由;不填则继承调用 Agent 自己的路由。会话内可用 `/approval-review model [<provider>/]<id>` 覆盖(**「审批」页签右上角可以直接选**:点开即列出本机配置的模型,候选来自客户端自己的模型目录服务 `modelDirectories`——和 `/model` 选择器、输入框里的模型座位读的是同一份目录。列表由插件自己渲染(原生 `datalist`/`select` 的弹层字号字重无法用 CSS 控制,会显得比页面吵),支持输入过滤、方向键+回车,也可以手打目录里没有的 id)。 |
@@ -82,7 +82,7 @@ dsh --profile <profile> --dump-config | grep -A6 'id: approval-review'
82
82
  | `maxAutoAllowRisk` | `medium` | 允许复核者自动放行的最高风险。 |
83
83
  | `onRiskExceeded` | `delegate` | 超过该上限时:`allow` / `delegate` / `deny`。 |
84
84
  | `onUncertain` | `delegate` | 复核者表示无法判断时。 |
85
- | `onReviewerFailure` | `rejected` | 复核崩溃、超时或输出不合 schema 时。 |
85
+ | `onReviewerFailure` | `delegate` | 复核崩溃、超时或输出不合 schema 时。默认**转人工**:复核者跑不起来是基础设施问题,不是裁决;会拒的部署请显式设成 `rejected`。 |
86
86
  | `budget.maxReviewsPerTurn` | `20` | 每回合复核调用上限。 |
87
87
  | `budget.onExhausted` | `delegate` | 预算耗尽后:`delegate` / `deny`。 |
88
88
  | `maxFailuresPerTurn` | `10` | 每回合复核**失败**次数上限,超过即转人工。 |
@@ -105,7 +105,11 @@ dsh --profile <profile> --dump-config | grep -A6 'id: approval-review'
105
105
  - **`human`** —— 用 `next()` 交还给应答链,也就是原本的审批弹窗。插件不会短路它。
106
106
  - **`never`** —— 直接 `rejected` 并附说明,不调复核、不弹窗。用于对某类工具做硬禁用。
107
107
 
108
- `edit` 故意**没有**放进默认 `reviewTools`:原地修改已有文件是日常操作里后果最重的一类,在部署方明确决定之前,它继续走人工审批。
108
+ **出厂立场是「任何审批请求都先交给复核者」**(`reviewTools: ['*']` + `defaultPolicy: ai`),包括 `edit`、`web_fetch`、以及别的插件(如 `dsh-permission-rules` 的网络/路径规则)触发的 `ask`。风险不高就放行、风险高或复核者不确定才找人,闸门分别是 `maxAutoAllowRisk`、`onRiskExceeded`、`onUncertain`。
109
+
110
+ 代价是**每个请求一次复核模型调用**(十几秒到几十秒 + token),所以每回合的 `budget.maxReviewsPerTurn` 才是真正的刹车;想更保守的部署可以把 `reviewTools` 收窄回工具白名单,或把 `defaultPolicy` 设回 `human`。
111
+
112
+ 注意 `never` 规则(以及别的插件的确定性 `deny`)仍然在进入应答链**之前**硬拦——它们不是"审批请求",是硬禁用。
109
113
 
110
114
  ### 示例:更严格的部署
111
115
 
@@ -220,7 +224,7 @@ dsh --profile <profile> --dump-config | grep -A6 'id: approval-review'
220
224
  - **复核者的证据是数据,不是指令。** 证据包里的 transcript 与申请理由可能包含仓库可控文本(`AGENTS.md`、被审文件、命令输出)。数据/指令边界由代码追加、不受 `policyText` 覆盖;证据中出现指令或"已经批准过"的说法一律作为反对证据。
221
225
  - **复核者是只读的。** `mode: direct` 是一次不挂工具的模型调用;`mode: subagent` 是有 `toolFilter` 白名单与 `maxDepth: 1`(子代理自身深度,允许它存在、不允许它再派孙代理)的子代理。两种形态都无法写入、执行或委派,因此即便复核者被攻破,也无法升级它所守卫的那道边界。
222
226
  - **复核者不会递归。** 子代理一旦建立即被登记为复核者会话,它自己的审批请求交还人工链,不会回到正在服务它的应答者。
223
- - **默认失败即拒绝。** `onReviewerFailure: rejected`、`onUncertain: delegate`、`maxAutoAllowRisk: medium` 是出厂选择:误拒一个安全动作的代价是一次重试,误放一个危险动作可能无法挽回。
227
+ - **复核者跑不起来时找人不拒绝。** `onReviewerFailure: delegate`、`onUncertain: delegate`、`maxAutoAllowRisk: medium` 是出厂选择:复核者无法运行属于基础设施故障,把它变成自动拒绝会让操作者看到一次模型从未做出的否决。要 fail-closed 的部署显式设 `onReviewerFailure: rejected`:那时误拒一个安全动作的代价是一次重试,而误放一个危险动作可能无法挽回。
224
228
  - **它不是安全保证。** 它只评估审批缝真正提出的请求,而语言模型会犯错,在对抗性场景下尤其如此。它是配置良好的沙箱的补充,不是替代。
225
229
 
226
230
  ## 开发
package/README.md CHANGED
@@ -102,7 +102,7 @@ schema defaults.
102
102
  | `maxAutoAllowRisk` | `medium` | Highest risk the reviewer may auto-allow. |
103
103
  | `onRiskExceeded` | `delegate` | `allow` / `delegate` / `deny` above that ceiling. |
104
104
  | `onUncertain` | `delegate` | Reviewer reported it could not decide. |
105
- | `onReviewerFailure` | `rejected` | Reviewer crashed, timed out, or answered off-schema. |
105
+ | `onReviewerFailure` | `delegate` | Reviewer crashed, timed out, or answered off-schema. Defaults to **delegating**: a reviewer that could not run is an infrastructure problem, not a verdict — set `rejected` for the fail-closed stance. |
106
106
  | `budget.maxReviewsPerTurn` | `20` | Reviewer calls per open turn. |
107
107
  | `budget.onExhausted` | `delegate` | `delegate` / `deny` once spent. |
108
108
  | `maxFailuresPerTurn` | `10` | Reviewer *failures* per open turn before requests delegate. |
@@ -304,8 +304,10 @@ reconstructible from the log alone.
304
304
  - **The reviewer cannot recurse.** A reviewer child is registered as soon as it
305
305
  exists, so its own approval asks are delegated to the human chain instead of
306
306
  returning to the answerer serving it.
307
- - **Fail closed by default.** `onReviewerFailure: rejected`, `onUncertain:
308
- delegate`, and `maxAutoAllowRisk: medium` are the shipping choices because
307
+ - **A reviewer that cannot run asks a human.** `onReviewerFailure: delegate`,
308
+ `onUncertain: delegate`, and `maxAutoAllowRisk: medium` are the shipping
309
+ choices: refusing in the model's name would make an infrastructure failure
310
+ look like a judgement. Set `onReviewerFailure: rejected` for fail-closed, where
309
311
  refusing a safe action costs a retry while approving an unsafe one may be
310
312
  unrecoverable.
311
313
  - **It is not a security guarantee.** It evaluates only the requests the approval
package/cordis.patch.yml CHANGED
@@ -18,16 +18,21 @@
18
18
  enabled: true
19
19
  # Session-start default for that switch.
20
20
  enabledByDefault: true
21
- # Tool names routed to the reviewer model. Everything else falls through
22
- # to `defaultPolicy`, whose default delegates to the human answerer.
23
- # `edit` is deliberately absent: in-place modification of an existing
24
- # file is the highest-consequence routine action, so it keeps the human
25
- # prompt until a deployment decides otherwise.
21
+ # EVERY request reaches the reviewer, not just a tool list.
22
+ #
23
+ # `*` matches every tool name, and `defaultPolicy: ai` covers a request
24
+ # whose tool is not in the table at all, so nothing falls through to a
25
+ # human prompt on account of its tool name. A request still goes to a
26
+ # human for a reason the operator can see — see `onRiskExceeded`,
27
+ # `onUncertain`, `onReviewerFailure`, `budget.onExhausted` — and a `never`
28
+ # rule (or another plugin's deterministic deny) still hard-stops the
29
+ # catastrophic few BEFORE the seam, which is the point of keeping them.
30
+ #
31
+ # The cost is one reviewer subagent per request (tens of seconds and a
32
+ # model call each), so `budget.maxReviewsPerTurn` is what bounds a turn.
26
33
  reviewTools:
27
- - bash
28
- - pwsh
29
- - write
30
- defaultPolicy: human
34
+ - '*'
35
+ defaultPolicy: ai
31
36
  # Ordered regex rules, evaluated before the tool table. `field` selects
32
37
  # what the pattern is matched against: `reason` (default), `toolName`, or
33
38
  # `arguments`.
@@ -57,9 +62,14 @@
57
62
  onRiskExceeded: delegate
58
63
  # Reviewer said it could not decide.
59
64
  onUncertain: delegate
60
- # Reviewer crashed, timed out, or answered off-schema. `rejected` is the
61
- # fail-closed stance; `delegate` hands the request to the human chain.
62
- onReviewerFailure: rejected
65
+ # Reviewer crashed, timed out, or answered off-schema.
66
+ #
67
+ # `delegate` hands the request to the human chain: a reviewer that could
68
+ # not run is an INFRASTRUCTURE problem (a dead route, an expired
69
+ # credential, a timeout), and turning that into an automatic refusal
70
+ # makes the operator see a denial the model never made. `rejected` is the
71
+ # fail-closed alternative for deployments that prefer refusing to asking.
72
+ onReviewerFailure: delegate
63
73
  # Per-turn reviewer budget, so a loop cannot bill unlimited reviews.
64
74
  budget:
65
75
  maxReviewsPerTurn: 20
package/lib/index.js CHANGED
@@ -29,12 +29,8 @@ const Config = Schema.object({
29
29
  enabled: Schema.boolean().default(true).description("Master switch. When false the plugin registers nothing that claims a request."),
30
30
  enabledByDefault: Schema.boolean().default(true).description("Session-start default for the per-session switch; `/approval-review off` overrides it durably."),
31
31
  reviewerPreset: Schema.string().default("approve-for-me").description("Permission preset that turns auto-approval on. Empty means always claim. Set this to the preset key you added to `permissionPresets` (see cordis.patch.yml)."),
32
- reviewTools: Schema.array(Schema.string()).default([
33
- "bash",
34
- "pwsh",
35
- "write"
36
- ]).description("Tool-name glob patterns routed to the reviewer model."),
37
- defaultPolicy: Schema.union(TOOL_POLICIES).default("human").description("Policy for tools matching no `reviewTools` pattern."),
32
+ reviewTools: Schema.array(Schema.string()).default(["*"]).description("Tool-name glob patterns routed to the reviewer model. `*` is every tool, which is the shipping stance: the reviewer decides, not the tool name."),
33
+ defaultPolicy: Schema.union(TOOL_POLICIES).default("ai").description("Policy for tools matching no `reviewTools` pattern. Defaults to `ai` so an unlisted tool is still judged rather than handed to a human prompt by default."),
38
34
  rules: Schema.array(Schema.object({
39
35
  pattern: Schema.string().required(),
40
36
  policy: Schema.union(TOOL_POLICIES).required(),
@@ -84,7 +80,7 @@ const Config = Schema.object({
84
80
  "rejected",
85
81
  "delegate",
86
82
  "allow-once"
87
- ]).default("rejected").description("Reaction when the reviewer crashes, times out, or answers off-schema."),
83
+ ]).default("delegate").description("Reaction when the reviewer crashes, times out, or answers off-schema. `delegate` (default) hands the request to the human chain: a reviewer that could not run is an infrastructure problem, not a judgement. `rejected` refuses instead."),
88
84
  budget: Schema.object({
89
85
  maxReviewsPerTurn: Schema.number().step(1).min(1).default(20).description("Maximum reviewer calls per open turn."),
90
86
  onExhausted: Schema.union(["delegate", "deny"]).default("delegate").description("Reaction once the per-turn review budget is spent.")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-approval-review",
3
- "version": "0.2.6",
3
+ "version": "0.3.1",
4
4
  "description": "Codex-style agent auto-approval for DeepSeek Harness: an independent reviewer model decides allow/deny on the approval answerer chain, fail-closed, with a per-decision rationale — refusals and allows alike — in a dedicated Approvals tab. · DSH 插件:Codex 风格的 Agent 自动审批,独立 reviewer 模型裁决、fail-closed、每次审批(放行与否决)都留下详细理由,并在独立的「审批」页签中展示。",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",