dsh-perm-gate 0.2.1-beta.2 → 0.2.1-beta.3

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.ja.md CHANGED
@@ -7,11 +7,13 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1-beta.3] - 2026-09-10
11
+
10
12
  ### 追加
11
13
 
12
14
  - リスク判定 `llmAssist`:設定されたカスタム LLM(OpenAI 互換エンドポイント)が各 `ask` を
13
15
  `safe` / `risky:<カテゴリ>` で判定。ハードカテゴリ(deletion / credential / remote / system /
14
- bulk)は常に人手へ、タイムアウトは 1 回リトライ、失敗は常に fail-closed。
16
+ bulk)は自動拒否、タイムアウトは 1 回リトライ、失敗は常に fail-closed。
15
17
  - 裁決学習(`riskLearning`、既定オフ、`riskThreshold` 1–10、既定 3):人手で承認され実際に実行された
16
18
  neutral リスクをカウントし、指紋一致する同一操作のみ自動許可。`$DSH_HOME/perm-gate/learning.json`
17
19
  に永続化され、ユーザーの YAML ルールには書き込みません。
@@ -78,6 +80,16 @@
78
80
 
79
81
  ### 修正
80
82
 
83
+ - **`llmAssist` の `safe` は記録されるだけで、実際には自動許可されていませんでした。**
84
+ `tools/pre-execute` リスナーは `ask` を即座に返してバックグラウンドで判定していましたが、ホストへ
85
+ 渡した ask はすでに承認 answerer へ向かっており、DSH にはそれを取り消す API がありません
86
+ (リクエストの `signal` ができるのは `cancelled` での決着だけです)。そのためパネルは表示され続け、
87
+ フィードには `safe` の自動許可が出ているのに人手でのクリックが必須でした — さらにハードリスクの
88
+ `auto-deny` はホストにまったく届いていませんでした。リスナーは決定を返す**前に** `refineAsk` を
89
+ await するようになり(`makePreExecuteListener`)、`safe` は `next()` で委譲されパネルは出ず、
90
+ ハードカテゴリは自動拒否、`risky:neutral` / `unresolved` / 判定失敗のみが人手の ask を維持します
91
+ (fail-closed)。待機時間は `riskTimeoutMs`(既定 20 秒)で上限が決まり、すでにキャンセルされた
92
+ 呼び出しは判定をスキップします。
81
93
  - **ユーザーが選んだ権限ティアをゲートが上書きしていました。** ゲートは独立した承認ティアを
82
94
  持ちますが *すべての* プリセットで動作し、あらゆる越境が `ask` になって DSH 承認シームへ
83
95
  転送され、`danger-full-access`(`approval: never`)ではそのシームが **どの answerer よりも
package/CHANGELOG.ko.md CHANGED
@@ -7,11 +7,13 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1-beta.3] - 2026-09-10
11
+
10
12
  ### 추가
11
13
 
12
14
  - 위험 등급 `llmAssist`: 설정된 사용자 지정 LLM(OpenAI 호환 엔드포인트)이 각 `ask`를
13
15
  `safe` / `risky:<카테고리>`로 판정합니다. 하드 카테고리(deletion / credential / remote / system /
14
- bulk)는 항상 사람에게 전달되고, 시간 초과는 1회 재시도하며, 모든 실패는 fail-closed를 유지합니다.
16
+ bulk)는 자동 거부되고, 시간 초과는 1회 재시도하며, 모든 실패는 fail-closed를 유지합니다.
15
17
  - 판정 학습(`riskLearning`, 기본 꺼짐, `riskThreshold` 1–10, 기본 3): 사람이 승인하고 실제 실행된
16
18
  neutral 위험은 카운트되어 지문이 일치하는 동일 작업만 자동 허용 — `$DSH_HOME/perm-gate/learning.json`에
17
19
  영속화되며 사용자 YAML 규칙에는 기록되지 않습니다.
@@ -77,6 +79,15 @@
77
79
 
78
80
  ### 수정
79
81
 
82
+ - **`llmAssist`의 `safe`는 기록만 될 뿐 실제로 자동 허용되지 않았습니다.** `tools/pre-execute`
83
+ 리스너가 `ask`를 즉시 반환하고 백그라운드에서 판정했지만, 호스트로 넘어간 ask는 이미 승인
84
+ answerer로 향하고 있고 DSH에는 이를 되돌릴 API가 없습니다(요청의 `signal`이 할 수 있는 일은
85
+ `cancelled`로 종결시키는 것뿐입니다). 그래서 패널은 계속 표시되었고, 피드에는 `safe` 자동 허용이
86
+ 찍혔는데도 사람이 직접 클릭해야 했습니다 — 하드 위험의 `auto-deny`는 호스트에 아예 전달되지
87
+ 않았습니다. 이제 리스너는 결정을 반환하기 **전에** `refineAsk`를 await하며
88
+ (`makePreExecuteListener`), `safe`는 `next()`로 위임되어 패널이 뜨지 않고, 하드 카테고리는 자동
89
+ 거부되며, `risky:neutral` / `unresolved` / 판정 실패만 사람에게 ask로 남습니다(fail-closed).
90
+ 대기 시간은 `riskTimeoutMs`(기본 20초)로 제한되며, 이미 취소된 호출은 판정을 건너뜁니다.
80
91
  - **사용자가 선택한 권한 티어를 게이트가 덮어썼습니다.** 게이트는 독립 승인 티어를 가지지만
81
92
  *모든* 프리셋에서 동작했고, 모든 경계가 `ask`가 되어 DSH 승인 시임으로 전달되었으며,
82
93
  `danger-full-access`(`approval: never`)에서는 그 시임이 **어떤 answerer보다도 먼저**
package/CHANGELOG.md CHANGED
@@ -7,11 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1-beta.3] - 2026-09-10
11
+
10
12
  ### Added
11
13
 
12
14
  - Risk-graded `llmAssist`: the configured custom LLM (any OpenAI-compatible endpoint) grades each
13
15
  `ask` as `safe` / `risky:<category>`; hard categories (deletion / credential / remote / system /
14
- bulk) always ask, timeouts retry once, and every failure stays fail-closed.
16
+ bulk) auto-deny, timeouts retry once, and every failure stays fail-closed.
15
17
  - Verdict learning (`riskLearning`, off by default; `riskThreshold` 1–10, default 3): neutral-risk
16
18
  asks confirmed by a human and actually executed count toward auto-allowing the exact same
17
19
  operation (fingerprint-matched) — persisted to `$DSH_HOME/perm-gate/learning.json`, never into
@@ -94,6 +96,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
94
96
 
95
97
  ### Fixed
96
98
 
99
+ - **`llmAssist` "safe" now actually auto-allows instead of only being recorded.** The
100
+ `tools/pre-execute` listener returned the `ask` immediately and graded the call in the
101
+ background, but an ask handed to the host is already on its way to the approval answerers and
102
+ DSH offers no API to retract it (the request's own `signal` can only settle it `cancelled`). The
103
+ panel therefore still appeared and the human still had to click, while the feed showed the
104
+ `safe` auto-allow — and a hard-risk `auto-deny` never reached the host at all. The listener now
105
+ awaits `refineAsk` **before** returning the decision (`makePreExecuteListener`): `safe` delegates
106
+ via `next()` with no panel, a hard-risk category auto-denies, and only `risky:neutral` /
107
+ `unresolved` / a grader failure keep the human ask (fail-closed). The wait is bounded by
108
+ `riskTimeoutMs` (default 20 s) and an already-cancelled call skips grading.
97
109
  - **The shell write-pattern layer never ran for DSH's primary shell tool.** `SHELL_TOOLS` listed
98
110
  `bash` / `pwsh` / `sh` / `cmd` / `powershell` but not **`shell`** (DSH's own tool name) or
99
111
  `terminal`, so `git push`, `chmod`, `tee`, redirects and every other write pattern fell through
package/INSTALL.ja.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` バージョン **0.1.0**。判定チェーン、ルールファイル形式、Permissive
15
+ `dsh-perm-gate` バージョン **0.2.1-beta.3**。判定チェーン、ルールファイル形式、Permissive
16
16
  ティアについては [日本語 README](./README.ja.md) を参照してください。
17
17
 
18
18
  ## 必要条件
package/INSTALL.ko.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` 버전 **0.1.0**. 결정 체인, 규칙 파일 형식, Permissive 티어는
15
+ `dsh-perm-gate` 버전 **0.2.1-beta.3**. 결정 체인, 규칙 파일 형식, Permissive 티어는
16
16
  [한국어 README](./README.ko.md)를 참고하세요.
17
17
 
18
18
  ## 요구 사항
package/INSTALL.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` version **0.1.0**. Continue with the [README](./README.md) for the
15
+ `dsh-perm-gate` version **0.2.1-beta.3**. Continue with the [README](./README.md) for the
16
16
  decision chain, the rules file format and the Permissive tier.
17
17
 
18
18
  ## Requirements
package/INSTALL.zh.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- `dsh-perm-gate` 版本 **0.1.0**。裁决链、规则文件格式与 Permissive 档位请见
15
+ `dsh-perm-gate` 版本 **0.2.1-beta.3**。裁决链、规则文件格式与 Permissive 档位请见
16
16
  [中文 README](./README.zh.md)。
17
17
 
18
18
  ## 前置条件
package/README.ja.md CHANGED
@@ -12,13 +12,13 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- > **互換性に関する注記:** v0.1.0 は `ja` / `ko` 辞書を同梱しますが、公式 DSH の
15
+ > **互換性に関する注記:** v0.2.1-beta.3 は `ja` / `ko` 辞書を同梱しますが、公式 DSH の
16
16
  > `LocaleRuntime` が公開するのは `zh` / `en` のみです(`LOCALE_IDS = ["zh", "en"]`)。
17
17
  > 素の DSH で `ja` / `ko` を選択すると `locale "<id>" is not registered` になります。
18
18
  > `LOCALE_IDS`(locale-settings.ts)と `LOCALES` ラベル(client/index.ts)を更新した
19
19
  > DSH fork を使って再ビルドしてください。
20
20
 
21
- バージョン **0.1.0** — 変更履歴は [日本語 changelog](./CHANGELOG.ja.md) を参照。
21
+ バージョン **0.2.1-beta.3** — 変更履歴は [日本語 changelog](./CHANGELOG.ja.md) を参照。
22
22
 
23
23
  DeepSeek Harness 向けの、単一・自己完結・決定論優先・fail-closed な権限ゲートです。
24
24
 
@@ -181,10 +181,10 @@ Full access / `custom`)では、ゲートの判定フローは**一切実行
181
181
 
182
182
  ### リスク判定 llmAssist、裁決学習、イベントフィード
183
183
 
184
- `llmAssist` 有効時、設定された LLM(OpenAI 互換エンドポイントなら任意——`classifierEndpoint` / `classifierModel` / `classifierApiKey` を自分の API に向けてください)が構造化プロトコルで `ask` を 1 件ずつ判定します:
184
+ `llmAssist` 有効時、設定された LLM(OpenAI 互換エンドポイントなら任意——`classifierEndpoint` / `classifierModel` / `classifierApiKey` を自分の API に向けてください)が構造化プロトコルで `ask` を 1 件ずつ判定します。**判定はゲートの `tools/pre-execute` ウォーターフォール内、決定がホストへ返る前に行われます**:`safe` はそのまま委譲されるため承認パネルは一切表示されず、本当に判断できない判定だけが人手に回ります。
185
185
 
186
- - `safe` → 自動許可(監査ソースは `classifier`)。
187
- - `risky` + **ハードリスクカテゴリ**(`deletion`、`credential`、`remote`、`system`、`bulk`)→ 常に人手へ。ハードリスクは自動許可も学習もされません。
186
+ - `safe` → 自動許可(監査ソースは `classifier`)。パネルは表示されません。
187
+ - `risky` + **ハードリスクカテゴリ**(`deletion`、`credential`、`remote`、`system`、`bulk`)→ **自動拒否**、パネルなし。ハードリスクは自動許可も学習もされません。
188
188
  - `risky:neutral` → `riskLearning` 有効時(設定カード内、既定オフ)、人手で承認され実際に実行された neutral リスクは `tool|カテゴリ` ごとにカウントされ、`riskThreshold`(既定 3)到達かつ新呼び出しの操作指紋(コマンド語+対象の基底名)が確認済みサンプルに一致した場合、**同一操作のみ**自動許可されます。学習沈殿(`riskSediment`、既定オン)を有効にすると、しきい値に達したキーの確認サンプルは**決定論的な許可ルール**になります:指紋の正確一致は LLM を経由せず自動許可——llmAssist オフでも継続し、沈殿ルールは設定カードで確認・管理(終止 / サンプル削除)できます。
189
189
  - タイムアウト(`riskTimeoutMs`、既定 20 秒、1 回リトライ)、通信失敗、プロトコル外出力は元の `ask` を維持します。
190
190
 
package/README.ko.md CHANGED
@@ -12,13 +12,13 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- > **호환성 안내:** v0.1.0은 `ja` / `ko` 사전을 포함하지만, 공식 DSH의 `LocaleRuntime`은
15
+ > **호환성 안내:** v0.2.1-beta.3은 `ja` / `ko` 사전을 포함하지만, 공식 DSH의 `LocaleRuntime`은
16
16
  > `zh` / `en`만 노출합니다(`LOCALE_IDS = ["zh", "en"]`). 기본 DSH에서 `ja` / `ko`를
17
17
  > 선택하면 `locale "<id>" is not registered` 오류가 발생합니다. `LOCALE_IDS`
18
18
  > (locale-settings.ts)와 `LOCALES` 라벨(client/index.ts)을 갱신한 DSH fork를 사용해
19
19
  > 다시 빌드하세요.
20
20
 
21
- 버전 **0.1.0** — 변경 내역은 [한국어 changelog](./CHANGELOG.ko.md)를 참고하세요.
21
+ 버전 **0.2.1-beta.3** — 변경 내역은 [한국어 changelog](./CHANGELOG.ko.md)를 참고하세요.
22
22
 
23
23
  DeepSeek Harness용 단일·자족적·결정론 우선·fail-closed 권한 게이트입니다.
24
24
 
@@ -180,10 +180,10 @@ host는 네임스페이스를 live로 읽으므로 변경은 재시작 없이
180
180
 
181
181
  ### 위험 등급 llmAssist, 판정 학습, 이벤트 피드
182
182
 
183
- `llmAssist`가 켜져 있으면 설정된 LLM(OpenAI 호환 엔드포인트라면 무엇이든 — `classifierEndpoint` / `classifierModel` / `classifierApiKey`을 자신의 API로 지정)이 구조화된 프로토콜로 `ask`를 하나씩 판정합니다:
183
+ `llmAssist`가 켜져 있으면 설정된 LLM(OpenAI 호환 엔드포인트라면 무엇이든 — `classifierEndpoint` / `classifierModel` / `classifierApiKey`을 자신의 API로 지정)이 구조화된 프로토콜로 `ask`를 하나씩 판정합니다. **판정은 게이트의 `tools/pre-execute` 워터폴 안에서, 결정이 호스트로 반환되기 전에 이루어집니다**: `safe`는 곧바로 위임되므로 승인 패널이 아예 표시되지 않고, 정말로 판단할 수 없는 판정만 사람에게 전달됩니다.
184
184
 
185
- - `safe` → 자동 허용(감사 소스 `classifier`).
186
- - `risky` + **하드 위험 카테고리**(`deletion`, `credential`, `remote`, `system`, `bulk`) → 항상 사람에게 전달. 하드 위험은 자동 허용도 학습도 되지 않습니다.
185
+ - `safe` → 자동 허용(감사 소스 `classifier`), 패널 없음.
186
+ - `risky` + **하드 위험 카테고리**(`deletion`, `credential`, `remote`, `system`, `bulk`) → **자동 거부**, 패널 없음. 하드 위험은 자동 허용도 학습도 되지 않습니다.
187
187
  - `risky:neutral` → `riskLearning` 활성 시(설정 카드, 기본 꺼짐), 사람이 승인하고 실제 실행된 neutral 위험은 `tool|카테고리` 키로 카운트되며, `riskThreshold`(기본 3) 도달 및 새 호출의 작업 지문(명령어 단어 + 대상 기본 이름)이 확인된 샘플과 일치하면 **동일 작업만** 자동 허용됩니다. 학습 침전(`riskSediment`, 기본 켜짐)을 사용하면 임계값에 도달한 키의 확인 샘플이 **결정론적 허용 규칙**이 됩니다: 지문이 정확히 일치하면 LLM 호출 없이 바로 허용——llmAssist가 꺼져도 유지되며, 침전 규칙은 설정 카드에서 확인·관리(종료 / 샘플 삭제)할 수 있습니다.
188
188
  - 시간 초과(`riskTimeoutMs`, 기본 20초, 1회 재시도), 전송 실패, 프로토콜 외 출력은 원래 `ask`를 유지합니다.
189
189
 
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- > **Compatibility note:** v0.1.0 ships `ja` / `ko` dictionaries, but official DSH exposes
15
+ > **Compatibility note:** v0.2.1-beta.3 ships `ja` / `ko` dictionaries, but official DSH exposes
16
16
  > only `zh` / `en` through `LocaleRuntime` (`LOCALE_IDS = ["zh", "en"]`). On stock DSH,
17
17
  > selecting `ja` / `ko` fails with `locale "<id>" is not registered`. Use a DSH fork that
18
18
  > updates `LOCALE_IDS` (locale-settings.ts) and `LOCALES` labels (client/index.ts), then
@@ -34,7 +34,7 @@
34
34
  > bundle value-imports nothing from `@deepseek-ai/*`, so the 0.1.2
35
35
  > `dsh-client-runtime` → `dsh-client-store` rename cannot break it.
36
36
 
37
- Version **0.1.0** — see the [Changelog](./CHANGELOG.md).
37
+ Version **0.2.1-beta.3** — see the [Changelog](./CHANGELOG.md).
38
38
 
39
39
  A single, self-sufficient, deterministic-first, fail-closed permission gate for DeepSeek Harness.
40
40
 
@@ -175,16 +175,19 @@ exactly as before.
175
175
  ### Risk-graded llmAssist, verdict learning, and the event feed
176
176
 
177
177
  When `llmAssist` is on, the configured LLM grades one `ask` at a time with a structured
178
- protocol. Two receiver sources are selectable in the settings card: a **custom API** (any
178
+ protocol. Grading happens **inside the gate's `tools/pre-execute` waterfall, before the decision
179
+ is returned to the host**: a `safe` verdict delegates the call straight through, so the approval
180
+ panel never appears; only a genuinely uncertain verdict reaches you. Two receiver sources are
181
+ selectable in the settings card: a **custom API** (any
179
182
  OpenAI-compatible endpoint — `classifierEndpoint` / `classifierModel` / `classifierApiKey`,
180
183
  with presets including Xiaomi MiMo `https://api.xiaomimimo.com/v1`), or the **DSH host model
181
184
  group** (the session's configured `llm` service, via `agentDefaultModel.currentSelection`,
182
185
  optionally overridden with `classifierProvider` / `classifierModel`). A **health test** button
183
186
  (`POST /api/dsh-perm-gate/health`) runs one minimal completion and reports latency.
184
187
 
185
- - `safe` → the ask is auto-allowed (audited as the `classifier` source).
186
- - `risky` + a **hard category** (`deletion`, `credential`, `remote`, `system`, `bulk`) →
187
- the ask **always** reaches the human; hard risks are never auto-allowed and never learned.
188
+ - `safe` → the call is auto-allowed (audited as the `classifier` source); no panel is shown.
189
+ - `risky` + a **hard category** (`deletion`, `credential`, `remote`, `system`, `bulk`) → the call
190
+ is **auto-denied** without a panel; hard risks are never auto-allowed and never learned.
188
191
  - `risky:neutral` → with `riskLearning` enabled (Settings card, off by default), each human
189
192
  approval that actually executes (settled via the host's `tools/result` event) counts toward
190
193
  a `tool|category` key; once the count reaches `riskThreshold` (default 3) **and** the new
package/README.zh.md CHANGED
@@ -12,7 +12,7 @@
12
12
  - [日本語 changelog](./CHANGELOG.ja.md)
13
13
  - [한국어 changelog](./CHANGELOG.ko.md)
14
14
 
15
- > **兼容性说明:** v0.1.0 自带 `ja` / `ko` 字典,但官方 DSH 的 `LocaleRuntime`
15
+ > **兼容性说明:** v0.2.1-beta.3 自带 `ja` / `ko` 字典,但官方 DSH 的 `LocaleRuntime`
16
16
  > 只暴露 `zh` / `en`(`LOCALE_IDS = ["zh", "en"]`)。在原版 DSH 上选择 `ja` / `ko`
17
17
  > 会报 `locale "<id>" is not registered`。请使用更新了 `LOCALE_IDS`
18
18
  > (locale-settings.ts)与 `LOCALES` 标签(client/index.ts)的 DSH fork 并重新构建。
@@ -31,7 +31,7 @@
31
31
  > 客户端 bundle 对 `@deepseek-ai/*` 无任何值导入,因此 0.1.2 的
32
32
  > `dsh-client-runtime` → `dsh-client-store` 改名不会影响它。
33
33
 
34
- 版本 **0.1.0** —— 变更见 [Changelog](./CHANGELOG.md)。
34
+ 版本 **0.2.1-beta.3** —— 变更见 [Changelog](./CHANGELOG.md)。
35
35
 
36
36
  一个**单一自足、确定性优先、fail-closed** 的 DeepSeek Harness 权限门插件。
37
37
 
@@ -138,10 +138,10 @@ Full access 之间。DSH 的 bundle patch 对这个 map 是**整表替换**而
138
138
 
139
139
  ### 风险分级 llmAssist、裁决学习与事件流
140
140
 
141
- 开启 `llmAssist` 后,接收 LLM(自定义 OpenAI 兼容端点,或 DSH 宿主模型组——见上文)按结构化协议逐条评估 `ask`:
141
+ 开启 `llmAssist` 后,接收 LLM(自定义 OpenAI 兼容端点,或 DSH 宿主模型组——见上文)按结构化协议逐条评估 `ask`。**判定发生在门禁的 `tools/pre-execute` 瀑布内部、决策返回宿主之前**:`safe` 直接放行,审批面板根本不会出现;只有真正无法确定的判定才会弹到你面前。
142
142
 
143
- - `safe` → 自动放行(审计来源为 `classifier`)。
144
- - `risky` + **硬风险类别**(`deletion`、`credential`、`remote`、`system`、`bulk`)→ 一律转人工;硬风险永不自动放行、也永不进入学习。
143
+ - `safe` → 自动放行(审计来源为 `classifier`),不弹面板。
144
+ - `risky` + **硬风险类别**(`deletion`、`credential`、`remote`、`system`、`bulk`)→ **自动拒绝**、不弹面板;硬风险永不自动放行、也永不进入学习。
145
145
  - `risky:neutral` → 若开启 `riskLearning`(设置卡片内,默认关闭),人工批准且真实执行的 neutral 风险会按 `tool|类别` 计数;计数达到 `riskThreshold`(默认 3)且新调用的操作指纹(命令词 + 目标基名)命中已确认样本时,**同一操作**自动放行。不同目标永不复用该放行。开启学习沉淀(`riskSediment`,默认开)后,满阈值 key 的确认样本会成为**确定性放行规则**:指纹精确命中即直接放行、无需再过 LLM——即使关闭 llmAssist 也继续生效;沉淀规则在设置卡片中可见、可管理(终止学习 / 删除样本)。
146
146
  - 超时(`riskTimeoutMs`,默认 20s,重试 1 次)、传输失败与协议外输出均维持原 `ask`——门禁绝不猜测。
147
147
 
package/lib/index.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import type { Context } from '@deepseek-ai/cordis';
9
9
  import { Config } from './config.js';
10
- import { PermGateRuntime } from './runtime.js';
10
+ import { PermGateRuntime, type ToolExecutionLike } from './runtime.js';
11
11
  export declare const name = "dsh-perm-gate";
12
12
  /**
13
13
  * The `tools` service drives `tools/pre-execute`/`tools/result` (dsh-tools).
@@ -28,4 +28,19 @@ export declare const PERMISSIVE_NAMESPACE = "dsh-perm-gate";
28
28
  * the approval service to `unavailable` — a rejection on the human's behalf.
29
29
  */
30
30
  export declare function makeApprovalObserver(runtime: Pick<PermGateRuntime, 'settleAskOutcome'>): (req: unknown, next: () => Promise<unknown>) => Promise<unknown>;
31
+ /**
32
+ * Build the `tools/pre-execute` waterfall listener (exported for tests).
33
+ *
34
+ * The llmAssist refinement is awaited **before** the decision leaves the
35
+ * waterfall. An ask that has been returned to the host is already on its way to
36
+ * the approval answerers, and DSH offers no API to retract it — the request's
37
+ * own `signal` can only settle it `cancelled` — so a `safe` verdict learned
38
+ * afterwards could be *recorded* but never acted on: the human still had to
39
+ * click. Grading here is what turns `safe` into a real auto-allow and keeps the
40
+ * prompt for the genuinely uncertain verdicts only (`risky:neutral`,
41
+ * `unresolved`, transport failure). The wait is bounded by the classifier's own
42
+ * `riskTimeoutMs` (default 20 s), and a grader failure keeps the original ask
43
+ * (fail-closed).
44
+ */
45
+ export declare function makePreExecuteListener(runtime: Pick<PermGateRuntime, 'decideExecution' | 'refineAsk'>): (exec: ToolExecutionLike, next: () => Promise<unknown>) => Promise<unknown>;
31
46
  export declare function apply(ctx: Context, config?: Record<string, unknown>): PermGateRuntime;
package/lib/index.js CHANGED
@@ -97,6 +97,42 @@ export function makeApprovalObserver(runtime) {
97
97
  return outcome;
98
98
  };
99
99
  }
100
+ /**
101
+ * Build the `tools/pre-execute` waterfall listener (exported for tests).
102
+ *
103
+ * The llmAssist refinement is awaited **before** the decision leaves the
104
+ * waterfall. An ask that has been returned to the host is already on its way to
105
+ * the approval answerers, and DSH offers no API to retract it — the request's
106
+ * own `signal` can only settle it `cancelled` — so a `safe` verdict learned
107
+ * afterwards could be *recorded* but never acted on: the human still had to
108
+ * click. Grading here is what turns `safe` into a real auto-allow and keeps the
109
+ * prompt for the genuinely uncertain verdicts only (`risky:neutral`,
110
+ * `unresolved`, transport failure). The wait is bounded by the classifier's own
111
+ * `riskTimeoutMs` (default 20 s), and a grader failure keeps the original ask
112
+ * (fail-closed).
113
+ */
114
+ export function makePreExecuteListener(runtime) {
115
+ return async (exec, next) => {
116
+ const decision = runtime.decideExecution(exec);
117
+ // Passthrough (allow / stand-down) and a hard-deny need no grading.
118
+ if (decision === undefined)
119
+ return next();
120
+ if (decision.kind !== 'ask')
121
+ return decision;
122
+ // A cancelled call is never worth a model round-trip; the registry rechecks
123
+ // cancellation after this gate settles.
124
+ if (exec.signal?.aborted === true)
125
+ return decision;
126
+ let refined;
127
+ try {
128
+ refined = await runtime.refineAsk(exec, decision);
129
+ }
130
+ catch {
131
+ return decision; // fail-closed: keep the human ask
132
+ }
133
+ return refined ?? next();
134
+ };
135
+ }
100
136
  export function apply(ctx, config = {}) {
101
137
  // Runtime-adjustable config: the composition entry is the base; the settings
102
138
  // namespace layers on top and `current()` always reads the active section.
@@ -329,28 +365,7 @@ export function apply(ctx, config = {}) {
329
365
  catch {
330
366
  // webServer unavailable: the feed remains disk-only, sediment is view-only in files
331
367
  }
332
- const listener = async (exec, next) => {
333
- const decision = runtime.decideExecution(exec);
334
- if (decision === undefined)
335
- return next();
336
- // llmAssist: the panel MUST appear immediately after pre-execute returns.
337
- // We NEVER await refineAsk here — return the original ask immediately so
338
- // the approval panel pops up. The LLM classifier runs in the background
339
- // and may auto-allow later via settleExecution.
340
- if (decision.kind === 'ask') {
341
- // Fire-and-forget: background LLM grading
342
- runtime.refineAsk(exec, decision)
343
- .then((refined) => {
344
- if (refined === undefined) {
345
- // classifier said "safe" → auto-allow
346
- runtime.settleExecution(exec, { isError: false });
347
- }
348
- })
349
- .catch(() => { });
350
- return decision;
351
- }
352
- return decision;
353
- };
368
+ const listener = makePreExecuteListener(runtime);
354
369
  // `tools/pre-execute` / `tools/result` / `approval/request` are service events,
355
370
  // not part of cordis core's typed `Events`, so they are registered through the
356
371
  // string overload.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-perm-gate",
3
- "version": "0.2.1-beta.2",
3
+ "version": "0.2.1-beta.3",
4
4
  "description": "DSH permission-gate: a single self-sufficient, deterministic-first, fail-closed gate covering P0 hard-deny -> P1 session grant -> P2 static rule (allow/deny) chain -> P3 optional LLM semantic classifier -> P4 ask, with command whitelist/blacklist.",
5
5
  "type": "module",
6
6
  "files": [