dsh-perm-gate 4.1.0 → 4.1.2
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 +26 -0
- package/CHANGELOG.ko.md +24 -0
- package/CHANGELOG.md +26 -0
- package/INSTALL.ja.md +28 -14
- package/INSTALL.ko.md +23 -11
- package/INSTALL.md +32 -16
- package/INSTALL.zh.md +22 -11
- package/README.ja.md +34 -7
- package/README.ko.md +34 -7
- package/README.md +34 -7
- package/README.zh.md +29 -7
- package/assets/allow1.png +0 -0
- package/assets/allow2.png +0 -0
- package/assets/gateoff.png +0 -0
- package/assets/llms.png +0 -0
- package/assets/permlist.png +0 -0
- package/assets/setting-head.png +0 -0
- package/assets/setting-rules.png +0 -0
- package/cordis.patch.yml +11 -8
- package/dsh.plugin.json +2 -2
- package/package.json +12 -2
- package/scripts/patch-permission-glyph.mjs +225 -42
package/CHANGELOG.ja.md
CHANGED
|
@@ -5,6 +5,32 @@
|
|
|
5
5
|
形式は [Keep a Changelog](https://keepachangelog.com/ja/1.1.0/) に従い、
|
|
6
6
|
このプロジェクトは [Semantic Versioning](https://semver.org/spec/v2.0.0.html) に準拠します。
|
|
7
7
|
|
|
8
|
+
## [4.1.2] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### 修正
|
|
11
|
+
|
|
12
|
+
- **入力欄グリフのパッチが DSH 0.1.5 で再び機能します。** `scripts/patch-permission-glyph.mjs`
|
|
13
|
+
は `["permissive",` の直後にエントリを挿入していましたが、そのキーは DSH 0.1.2 がグリフを
|
|
14
|
+
描いていた値で、0.1.5-rc.2 のバンドルにはどこにも存在しません。そのためパッチは
|
|
15
|
+
`anchor not found` で失敗し、両ティアはテキストのみのままでした。取得元はティアごとに
|
|
16
|
+
`GLYPH_TARGETS` で宣言し、アンカーは**解決**します —— まず `["<key>",`、次にバンドルが使う
|
|
17
|
+
定数別名(`danger-full-access` は `[FULL_ACCESS,`)。ホストが描かないキーは、Map が実際に
|
|
18
|
+
持つキーと一緒に報告されます。
|
|
19
|
+
- **UI を提供していない DSH インストールにパッチを当てなくなりました。** 候補は profile
|
|
20
|
+
スコープを先、CLI インストールを次に並べ、同じバンドルを持つ**他の**インストールは黙って
|
|
21
|
+
見送らず出力に列挙します。このプロジェクトのホストで実測:グローバル CLI インストールが
|
|
22
|
+
ランチャー管理のランタイムを隠していたため、パッチは成功と報告しつつピッカーは
|
|
23
|
+
アイコンなしのままでした。
|
|
24
|
+
|
|
25
|
+
### 変更
|
|
26
|
+
|
|
27
|
+
- **各ティアは、共有するファイルサンドボックスのグリフを付けます(共通の盾+目ではなく)。**
|
|
28
|
+
「自动审查」は `workspace-write` のグリフを、「自动审查(高权限)」は `danger-full-access` の
|
|
29
|
+
グリフをコピーするため、アイコンはそのティアが実際に与えるアクセスと一致します。同一アイコンの
|
|
30
|
+
旧挙動は、`permissive` のグリフを持っていた 0.1.2 の Map に由来します。
|
|
31
|
+
- **2 つのグリフは両方適用するか、両方とも適用しません。** 解決できない取得元キーは何も書かない
|
|
32
|
+
拒否であり、部分的にパッチされたバンドルにはなりません。
|
|
33
|
+
|
|
8
34
|
## [2.4.1] - 2026-09-17
|
|
9
35
|
|
|
10
36
|
### 修正
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -5,6 +5,30 @@
|
|
|
5
5
|
형식은 [Keep a Changelog](https://keepachangelog.com/ko/1.1.0/)을 따르며,
|
|
6
6
|
이 프로젝트는 [Semantic Versioning](https://semver.org/spec/v2.0.0.html)을 준수합니다.
|
|
7
7
|
|
|
8
|
+
## [4.1.2] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### 수정됨
|
|
11
|
+
|
|
12
|
+
- **입력창 글리프 패치가 DSH 0.1.5에서 다시 동작합니다.** `scripts/patch-permission-glyph.mjs`는
|
|
13
|
+
`["permissive",` 뒤에 항목을 끼워 넣었지만, 그 키는 DSH 0.1.2가 글리프를 그리던 값이고
|
|
14
|
+
0.1.5-rc.2 번들에는 어디에도 없습니다. 그래서 패치는 `anchor not found`로 실패했고 두 티어는
|
|
15
|
+
텍스트만 표시했습니다. 이제 원본은 티어별로 `GLYPH_TARGETS`에 선언하고, 앵커는 **해석**합니다
|
|
16
|
+
—— 먼저 `["<key>",`, 다음으로 번들이 쓰는 상수 별칭(`danger-full-access`는 `[FULL_ACCESS,`).
|
|
17
|
+
호스트가 그리지 않는 키는 Map이 실제로 가진 키와 함께 보고됩니다.
|
|
18
|
+
- **UI를 제공하지 않는 DSH 설치에는 더 이상 패치하지 않습니다.** 후보는 profile 스코프를
|
|
19
|
+
먼저, CLI 설치를 다음으로 정렬하고, 같은 번들을 가진 **다른** 설치는 조용히 넘기지 않고
|
|
20
|
+
출력에 나열합니다. 이 프로젝트의 호스트에서 실측: 전역 CLI 설치가 런처가 관리하는
|
|
21
|
+
런타임을 가려서, 패치는 성공을 보고했지만 선택기는 아이콘 없이 남아 있었습니다.
|
|
22
|
+
|
|
23
|
+
### 변경됨
|
|
24
|
+
|
|
25
|
+
- **각 티어는 공유하는 파일 샌드박스의 글리프를 답니다(공통 방패+눈이 아니라).** 「自动审查」는
|
|
26
|
+
`workspace-write` 글리프를, 「自动审查(高权限)」는 `danger-full-access` 글리프를 복사하므로
|
|
27
|
+
아이콘이 그 티어가 실제로 부여하는 접근 권한과 일치합니다. 동일 아이콘 쌍은 `permissive`
|
|
28
|
+
글리프를 갖고 있던 0.1.2의 Map에서 온 동작입니다.
|
|
29
|
+
- **두 글리프는 모두 적용되거나 모두 적용되지 않습니다.** 해석할 수 없는 원본 키는 아무것도 쓰지
|
|
30
|
+
않는 거부이며, 일부만 패치된 번들이 되지 않습니다.
|
|
31
|
+
|
|
8
32
|
## [2.4.1] - 2026-09-17
|
|
9
33
|
|
|
10
34
|
### 수정됨
|
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [4.1.2] - 2026-09-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **The composer glyph patch works on DSH 0.1.5 again.** `scripts/patch-permission-glyph.mjs`
|
|
13
|
+
spliced its entry after `["permissive",` — a key DSH 0.1.2 rendered a glyph for and 0.1.5-rc.2
|
|
14
|
+
no longer contains anywhere in the bundle — so the patch died with `anchor not found` and both
|
|
15
|
+
plugin tiers stayed text-only. The sources are now declared per tier in `GLYPH_TARGETS` and each
|
|
16
|
+
anchor is **resolved**: `["<key>",` first, then the constant alias the bundle uses instead
|
|
17
|
+
(`danger-full-access` is keyed `[FULL_ACCESS,`). A source key this host does not render is
|
|
18
|
+
reported together with the keys the map does carry.
|
|
19
|
+
- **The probe no longer patches a DSH install that is not serving the UI.** Candidate bundles are
|
|
20
|
+
ordered profile scope first, CLI install second, and any *other* install carrying the same
|
|
21
|
+
bundle is listed in the output instead of being silently passed over. Measured on this
|
|
22
|
+
project's own host: a global CLI install shadowed the launcher-managed runtime, so the patch
|
|
23
|
+
reported success while the picker stayed icon-free.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Each tier wears the glyph of the file sandbox it shares, not one shared shield+eye.** 自动审查
|
|
28
|
+
copies the `workspace-write` glyph and 自动审查(高权限) the `danger-full-access` one, so the icon
|
|
29
|
+
matches the access the tier actually grants. The old identical-icon pair came from 0.1.2, whose
|
|
30
|
+
map still keyed a `permissive` glyph that 0.1.5 dropped.
|
|
31
|
+
- **Both glyphs are applied, or neither.** A source key the script cannot resolve is a refusal that
|
|
32
|
+
writes nothing, rather than a half-patched bundle.
|
|
33
|
+
|
|
8
34
|
## [2.6.0] - 2026-09-17
|
|
9
35
|
|
|
10
36
|
### Added
|
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` バージョン **
|
|
15
|
+
`dsh-perm-gate` バージョン **4.1.2**。判定チェーン、ルールファイル形式、自动审查
|
|
16
16
|
ティアについては [日本語 README](./README.ja.md) を参照してください。
|
|
17
17
|
|
|
18
18
|
## 必要条件
|
|
@@ -89,13 +89,21 @@ dsh profile reload --profile web
|
|
|
89
89
|
|
|
90
90
|
## **DSH** アップグレード後:入力欄アイコンのパッチを再適用
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
「自动审查」と「自动审查(高权限)」の 2 つのティアにアイコンが付くのは、
|
|
93
93
|
`scripts/patch-permission-glyph.mjs` が **DSH ホストパッケージ内の閉じた Map** に
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
それらを追加したからにすぎません ——「自动审查」は `workspace-write` の盾+鉛筆を、
|
|
95
|
+
「自动审查(高权限)」は `danger-full-access` の盾+感嘆符をコピーしており、それぞれが
|
|
96
|
+
実際に共有するファイルサンドボックスのグリフになります。DSH はプラグイン提供ティアに
|
|
97
|
+
**設計上アイコンを与えません** —— その Map 自身のコメントが *"host-configured names
|
|
98
|
+
outside the design set get none"* と述べています。プラグインが影響できる option
|
|
99
|
+
オブジェクトは `{value, name, description}` しか運ばないため、代わりに使えるプラグイン側の
|
|
100
|
+
シームは存在しません。
|
|
101
|
+
|
|
102
|
+
スクリプトは取得元のキーを**解決**します(`["<key>",` を先に、次にホストが使う定数別名 ——
|
|
103
|
+
0.1.5 の `danger-full-access` は `[FULL_ACCESS,`)。DSH はこの設計セットを一度変更して
|
|
104
|
+
います —— 0.1.2 には `permissive` のグリフがあり、0.1.5 には `read-only` /
|
|
105
|
+
`workspace-write` / `danger-full-access` しかありません。解決できないキーは、Map が実際に
|
|
106
|
+
持つキーと一緒に報告されるため、ティアの付け替えは `GLYPH_TARGETS` の 1 行で済みます。
|
|
99
107
|
|
|
100
108
|
このパッチは**ホスト**のファイルを書き換えるので、**DSH のアップグレードや再インストールで
|
|
101
109
|
失われます**。*本プラグイン*のアップグレードでは失われません。アイコンは元々プラグインの
|
|
@@ -114,11 +122,15 @@ dsh profile reload --profile web
|
|
|
114
122
|
プラグインが許可なく自分の harness を書き換えてよい理由はありません。適用の有無にかかわらず
|
|
115
123
|
DSH の動作に影響はなく、ティア自体はそのまま機能します。
|
|
116
124
|
|
|
117
|
-
このスクリプトは冪等(2 回目は no-op)で、バックアップは 1
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
125
|
+
このスクリプトは冪等(2 回目は no-op)で、バックアップは 1 度だけ取り、2 つのグリフは
|
|
126
|
+
両方適用するか両方とも適用しないか(解決できない取得元キーは拒否であり、部分適用では
|
|
127
|
+
ありません)のいずれかで、壊れたスライスを書き込もうとしません —— 結果に対して
|
|
128
|
+
`node --check` を実行し、失敗すればバックアップを復元します —— したがって DSH
|
|
129
|
+
アップグレードのたびに無条件で再実行して安全です。
|
|
130
|
+
呼び出し元の profile スコープを先に、CLI インストールを次に探すため、nvm のバージョン
|
|
131
|
+
変更やインストール symlink の貼り替えでも壊れません。マシンに複数の DSH インストールが
|
|
132
|
+
ある場合、**パッチを当てなかった**方は出力に列挙されます —— 目の前の UI を提供している
|
|
133
|
+
のはそのうち 1 つだけなので、必要ならそのパスを引数で明示してください。
|
|
122
134
|
|
|
123
135
|
**プラグインを再インストールしてもアイコンは戻りません。**
|
|
124
136
|
`dsh plugin --profile web add …` は profile ディレクトリ内の pnpm に引数を転送し、
|
|
@@ -194,10 +206,12 @@ DSH の bundle patch は `permission.config.presets` を key 単位でマージ
|
|
|
194
206
|
します。profile を再読込して `cordis.patch.yml` を再適用し、後から読み込まれる
|
|
195
207
|
プラグインが `presets` を上書きしていないか確認してください。
|
|
196
208
|
|
|
197
|
-
|
|
209
|
+
**「自动审查」ティアの入力欄アイコンが消えた。**
|
|
198
210
|
DSH のアップグレードまたは再インストールが、パッチを当てたホスト bundle を置き換え
|
|
199
211
|
ました。プラグインを再インストールしても戻りません。
|
|
200
|
-
`npx dsh-perm-gate-patch-glyph`
|
|
212
|
+
`npx dsh-perm-gate-patch-glyph` を実行して再読込してください。取得元キーが無いと
|
|
213
|
+
報告された場合は DSH がグリフ表を再度変更しています —— エラーが現在のキー一覧を
|
|
214
|
+
示すので、`GLYPH_TARGETS` の該当ティアを付け替えてください。
|
|
201
215
|
ティア自体は影響を受けません —— パッチなしでもラベルとゲートは動作します。
|
|
202
216
|
|
|
203
217
|
**ルールファイルがあるのに `--list` が `ruleCount: 0` を返す。**
|
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` 버전 **
|
|
15
|
+
`dsh-perm-gate` 버전 **4.1.2**. 결정 체인, 규칙 파일 형식, 自动审查 티어는
|
|
16
16
|
[한국어 README](./README.ko.md)를 참고하세요.
|
|
17
17
|
|
|
18
18
|
## 요구 사항
|
|
@@ -90,13 +90,21 @@ dsh profile reload --profile web
|
|
|
90
90
|
|
|
91
91
|
## **DSH** 업그레이드 후: 입력창 아이콘 패치 재적용
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
`scripts/patch-permission-glyph.mjs`가 **DSH 호스트 패키지 안의 닫힌 Map**에
|
|
95
|
-
추가했기
|
|
93
|
+
「自动审查」와「自动审查(高权限)」두 티어에 아이콘이 붙는 것은
|
|
94
|
+
`scripts/patch-permission-glyph.mjs`가 **DSH 호스트 패키지 안의 닫힌 Map**에 두 항목을
|
|
95
|
+
추가했기 때문입니다 ——「自动审查」는 `workspace-write`의 방패+연필을,
|
|
96
|
+
「自动审查(高权限)」는 `danger-full-access`의 방패+느낌표를 복사해 각자가 실제로 공유하는
|
|
97
|
+
파일 샌드박스의 글리프를 갖습니다. DSH는 플러그인이 제공한 티어에 **설계상 아이콘을 주지
|
|
96
98
|
않습니다** —— 그 Map 자체의 주석이 *"host-configured names outside the design set get
|
|
97
99
|
none"*이라고 말합니다. 플러그인이 영향을 줄 수 있는 option 객체는
|
|
98
100
|
`{value, name, description}`만 나르므로, 대신 쓸 수 있는 플러그인 측 이음새가 없습니다.
|
|
99
101
|
|
|
102
|
+
스크립트는 원본 키를 **해석**합니다(`["<key>",`를 먼저, 다음으로 호스트가 쓰는 상수 별칭 ——
|
|
103
|
+
0.1.5의 `danger-full-access`는 `[FULL_ACCESS,`). DSH는 이 설계 집합을 이미 한 번 바꿨습니다 ——
|
|
104
|
+
0.1.2에는 `permissive` 글리프가 있었고 0.1.5에는 `read-only` / `workspace-write` /
|
|
105
|
+
`danger-full-access`만 있습니다. 해석할 수 없는 키는 Map이 실제로 가진 키와 함께 보고되므로
|
|
106
|
+
티어 교체는 `GLYPH_TARGETS` 한 줄이면 됩니다.
|
|
107
|
+
|
|
100
108
|
이 패치는 **호스트** 파일을 고치므로 **DSH 업그레이드나 재설치에서 사라집니다**.
|
|
101
109
|
*이 플러그인*을 업그레이드할 때는 사라지지 않습니다. 아이콘은 애초에 플러그인 소유가
|
|
102
110
|
아니었고, 플러그인 자신의 기여(`cordis.patch.yml`의 `name:` / `description:`)는
|
|
@@ -114,10 +122,13 @@ dsh profile reload --profile web
|
|
|
114
122
|
플러그인이 허락 없이 자신이 설치된 harness를 다시 쓸 이유는 없습니다. 적용 여부와 무관하게
|
|
115
123
|
DSH 동작에는 영향이 없고 티어 자체는 그대로 기능합니다.
|
|
116
124
|
|
|
117
|
-
이 스크립트는 멱등이며(두 번째 실행은 no-op), 백업은 한 번만 뜨고,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
이 스크립트는 멱등이며(두 번째 실행은 no-op), 백업은 한 번만 뜨고, 두 글리프를 모두
|
|
126
|
+
적용하거나 모두 적용하지 않으며(해석할 수 없는 원본 키는 거부이지 부분 적용이 아닙니다),
|
|
127
|
+
잘못 잘린 조각을 쓰지 않습니다 —— 결과에 `node --check`를 돌려 실패하면 백업을 복원합니다
|
|
128
|
+
—— 따라서 DSH 업그레이드 때마다 무조건 다시 실행해도 안전합니다. 호출자의 profile 스코프를 먼저, CLI 설치를 다음으로 탐색하므로 nvm 버전 변경이나 설치
|
|
129
|
+
symlink 재지정에도 깨지지 않습니다. 머신에 DSH 설치가 여러 개면 **패치하지 않은** 쪽이
|
|
130
|
+
출력에 나열됩니다 —— 눈앞의 UI를 제공하는 것은 그중 하나뿐이므로, 필요하면 그 경로를
|
|
131
|
+
인수로 명시하세요.
|
|
121
132
|
|
|
122
133
|
**플러그인을 재설치해도 아이콘은 돌아오지 않습니다.**
|
|
123
134
|
`dsh plugin --profile web add …`는 profile 디렉터리 안의 pnpm으로 인자를 넘기고
|
|
@@ -190,11 +201,12 @@ DSH 번들 패치는 `permission.config.presets`를 key 단위로 병합하지
|
|
|
190
201
|
합니다. profile을 다시 불러와 `cordis.patch.yml`을 재적용하고, 더 늦게 로드되는
|
|
191
202
|
플러그인이 `presets`를 덮어쓰지 않는지 확인하세요.
|
|
192
203
|
|
|
193
|
-
|
|
204
|
+
**「自动审查」티어의 입력창 아이콘이 사라졌습니다.**
|
|
194
205
|
DSH 업그레이드나 재설치가 패치해 둔 호스트 번들을 교체했습니다. 플러그인을
|
|
195
206
|
재설치해도 되돌아오지 않습니다. `npx dsh-perm-gate-patch-glyph`를 실행하고
|
|
196
|
-
다시 불러오세요.
|
|
197
|
-
|
|
207
|
+
다시 불러오세요. 원본 키가 없다고 보고되면 DSH가 글리프 표를 다시 바꾼 것입니다 ——
|
|
208
|
+
오류가 현재 키 목록을 알려 주므로 `GLYPH_TARGETS`에서 해당 티어를 다시 지정하세요.
|
|
209
|
+
티어 자체는 영향받지 않습니다 —— 패치가 없어도 라벨과 게이트는 정상 동작합니다.
|
|
198
210
|
|
|
199
211
|
**규칙 파일이 있는데 `--list`가 `ruleCount: 0`을 표시합니다.**
|
|
200
212
|
`rulesFile`은 플러그인 디렉터리가 아니라 Harness 프로세스의 CWD에서 해석됩니다.
|
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 **
|
|
15
|
+
`dsh-perm-gate` version **4.1.2**. Continue with the [README](./README.md) for the
|
|
16
16
|
decision chain, the rules file format and the 自动审查 tier.
|
|
17
17
|
|
|
18
18
|
## Requirements
|
|
@@ -90,12 +90,21 @@ dsh profile reload --profile web
|
|
|
90
90
|
|
|
91
91
|
## After a **DSH** upgrade: re-apply the composer glyph patch
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
Both 自动审查 tiers draw an icon only because `scripts/patch-permission-glyph.mjs`
|
|
94
|
+
added them to a **closed map inside a DSH host package**: 自动审查 copies the
|
|
95
|
+
`workspace-write` glyph and 自动审查(高权限) the `danger-full-access` one — the
|
|
96
|
+
built-in tier each shares a file sandbox with. DSH gives host-configured tiers no
|
|
97
|
+
glyph by design — the map's own comment reads *"host-configured names outside the
|
|
98
|
+
design set get none"* — and the option objects a plugin can influence carry only
|
|
99
|
+
`{value, name, description}`, so there is no plugin-side seam to use instead.
|
|
100
|
+
|
|
101
|
+
The script resolves each source key instead of assuming its spelling: `["<key>",`
|
|
102
|
+
first, then the constant alias the bundle may use instead (`[FULL_ACCESS,` for
|
|
103
|
+
`danger-full-access`). DSH has already changed this design set once — 0.1.2 keyed a
|
|
104
|
+
`permissive` glyph, 0.1.5 keeps only `read-only` / `workspace-write` /
|
|
105
|
+
`danger-full-access` — and a source key it cannot resolve is reported together with
|
|
106
|
+
the keys the map does carry, so re-pointing a tier is a one-line edit to
|
|
107
|
+
`GLYPH_TARGETS`.
|
|
99
108
|
|
|
100
109
|
That patch edits a **host** file, so a DSH upgrade or reinstall erases it. Upgrading
|
|
101
110
|
*this plugin* does not: the plugin never owned the glyph, and its own contribution
|
|
@@ -112,11 +121,16 @@ the `dsh-perm-gate-patch-glyph` bin — so no source checkout is needed. It is d
|
|
|
112
121
|
**not** wired to `postinstall`: it edits a host package, and a plugin must not rewrite its
|
|
113
122
|
harness uninvited. Running DSH is unaffected either way; the tier works with or without it.
|
|
114
123
|
|
|
115
|
-
It is idempotent (a second run is a no-op), backs the bundle up once,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
It is idempotent (a second run is a no-op), backs the bundle up once, applies both
|
|
125
|
+
glyphs or neither (a source key it cannot resolve is a refusal, not a half-patch),
|
|
126
|
+
and refuses to write a mis-sliced bundle — it runs `node --check` on the result and
|
|
127
|
+
restores the backup if that fails — so it is safe to run unconditionally after every
|
|
128
|
+
DSH upgrade.
|
|
129
|
+
It locates the bundle from the caller's profile scope first and the CLI install second,
|
|
130
|
+
so an nvm version change or a re-pointed install symlink does not break it — and when the
|
|
131
|
+
machine carries more than one DSH install, the ones it did **not** patch are listed in the
|
|
132
|
+
output, because only one of them serves the UI in front of you. Pass that path explicitly
|
|
133
|
+
to patch it.
|
|
120
134
|
|
|
121
135
|
**Reinstalling the plugin does not restore the glyph.** `dsh plugin --profile web
|
|
122
136
|
add …` forwards to pnpm inside the profile directory and writes the profile's own
|
|
@@ -191,11 +205,13 @@ The DSH bundle patch replaces the whole `permission.config.presets` map instead
|
|
|
191
205
|
merging per key. Reload the profile so `cordis.patch.yml` is re-applied, and make
|
|
192
206
|
sure no later plugin overwrites `presets`.
|
|
193
207
|
|
|
194
|
-
|
|
195
|
-
A DSH upgrade or reinstall replaced the host bundle the
|
|
208
|
+
**A 自动审查 tier lost its icon in the composer.**
|
|
209
|
+
A DSH upgrade or reinstall replaced the host bundle the glyphs were patched into;
|
|
196
210
|
re-running the plugin install will not bring it back. Run
|
|
197
|
-
`npx dsh-perm-gate-patch-glyph` and reload.
|
|
198
|
-
|
|
211
|
+
`npx dsh-perm-gate-patch-glyph` and reload. If it reports that a source key is gone,
|
|
212
|
+
DSH changed the glyph map again: the error names the keys the map now carries, so
|
|
213
|
+
re-point the affected tier in `GLYPH_TARGETS`. The tiers themselves are
|
|
214
|
+
unaffected — their labels and their gating keep working without the patch.
|
|
199
215
|
|
|
200
216
|
**`--list` reports `ruleCount: 0` although my rules file exists.**
|
|
201
217
|
`rulesFile` is resolved against the process CWD of the harness, not the plugin
|
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` 版本 **
|
|
15
|
+
`dsh-perm-gate` 版本 **4.1.2**。裁决链、规则文件格式与自动审查档位请见
|
|
16
16
|
[中文 README](./README.zh.md)。
|
|
17
17
|
|
|
18
18
|
## 前置条件
|
|
@@ -87,11 +87,18 @@ dsh profile reload --profile web
|
|
|
87
87
|
|
|
88
88
|
## **DSH** 升级后:重打输入区图标补丁
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
两个「自动审查」档位之所以有图标,只是因为 `scripts/patch-permission-glyph.mjs`
|
|
91
|
+
把它们加进了 **DSH 宿主包里的一张封闭 Map**:「自动审查」复制 `workspace-write` 的
|
|
92
|
+
盾+铅笔,「自动审查(高权限)」复制 `danger-full-access` 的盾+感叹号 —— 各自对应
|
|
93
|
+
自己实际共用的文件沙箱。DSH 对插件档位**设计上就不给图标** —— 那张表自己的注释写着
|
|
94
|
+
*"host-configured names outside the design set get none"*;而插件能影响的 option 对象
|
|
95
|
+
只携带 `{value, name, description}`,所以插件侧没有可用的接缝。
|
|
96
|
+
|
|
97
|
+
脚本会**解析**源键而不是假定它的写法:先找 `["<key>",`,再找宿主可能改用的常量别名
|
|
98
|
+
(`danger-full-access` 在 0.1.5 里就写作 `[FULL_ACCESS,`)。宿主已经改过一次设计集 ——
|
|
99
|
+
0.1.2 有 `permissive` 图标,0.1.5 只剩 `read-only` / `workspace-write` /
|
|
100
|
+
`danger-full-access` —— 解析不到的源键会在报错里连同「表里实际有哪些键」一起给出,
|
|
101
|
+
所以改档位只需改 `GLYPH_TARGETS` 一行。
|
|
95
102
|
|
|
96
103
|
该补丁改的是**宿主**文件,因此 **DSH 升级或重装会把它抹掉**。升级*本插件*不会:
|
|
97
104
|
图标从来不属于插件,而插件自己的贡献(`cordis.patch.yml` 里的 `name:` /
|
|
@@ -108,9 +115,12 @@ dsh profile reload --profile web
|
|
|
108
115
|
`postinstall` 上:它改的是宿主包,插件不该未经许可改写自己所在的 harness。有没有它
|
|
109
116
|
都不影响 DSH 运行,档位本身照常工作。
|
|
110
117
|
|
|
111
|
-
脚本是幂等的(重复执行是 no-op
|
|
112
|
-
`node --check
|
|
113
|
-
|
|
118
|
+
脚本是幂等的(重复执行是 no-op),只备份一次,两个图标要么都打要么都不打(源键解析不到
|
|
119
|
+
即拒绝,不会打出半个补丁),且拒绝写坏切片 —— 它会对结果跑 `node --check`,失败即还原
|
|
120
|
+
备份 —— 所以每次 DSH 升级后无条件重跑都是安全的。
|
|
121
|
+
它先探测调用方自己的 profile 作用域,再看 CLI 安装目录,因此 nvm 换版本或安装软链重指都
|
|
122
|
+
不会让它失效;当机器上存在多份 DSH 安装时,**没有**被打补丁的那些会列在输出里 —— 因为
|
|
123
|
+
真正服务你面前界面的只有其中一份,需要时把那个路径作为参数显式传入。
|
|
114
124
|
|
|
115
125
|
**重装插件并不会恢复图标。**`dsh plugin --profile web add …` 只是把参数转发给
|
|
116
126
|
profile 目录里的 pnpm,只写 profile 自己的 `node_modules`;`-w`
|
|
@@ -180,9 +190,10 @@ DSH 的 bundle patch 是整体替换 `permission.config.presets`,而非逐 key
|
|
|
180
190
|
请重载 profile 让 `cordis.patch.yml` 重新生效,并确认没有更晚加载的插件覆盖了
|
|
181
191
|
`presets`。
|
|
182
192
|
|
|
183
|
-
|
|
193
|
+
**某个「自动审查」档位在输入区丢了图标。**
|
|
184
194
|
DSH 升级或重装替换了被打了补丁的宿主 bundle;重装插件不会把它带回来。执行
|
|
185
|
-
`npx dsh-perm-gate-patch-glyph`
|
|
195
|
+
`npx dsh-perm-gate-patch-glyph` 后重载即可。若报错说某个源键不存在,说明宿主又改了
|
|
196
|
+
图标表 —— 报错会列出表里现有的键,按提示改 `GLYPH_TARGETS` 即可。档位本身不受影响 ——
|
|
186
197
|
没有补丁,它的标签与门禁照常工作。
|
|
187
198
|
|
|
188
199
|
**规则文件存在但 `--list` 显示 `ruleCount: 0`。**
|
package/README.ja.md
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
> メソッドであるため、`typeof` プロブで読み取り、欠落時やエラー時には「ポリシー
|
|
46
46
|
> 不明」にフォールバックします。
|
|
47
47
|
|
|
48
|
-
バージョン **
|
|
48
|
+
バージョン **4.1.2** — 変更履歴は [日本語 changelog](./CHANGELOG.ja.md) を参照。
|
|
49
49
|
|
|
50
50
|
DeepSeek Harness 向けの、単一・自己完結・決定論優先・fail-closed な権限ゲートです。
|
|
51
51
|
|
|
@@ -93,6 +93,31 @@ DSH の安全まわりのエコシステムでは、この役割が `dsh-permiss
|
|
|
93
93
|
オンにすると、ゲートが許可した呼び出しを `callId` で正確に一致させてここで直接回答
|
|
94
94
|
します。
|
|
95
95
|
|
|
96
|
+
## スクリーンショット
|
|
97
|
+
|
|
98
|
+
**設定カード — 自动审查ティア**:フロントが露出するのは単一スイッチのみで、バックエンドの
|
|
99
|
+
4 戦略は組み合わせ可能です。`LlmAssist` はセッションの現在のモデルに追従するか、
|
|
100
|
+
Provider / Model を固定でき、ヘルスチェックと裁決学習を備えます:
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+

|
|
104
|
+
|
|
105
|
+
**ゲートが実際に読み込んだルール** — `dsh-perm-gate-rules` 設定名前空間から直接読み出した
|
|
106
|
+
読み取り専用ビューです。パネルからは変更できません:
|
|
107
|
+
|
|
108
|
+

|
|
109
|
+
|
|
110
|
+
**権限承認記録** — このセッションのすべての裁決を新しい順に、モデルが見た理由つきで表示します:
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
|
|
114
|
+
**裁決ストリップ** — 自動許可は根拠(読み取り専用ツール、または LLM の `safe` 判定)を明示し、
|
|
115
|
+
ゲートの停止も決して無言では起きません:
|
|
116
|
+
|
|
117
|
+

|
|
118
|
+

|
|
119
|
+

|
|
120
|
+
|
|
96
121
|
## インストール
|
|
97
122
|
|
|
98
123
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) が導入済みである
|
|
@@ -235,9 +260,10 @@ P0–P4 の全チェーンが動作します。ゲートはプリセットの**
|
|
|
235
260
|
`cordis.patch.yml` は全セッション共通で中国語ラベルを配布します。
|
|
236
261
|
|
|
237
262
|
**アイコンは別の話です。** 入力欄のグリフ表は閉じており、その表自身のコメントが規則を述べています:
|
|
238
|
-
*host-configured names outside the design set get none
|
|
239
|
-
|
|
240
|
-
`
|
|
263
|
+
*host-configured names outside the design set get none*。どちらのティアも組み込み値ではないため、
|
|
264
|
+
アイコンは `npx dsh-perm-gate-patch-glyph` がその表に 2 項目を足すことで初めて付きます ——
|
|
265
|
+
「自动审查」は `workspace-write` の盾+鉛筆を、「自动审查(高权限)」は `danger-full-access` の
|
|
266
|
+
盾+感嘆符をコピーし、それぞれが実際に共有するファイルサンドボックスのグリフになります。このパッチは
|
|
241
267
|
**ホスト**パッケージを書き換えるため、DSH アップグレードのたびに失われます ——
|
|
242
268
|
[DSH アップグレード後:入力欄アイコンのパッチを再適用](./INSTALL.ja.md) を参照してください。
|
|
243
269
|
|
|
@@ -342,9 +368,10 @@ Full access / `custom`)では、ゲートの判定フローは**一切実行
|
|
|
342
368
|
人手に回した `ask` は回答が返るまで追跡されます。受動的な `approval/request` オブザーバが閉じた結果を記録し(`allowed-once` → **承認**、`rejected` → **拒否**、`cancelled` → **キャンセル**、`unavailable` → 拒否=承認チャネルなし)、オブザーバが関連付けできない場合(`callId` 欠落・approval サービスなし・上流リスナーの短絡)は `tools/result` が同じ ask をフォールバックとして解決します。承認時には承認後の学習進捗(`n`/しきい値)を表示し、通知バーも 3 つの終態にラベルを付けます。
|
|
343
369
|
|
|
344
370
|
さらにプリセットの**拒否キーワード黑名単**(dsh-approval-gate の `DEFAULT_DENY_KEYWORDS` を継承:`rm -rf`、`push --force`、`drop table`、`mkfs`、`git reset --hard`、`docker system prune` など)を備え、テキストがキーワードを含む呼び出し(大小文字を区別しない部分一致)は許可リスト / 許可 / LLM より先に拒否します。設定カードでリストとして編集でき(プリセット項目にはタグ付き、ワンクリック復元対応)、未設定・空ならプリセットを適用します——黑名単が静かに無効化されることはありません。
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
371
|
+
「自动审查」と「自动审查(高权限)」はどちらもピッカーにアイコンを描きます —— インストール
|
|
372
|
+
ガイドに書かれたホストパッチが、それぞれが共有するファイルサンドボックスのティア
|
|
373
|
+
(`workspace-write` と `danger-full-access`)からコピーしたものです。そのパッチが
|
|
374
|
+
ない場合、両ティアはどのサーフェスでもテキストのみになります。ラベルとゲートは影響を
|
|
348
375
|
受けません。
|
|
349
376
|
|
|
350
377
|
|
package/README.ko.md
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
> 모두에서 user-approval 서비스의 **개인** 메소드이므로 `typeof` 프로브를 통해
|
|
44
44
|
> 읽으며, 누락되거나 예외 발생 시 "정책_unknown"으로 후퇴합니다.
|
|
45
45
|
|
|
46
|
-
버전 **
|
|
46
|
+
버전 **4.1.2** — 변경 내역은 [한국어 changelog](./CHANGELOG.ko.md)를 참고하세요.
|
|
47
47
|
|
|
48
48
|
DeepSeek Harness용 단일·자족적·결정론 우선·fail-closed 권한 게이트입니다.
|
|
49
49
|
|
|
@@ -90,6 +90,31 @@ DSH 안전 생태계에서는 이 역할이 `dsh-permission-rules` / `dsh-auto-m
|
|
|
90
90
|
없고, 게이트가 자동 허용한 호출에도 확인 프롬프트가 나타납니다. 이 전략을 켜면 게이트가
|
|
91
91
|
`callId`로 이미 허용한 호출을 정확히 매칭하여 여기서 직접 응답합니다.
|
|
92
92
|
|
|
93
|
+
## 스크린샷
|
|
94
|
+
|
|
95
|
+
**설정 카드 — 自动审查 티어**: 프론트는 단일 스위치만 노출하고, 백엔드의 네 전략은 조합
|
|
96
|
+
가능합니다. `LlmAssist`는 세션의 현재 모델을 따르거나 Provider / Model을 고정할 수 있으며,
|
|
97
|
+
헬스 테스트와 판정 학습을 갖춥니다:
|
|
98
|
+
|
|
99
|
+

|
|
100
|
+

|
|
101
|
+
|
|
102
|
+
**게이트가 실제로 로드한 규칙** — `dsh-perm-gate-rules` 설정 네임스페이스에서 직접 읽은
|
|
103
|
+
읽기 전용 뷰입니다. 패널에서는 바꿀 수 없습니다:
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
**권한 승인 기록** — 이 세션의 모든 판정을 최신순으로, 모델이 본 이유와 함께 표시합니다:
|
|
108
|
+
|
|
109
|
+

|
|
110
|
+
|
|
111
|
+
**판정 스트립** — 자동 허용은 근거(읽기 전용 도구 또는 LLM의 `safe` 판정)를 밝히고,
|
|
112
|
+
게이트 정지도 결코 조용히 일어나지 않습니다:
|
|
113
|
+
|
|
114
|
+

|
|
115
|
+

|
|
116
|
+

|
|
117
|
+
|
|
93
118
|
## 설치
|
|
94
119
|
|
|
95
120
|
[DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)가 설치되어 있어야
|
|
@@ -230,9 +255,10 @@ DSH **자신**의 트래픽 —— 내장 네트워크 도구와 LLM 전송 —
|
|
|
230
255
|
라벨을 배포합니다.
|
|
231
256
|
|
|
232
257
|
**아이콘은 다른 이야기입니다.** 입력창의 글리프 표는 닫혀 있고, 그 표 자체의 주석이 규칙을
|
|
233
|
-
말합니다: *host-configured names outside the design set get none*.
|
|
234
|
-
|
|
235
|
-
|
|
258
|
+
말합니다: *host-configured names outside the design set get none*. 두 티어 모두 내장 값이 아니므로
|
|
259
|
+
아이콘은 `npx dsh-perm-gate-patch-glyph`가 그 표에 항목 두 개를 넣어야 생깁니다 ——
|
|
260
|
+
「自动审查」는 `workspace-write`의 방패+연필을, 「自动审查(高权限)」는 `danger-full-access`의
|
|
261
|
+
방패+느낌표를 복사해 각자가 실제로 공유하는 파일 샌드박스의 글리프를 갖습니다.
|
|
236
262
|
이 패치는 **호스트** 패키지를 고치므로 DSH 업그레이드마다 사라집니다 ——
|
|
237
263
|
[DSH 업그레이드 후: 입력창 아이콘 패치 재적용](./INSTALL.ko.md)을 참고하세요.
|
|
238
264
|
|
|
@@ -343,9 +369,10 @@ host는 네임스페이스를 live로 읽으므로 변경은 재시작 없이
|
|
|
343
369
|
카드에서 목록으로 편집할 수 있고(프리셋 항목에는 태그 표시, 원클릭 복원 지원) 미설정·빈 목록 시 프리셋이
|
|
344
370
|
적용됩니다——블랙리스트가 조용히 꺼지지 않습니다.
|
|
345
371
|
블랙리스트가 조용히 꺼지는 일은 없습니다.
|
|
346
|
-
「自动审查」와 「自动审查(高权限)」는 둘 다 선택기에
|
|
347
|
-
|
|
348
|
-
|
|
372
|
+
「自动审查」와 「自动审查(高权限)」는 둘 다 선택기에 아이콘을 그립니다 — 설치 안내에 적힌
|
|
373
|
+
호스트 패치가 각자가 공유하는 파일 샌드박스 티어(`workspace-write`와 `danger-full-access`)에서
|
|
374
|
+
복사한 것입니다. 그 패치가 없으면 두 티어 모두 모든 화면에서 텍스트만 표시됩니다.
|
|
375
|
+
라벨과 게이트는 영향을 받지 않습니다.
|
|
349
376
|
|
|
350
377
|
|
|
351
378
|
## CLI(독립 실행 dry-run)
|
package/README.md
CHANGED
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
> **private** method of the user-approval service on both, so it is read behind a
|
|
43
43
|
> `typeof` probe and degrades to “policy unknown” when absent or throwing.
|
|
44
44
|
|
|
45
|
-
Version **
|
|
45
|
+
Version **4.1.2** — see the [Changelog](./CHANGELOG.md).
|
|
46
46
|
|
|
47
47
|
A single, self-sufficient, deterministic-first, fail-closed permission gate for DeepSeek Harness.
|
|
48
48
|
|
|
@@ -104,6 +104,31 @@ grants and LLM. It is editable as a list in the settings card (preset entries ar
|
|
|
104
104
|
one-click restore brings the preset back); unset or empty applies the preset — the blacklist
|
|
105
105
|
never silently turns off.
|
|
106
106
|
|
|
107
|
+
## Screenshots
|
|
108
|
+
|
|
109
|
+
**The settings card — the 自动审查 tier.** The front end exposes a single switch; the four back-end
|
|
110
|
+
strategies combine. `LlmAssist` can follow the session's current model or be pinned to a provider /
|
|
111
|
+
model, and carries its own health test and verdict learning:
|
|
112
|
+
|
|
113
|
+

|
|
114
|
+

|
|
115
|
+
|
|
116
|
+
**The rules the gate actually loaded**, read straight from the `dsh-perm-gate-rules` settings
|
|
117
|
+
namespace — the panel is read-only by design, so it can never edit what it shows:
|
|
118
|
+
|
|
119
|
+

|
|
120
|
+
|
|
121
|
+
**Approval decision log** — every verdict in this session, newest first, with the reason the model saw:
|
|
122
|
+
|
|
123
|
+

|
|
124
|
+
|
|
125
|
+
**Decision strips** — an auto-allow states its basis (a read-only tool, or an LLM `safe` verdict),
|
|
126
|
+
and a stand-down is never silent:
|
|
127
|
+
|
|
128
|
+

|
|
129
|
+

|
|
130
|
+

|
|
131
|
+
|
|
107
132
|
## Install
|
|
108
133
|
|
|
109
134
|
Requires an existing [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) installation.
|
|
@@ -264,9 +289,10 @@ default row and the composer picker) and only supplies its own localized labels
|
|
|
264
289
|
built-in values, so `cordis.patch.yml` ships the Chinese label for every session.
|
|
265
290
|
|
|
266
291
|
The **icon** is a different story. The composer's glyph map is closed, and its own comment states
|
|
267
|
-
the rule: *host-configured names outside the design set get none.*
|
|
268
|
-
|
|
269
|
-
|
|
292
|
+
the rule: *host-configured names outside the design set get none.* Neither tier is a built-in value,
|
|
293
|
+
so both get an icon only because `npx dsh-perm-gate-patch-glyph` adds them to that map — 自动审查
|
|
294
|
+
copies the `workspace-write` glyph, 自动审查(高权限) the `danger-full-access` one, so each tier
|
|
295
|
+
shows the glyph of the file sandbox it actually shares. That patch edits a **host**
|
|
270
296
|
package, so it is lost on every DSH upgrade — see
|
|
271
297
|
[After a DSH upgrade](./INSTALL.md#after-a-dsh-upgrade-re-apply-the-composer-glyph-patch).
|
|
272
298
|
|
|
@@ -374,9 +400,10 @@ channel existed), with `tools/result` settling the same ask as a fallback when t
|
|
|
374
400
|
correlate it. Approvals report the post-approval learning progress (`n`/threshold), and the notice
|
|
375
401
|
strip labels all three terminal states.
|
|
376
402
|
|
|
377
|
-
自动审查 and 自动审查(高权限) both draw
|
|
378
|
-
|
|
379
|
-
|
|
403
|
+
自动审查 and 自动审查(高权限) both draw a glyph in the picker — copied from the built-in tiers whose
|
|
404
|
+
file sandbox they share (`workspace-write` and `danger-full-access`) by the host patch the
|
|
405
|
+
installation guide describes. Without that patch both tiers are text-only on every surface; their
|
|
406
|
+
labels and their gating are unaffected.
|
|
380
407
|
|
|
381
408
|
### A selectable session tier
|
|
382
409
|
|
package/README.zh.md
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
> 线上都是 user-approval 服务的**私有**方法,因此通过 `typeof` 探测读取,缺失
|
|
40
40
|
> 或抛错时降级为「策略未知」。
|
|
41
41
|
|
|
42
|
-
版本 **
|
|
42
|
+
版本 **4.1.2** —— 变更见 [Changelog](./CHANGELOG.md)。
|
|
43
43
|
|
|
44
44
|
一个**单一自足、确定性优先、fail-closed** 的 DeepSeek Harness 权限门插件。
|
|
45
45
|
|
|
@@ -65,6 +65,27 @@
|
|
|
65
65
|
- **自动审查档位**(机器值 `permissive`)——一个**独立审批模式**(区别于只读、完全权限与白名单档),既不是"自动审批",也不授予泛化权限。前端只暴露**一个开关**(`permissive`),后台四个审批策略**可组合**、由插件设置决定——仍对 P0 保持 fail-closed。权限下拉框与设置行都按产品名「自动审查」显示;图标见下文(内置档自带,插件档需补丁)。
|
|
66
66
|
- **沙箱提权自动答复**(`trustEscalation`)— 沙箱提权是从 shell / pwsh / edit 工具**体内部**(`tools/pre-execute` 之后)发出的,所以门禁从未见过它,一个它自动放行的调用仍会弹出确认。开启后,门禁以 `callId` 精确匹配已放行调用并直接答复。
|
|
67
67
|
|
|
68
|
+
## 界面预览
|
|
69
|
+
|
|
70
|
+
**设置卡片 —— 自动审查档位**:前端只暴露一个开关,后台四个策略可组合;`LlmAssist` 可跟随会话当前模型,也可钉住 Provider / Model,并自带健康测试与裁决学习:
|
|
71
|
+
|
|
72
|
+

|
|
73
|
+

|
|
74
|
+
|
|
75
|
+
**门禁实际加载的规则**——直接读自 `dsh-perm-gate-rules` 设置命名空间;面板按设计只读,改不了它显示的内容:
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
**权限审批记录**——本会话每一次裁决,最新在上,附带模型看到的理由:
|
|
80
|
+
|
|
81
|
+

|
|
82
|
+
|
|
83
|
+
**裁决条**——自动放行会写明依据(只读工具,或 LLM 判定 `safe`);停摆也绝不静默:
|
|
84
|
+
|
|
85
|
+

|
|
86
|
+

|
|
87
|
+

|
|
88
|
+
|
|
68
89
|
## 安装
|
|
69
90
|
|
|
70
91
|
需要先安装 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)。
|
|
@@ -180,9 +201,10 @@ MSYS2/Cygwin 的 `sh.exe`、ConPTY 都会以 `Win32 error 5` / `couldn't create
|
|
|
180
201
|
标签,因此 `cordis.patch.yml` 直接写中文名,对所有会话一致。
|
|
181
202
|
|
|
182
203
|
**图标是另一回事。** 输入栏的图标表是闭合的,表自己的注释写明了规则:*host-configured names
|
|
183
|
-
outside the design set get none
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
outside the design set get none*。两个档位都不是内置值,图标全靠
|
|
205
|
+
`npx dsh-perm-gate-patch-glyph` 往那张表里补两项 ——「自动审查」复制 `workspace-write` 的
|
|
206
|
+
盾+铅笔,「自动审查(高权限)」复制 `danger-full-access` 的盾+感叹号,各自对应自己实际共用的
|
|
207
|
+
文件沙箱。该补丁改的是**宿主**包,每次 DSH 升级都会丢 —— 见
|
|
186
208
|
[DSH 升级后:重打输入区图标补丁](./INSTALL.zh.md)。
|
|
187
209
|
|
|
188
210
|
`cordis.yml`:
|
|
@@ -275,9 +297,9 @@ LLM 评定为 `safe` 且门禁自动放行的调用因此仍会弹出确认。
|
|
|
275
297
|
调用文本命中任一关键词(大小写不敏感子串)即直接拒绝,且先于白名单 / 授权 / LLM。黑名单在设置
|
|
276
298
|
卡片中按列表查看与增删(预置条目带标签,可一键恢复预置);未设置或为空时应用预置列表——黑名单
|
|
277
299
|
不会静默关闭。
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
「自动审查」与「自动审查(高权限)」在选择器里都画图标 —— 图标由安装指南里描述的那次宿主补丁
|
|
301
|
+
从各自共用的文件沙箱档位复制而来(`workspace-write` 与 `danger-full-access`)。没有该补丁时,
|
|
302
|
+
两个档位在所有界面上都是纯文字;它们的标签与门禁不受影响。
|
|
281
303
|
|
|
282
304
|
|
|
283
305
|
## CLI(独立 dry-run)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/assets/llms.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/cordis.patch.yml
CHANGED
|
@@ -17,14 +17,17 @@
|
|
|
17
17
|
# The composer's permission picker draws an icon per preset from a CLOSED map
|
|
18
18
|
# inside @deepseek-ai/dsh-client-ui-conversation (`permissionGlyphs`), whose
|
|
19
19
|
# source states the rule: "host-configured names outside the design set get
|
|
20
|
-
# none".
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
20
|
+
# none". Neither tier is a built-in value, so DSH renders no glyph for either
|
|
21
|
+
# — the map is a closed closure variable and its only plugin-facing input is
|
|
22
|
+
# {value, name, description} on the option objects, which carries no icon. The
|
|
23
|
+
# only way to give a tier an icon is to add an entry to that map.
|
|
24
|
+
# `scripts/patch-permission-glyph.mjs` does exactly that, copying the glyph of
|
|
25
|
+
# the built-in tier whose file sandbox each of ours shares:
|
|
26
|
+
# permissive <- ["workspace-write", (same sandbox: workspace-write)
|
|
27
|
+
# permissive-full <- [FULL_ACCESS, (same sandbox: danger-full-access)
|
|
28
|
+
# The source key is RESOLVED, not assumed (0.1.2 keyed a `permissive` glyph that
|
|
29
|
+
# 0.1.5 dropped, so a literal anchor died with "anchor not found"). It patches a
|
|
30
|
+
# HOST package, so it must be re-run after any DSH upgrade or reinstall.
|
|
28
31
|
#
|
|
29
32
|
# The DSH bundle patch REPLACES the whole `permission.config.presets` map (it
|
|
30
33
|
# does not merge per key), so this file MUST restate every tier that should
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-perm-gate",
|
|
3
3
|
"name": "dsh-perm-gate",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.1.2",
|
|
5
5
|
"description": {
|
|
6
6
|
"en": "Deterministic-first, fail-closed permission gate for DeepSeek Harness 0.1.2+: a P0 hard-deny -> P1 precise session grant -> P2 static allow/deny rule chain -> P3 optional LLM classifier -> P4 ask pipeline over the tools/pre-execute waterfall, with a standalone CLI dry-run evaluator and an auditable decision log. It only vetoes with deny/ask and always delegates otherwise, and unknown or malformed rules fail loud instead of silently standing the gate down.",
|
|
7
7
|
"zh": "面向 DeepSeek Harness 0.1.2+ 的确定性优先、失败即拒的权限闸门:在 tools/pre-execute 瀑布上串起 P0 硬拒绝 → P1 精确会话授权 → P2 静态 allow/deny 规则链 → P3 可选 LLM 语义分类 → P4 ask,并附带独立 CLI 干跑评估器与可审计的决策日志。只以 deny/ask 否决、其余一律放行,规则未知或非法时直接报错,绝不静默失效。"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"engines": {
|
|
16
|
-
"dsh": ">=0.1.
|
|
16
|
+
"dsh": ">=0.1.5-rc.1 <0.2.0-0"
|
|
17
17
|
},
|
|
18
18
|
"components": {
|
|
19
19
|
"host": "lib/index.js",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-perm-gate",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org",
|
|
6
6
|
"access": "public",
|
|
@@ -70,7 +70,17 @@
|
|
|
70
70
|
"dsh": ">=0.1.5-rc.1 <0.2.0-0"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
|
-
"@deepseek-ai/cordis": "^4.0.1"
|
|
73
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
74
|
+
"@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.1 <0.2.0-0",
|
|
75
|
+
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.1 <0.2.0-0",
|
|
76
|
+
"@deepseek-ai/dsh-client-ui-renderer": ">=0.1.5-rc.1 <0.2.0-0",
|
|
77
|
+
"@deepseek-ai/dsh-client-ui-conversation": ">=0.1.5-rc.1 <0.2.0-0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependenciesMeta": {
|
|
80
|
+
"@deepseek-ai/dsh-client-locale": { "optional": true },
|
|
81
|
+
"@deepseek-ai/dsh-client-ui-settings": { "optional": true },
|
|
82
|
+
"@deepseek-ai/dsh-client-ui-renderer": { "optional": true },
|
|
83
|
+
"@deepseek-ai/dsh-client-ui-conversation": { "optional": true }
|
|
74
84
|
},
|
|
75
85
|
"dependencies": {
|
|
76
86
|
"@deepseek-ai/schemastery": "^3.18.0",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Give this plugin's own permission tiers the composer glyphs the host will not
|
|
4
|
+
* draw for them.
|
|
4
5
|
*
|
|
5
6
|
* WHY THIS EXISTS
|
|
6
7
|
* ---------------
|
|
@@ -9,22 +10,40 @@
|
|
|
9
10
|
*
|
|
10
11
|
* const permissionGlyphs = new Map([
|
|
11
12
|
* ["read-only", <svg …/>],
|
|
12
|
-
* ["permissive", <svg …/>], // the 自动审查 tier's shield+eye
|
|
13
13
|
* ["workspace-write", <svg …/>],
|
|
14
|
-
* [
|
|
14
|
+
* [FULL_ACCESS, <svg …/>], // const FULL_ACCESS = "danger-full-access"
|
|
15
15
|
* ]);
|
|
16
16
|
* function permissionGlyph(value) { return permissionGlyphs.get(value); }
|
|
17
17
|
*
|
|
18
18
|
* The source comment states the rule: "host-configured names outside the design
|
|
19
19
|
* set get none." There is no plugin-facing seam — the option objects the host
|
|
20
20
|
* supplies carry only `{value, name, description}`, and the map itself is a
|
|
21
|
-
* closure variable. So a plugin-contributed tier
|
|
22
|
-
*
|
|
23
|
-
* trigger, while the built-in `permissive` tier shows the shield+eye.
|
|
21
|
+
* closure variable. So a plugin-contributed tier renders with no icon in the
|
|
22
|
+
* dropdown and falls back to plain text in the trigger.
|
|
24
23
|
*
|
|
25
|
-
* The only way to give our
|
|
26
|
-
* This script does exactly that
|
|
27
|
-
*
|
|
24
|
+
* The only way to give our tiers the same glyph is to add entries to that map.
|
|
25
|
+
* This script does exactly that — one glyph per tier, copied from the built-in
|
|
26
|
+
* tier whose file sandbox the plugin tier shares:
|
|
27
|
+
*
|
|
28
|
+
* permissive sandbox: workspace-write <- the ["workspace-write",] entry
|
|
29
|
+
* permissive-full sandbox: danger-full-access <- the [FULL_ACCESS,] entry
|
|
30
|
+
*
|
|
31
|
+
* WHY THE SOURCES ARE DECLARED AND THE ANCHOR IS RESOLVED
|
|
32
|
+
* -------------------------------------------------------
|
|
33
|
+
* DSH 0.1.2 shipped four glyphs, `permissive` among them; 0.1.5-rc.2 ships three
|
|
34
|
+
* (`read-only` / `workspace-write` / `danger-full-access`) and the string
|
|
35
|
+
* `permissive` no longer occurs anywhere in the bundle — so a splice anchored on
|
|
36
|
+
* `["permissive",` dies with "anchor not found" on the current host. Two
|
|
37
|
+
* properties keep this working across such a design-set change:
|
|
38
|
+
*
|
|
39
|
+
* 1. the source key is a declaration (`GLYPH_TARGETS`) rather than a literal
|
|
40
|
+
* buried in the splice, so re-pointing a tier is a one-line edit; and
|
|
41
|
+
* 2. the anchor for a key is RESOLVED, not assumed to be a string literal:
|
|
42
|
+
* `["<key>",` first, then the constant alias the bundle may use instead
|
|
43
|
+
* (`[FULL_ACCESS,` for `"danger-full-access"`).
|
|
44
|
+
*
|
|
45
|
+
* When a declared source key is gone, the refusal names the keys the map DOES
|
|
46
|
+
* carry, so the next drift is diagnosed from the error text alone.
|
|
28
47
|
*
|
|
29
48
|
* VOLUNTARY, NOT AUTOMATIC
|
|
30
49
|
* ------------------------
|
|
@@ -42,8 +61,11 @@
|
|
|
42
61
|
* npx dsh-perm-gate-patch-glyph --check # report only; exit 1 if missing
|
|
43
62
|
* node scripts/patch-permission-glyph.mjs <path/to/client.js>
|
|
44
63
|
*
|
|
45
|
-
* With no argument it probes the
|
|
46
|
-
*
|
|
64
|
+
* With no argument it probes the profile scopes first and the CLI install second,
|
|
65
|
+
* and lists any other DSH install that carries the same bundle — on a host with
|
|
66
|
+
* both, only one of them serves the UI, so the explicit-path form is the answer
|
|
67
|
+
* when the probe picks the wrong one. The script is idempotent (a second run is a
|
|
68
|
+
* no-op), backs the file up once, applies every target or none, and refuses to
|
|
47
69
|
* write a bundle it cannot slice correctly, so it can never half-apply.
|
|
48
70
|
*/
|
|
49
71
|
import { existsSync, readFileSync, writeFileSync, copyFileSync } from 'node:fs'
|
|
@@ -52,13 +74,23 @@ import { homedir } from 'node:os'
|
|
|
52
74
|
import { dirname, join, resolve } from 'node:path'
|
|
53
75
|
import { pathToFileURL } from 'node:url'
|
|
54
76
|
|
|
55
|
-
/** The map key whose entry is cloned, and the key it is cloned onto. */
|
|
56
|
-
export const SOURCE_KEY = 'permissive'
|
|
57
|
-
export const TARGET_KEY = 'permissive-full'
|
|
58
|
-
|
|
59
77
|
/** Relative path from a package root to the bundle holding the map. */
|
|
60
78
|
const BUNDLE = join('@deepseek-ai', 'dsh-client-ui-conversation', 'lib', 'client.js')
|
|
61
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The glyphs to add: each plugin tier, and the built-in tier it copies.
|
|
82
|
+
*
|
|
83
|
+
* The pairing is by file-sandbox meaning, not by name similarity — `permissive`
|
|
84
|
+
* runs in the same sandbox as `workspace-write`, `permissive-full` in the same
|
|
85
|
+
* sandbox as `danger-full-access` — so the icon a user sees matches the access
|
|
86
|
+
* the tier actually grants. A tier listed here gets that built-in's entry
|
|
87
|
+
* verbatim apart from its key.
|
|
88
|
+
*/
|
|
89
|
+
export const GLYPH_TARGETS = [
|
|
90
|
+
{ target: 'permissive', source: 'workspace-write' },
|
|
91
|
+
{ target: 'permissive-full', source: 'danger-full-access' },
|
|
92
|
+
]
|
|
93
|
+
|
|
62
94
|
/** Thrown for every refusal; the CLI turns it into an exit code 1. */
|
|
63
95
|
export class GlyphPatchError extends Error {}
|
|
64
96
|
|
|
@@ -66,6 +98,15 @@ export class GlyphPatchError extends Error {}
|
|
|
66
98
|
* Candidate bundle paths, most specific first. Pure: no filesystem access, so
|
|
67
99
|
* the probe order is unit-testable.
|
|
68
100
|
*
|
|
101
|
+
* Profile scope before CLI-install scope, and that order is load-bearing on a
|
|
102
|
+
* machine with more than one DSH: a profile's UI is served by the profile's own
|
|
103
|
+
* dependency scope, not by whichever global CLI install happens to sit beside the
|
|
104
|
+
* running `node`. Measured on this project's own host — a global install under
|
|
105
|
+
* `dirname(node)` existed and shadowed the profile's bundle under an older probe
|
|
106
|
+
* order, so the patch reported success while the picker stayed icon-free. On a
|
|
107
|
+
* stock single-install machine every candidate is one inode, so the order decides
|
|
108
|
+
* nothing there; it only matters exactly when guessing wrong is silent.
|
|
109
|
+
*
|
|
69
110
|
* `execDir` is `dirname(process.execPath)`. Deriving from the running node
|
|
70
111
|
* binary — never from a hardcoded nvm version or install path — is what keeps
|
|
71
112
|
* the script working across node upgrades and re-pointed install symlinks.
|
|
@@ -75,19 +116,111 @@ export class GlyphPatchError extends Error {}
|
|
|
75
116
|
*/
|
|
76
117
|
export function candidateBundles({ execDir, cwd, dshHome }) {
|
|
77
118
|
return [
|
|
78
|
-
// 1.
|
|
79
|
-
join(execDir, 'node_modules', BUNDLE),
|
|
80
|
-
// 2. The same scope nested inside the `dsh` package itself.
|
|
81
|
-
join(execDir, 'node_modules', '@deepseek-ai', 'dsh', 'node_modules', BUNDLE),
|
|
82
|
-
// 3. Whatever profile the caller is standing in.
|
|
119
|
+
// 1. Whatever profile the caller is standing in.
|
|
83
120
|
join(cwd, 'node_modules', BUNDLE),
|
|
84
|
-
//
|
|
121
|
+
// 2. The hoisted scope every profile under DSH_HOME shares.
|
|
85
122
|
join(dshHome, 'profiles', 'node_modules', BUNDLE),
|
|
123
|
+
// 3. `@deepseek-ai` scope hoisted beside the running node binary.
|
|
124
|
+
join(execDir, 'node_modules', BUNDLE),
|
|
125
|
+
// 4. The same scope nested inside the `dsh` package itself.
|
|
126
|
+
join(execDir, 'node_modules', '@deepseek-ai', 'dsh', 'node_modules', BUNDLE),
|
|
86
127
|
]
|
|
87
128
|
}
|
|
88
129
|
|
|
89
130
|
/**
|
|
90
|
-
*
|
|
131
|
+
* In-page string constants (`const FULL_ACCESS = "danger-full-access"`), as
|
|
132
|
+
* name -> value. First declaration wins, so a later reassignment cannot shadow
|
|
133
|
+
* the spelling the map was built with above it.
|
|
134
|
+
*
|
|
135
|
+
* The bundle may key a map entry by such a name rather than by a literal, which
|
|
136
|
+
* is the whole reason the anchor is resolved instead of concatenated.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} source
|
|
139
|
+
* @returns {Map<string, string>} constant name -> its string value
|
|
140
|
+
*/
|
|
141
|
+
export function pageConstants(source) {
|
|
142
|
+
const constants = new Map()
|
|
143
|
+
for (const match of source.matchAll(/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*"([^"]*)"/g)) {
|
|
144
|
+
if (!constants.has(match[1])) constants.set(match[1], match[2])
|
|
145
|
+
}
|
|
146
|
+
return constants
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The `permissionGlyphs` map's text, brackets included.
|
|
151
|
+
*
|
|
152
|
+
* Parsed rather than assumed: the map is a `new Map([ … ])` closure variable, so
|
|
153
|
+
* its extent is found by bracket depth from its `[`. Reading the map (instead of
|
|
154
|
+
* only probing for one anchor inside it) is what lets a refusal report the keys
|
|
155
|
+
* the host actually renders.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} source
|
|
158
|
+
* @returns {string} the map array text, from `[` to the matching `]`
|
|
159
|
+
*/
|
|
160
|
+
export function glyphMapText(source) {
|
|
161
|
+
const start = source.search(/permissionGlyphs\s*=\s*new Map\(\s*\[/)
|
|
162
|
+
if (start === -1) throw new GlyphPatchError('permissionGlyphs map not found in this bundle')
|
|
163
|
+
const open = source.indexOf('[', start)
|
|
164
|
+
let depth = 0
|
|
165
|
+
for (let i = open; i < source.length; i += 1) {
|
|
166
|
+
const ch = source[i]
|
|
167
|
+
if (ch === '[' || ch === '(' || ch === '{') depth += 1
|
|
168
|
+
else if (ch === ']' || ch === ')' || ch === '}') {
|
|
169
|
+
depth -= 1
|
|
170
|
+
if (depth === 0) return source.slice(open, i + 1)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new GlyphPatchError('unterminated permissionGlyphs map')
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The preset values the map renders a glyph for — literals as-is, and entries
|
|
178
|
+
* keyed by an in-page constant resolved to that constant's value.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} source
|
|
181
|
+
* @returns {string[]} e.g. `['read-only', 'workspace-write', 'danger-full-access']`
|
|
182
|
+
*/
|
|
183
|
+
export function glyphKeys(source) {
|
|
184
|
+
const constants = pageConstants(source)
|
|
185
|
+
const keys = []
|
|
186
|
+
for (const match of glyphMapText(source).matchAll(/\[\s*(?:"([^"]+)"|([A-Za-z_$][\w$]*))\s*,/g)) {
|
|
187
|
+
if (match[1] !== undefined) keys.push(match[1])
|
|
188
|
+
else keys.push(constants.get(match[2]) ?? match[2])
|
|
189
|
+
}
|
|
190
|
+
return keys
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Resolve the anchor text of one map entry, literal first, constant alias second.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} source the bundle's text
|
|
197
|
+
* @param {string} key the preset value, e.g. `danger-full-access`
|
|
198
|
+
* @returns {{ anchor: string, index: number }}
|
|
199
|
+
* @throws {GlyphPatchError} when the host renders no glyph for `key`
|
|
200
|
+
*/
|
|
201
|
+
export function resolveAnchor(source, key) {
|
|
202
|
+
for (const anchor of [`["${key}",`, `['${key}',`]) {
|
|
203
|
+
const index = source.indexOf(anchor)
|
|
204
|
+
if (index !== -1) return { anchor, index }
|
|
205
|
+
}
|
|
206
|
+
const name = [...pageConstants(source)].find(([, value]) => value === key)?.[0]
|
|
207
|
+
if (name !== undefined) {
|
|
208
|
+
const anchor = `[${name},`
|
|
209
|
+
const index = source.indexOf(anchor)
|
|
210
|
+
if (index !== -1) return { anchor, index }
|
|
211
|
+
throw new GlyphPatchError(
|
|
212
|
+
`anchor not found: ${anchor} (the alias for "${key}")`,
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
throw new GlyphPatchError(
|
|
216
|
+
`this host renders no glyph for "${key}" — the map carries: ` +
|
|
217
|
+
`${glyphKeys(source).map((k) => `"${k}"`).join(', ')}\n` +
|
|
218
|
+
' Re-point GLYPH_TARGETS at a key the map does carry.',
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract the full `["<key>", …]` (or `[CONST, …]`) map entry, by bracket depth.
|
|
91
224
|
*
|
|
92
225
|
* The scan starts ON the entry's opening `[` (the one immediately before the
|
|
93
226
|
* key), so depth returns to zero exactly at that entry's closing `]`. Starting
|
|
@@ -98,12 +231,12 @@ export function candidateBundles({ execDir, cwd, dshHome }) {
|
|
|
98
231
|
* contain no brackets.
|
|
99
232
|
*
|
|
100
233
|
* @param {string} source
|
|
101
|
-
* @param {string}
|
|
234
|
+
* @param {string} anchor the text returned by {@link resolveAnchor}
|
|
102
235
|
* @returns {string} the entry, brackets included
|
|
103
236
|
*/
|
|
104
|
-
export function sliceEntry(source,
|
|
105
|
-
const start = source.indexOf(
|
|
106
|
-
if (start === -1) throw new GlyphPatchError(`anchor not found:
|
|
237
|
+
export function sliceEntry(source, anchor) {
|
|
238
|
+
const start = source.indexOf(anchor)
|
|
239
|
+
if (start === -1) throw new GlyphPatchError(`anchor not found: ${anchor}`)
|
|
107
240
|
let depth = 0
|
|
108
241
|
for (let i = start; i < source.length; i += 1) {
|
|
109
242
|
const ch = source[i]
|
|
@@ -113,24 +246,41 @@ export function sliceEntry(source, key) {
|
|
|
113
246
|
if (depth === 0) return source.slice(start, i + 1)
|
|
114
247
|
}
|
|
115
248
|
}
|
|
116
|
-
throw new GlyphPatchError(`unterminated entry for
|
|
249
|
+
throw new GlyphPatchError(`unterminated entry for ${anchor}`)
|
|
117
250
|
}
|
|
118
251
|
|
|
119
252
|
/**
|
|
120
|
-
*
|
|
253
|
+
* Splice every declared glyph into the map.
|
|
254
|
+
*
|
|
255
|
+
* All or nothing: a source key this host does not carry throws before the caller
|
|
256
|
+
* can write anything, so a partially-glyphed bundle is impossible. Idempotent
|
|
257
|
+
* per target — a target already present is left untouched and reported as such.
|
|
121
258
|
*
|
|
122
259
|
* @param {string} source the bundle's text
|
|
123
|
-
* @returns {{ changed: boolean, source: string,
|
|
124
|
-
* `changed: false` means the target key was already present (idempotent no-op).
|
|
260
|
+
* @returns {{ changed: boolean, source: string, added: string[], already: string[] }}
|
|
125
261
|
*/
|
|
126
262
|
export function applyGlyphPatch(source) {
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
263
|
+
let next = source
|
|
264
|
+
const added = []
|
|
265
|
+
const already = []
|
|
266
|
+
|
|
267
|
+
for (const { target, source: sourceKey } of GLYPH_TARGETS) {
|
|
268
|
+
if (next.includes(`["${target}",`)) {
|
|
269
|
+
already.push(target)
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
const { anchor } = resolveAnchor(next, sourceKey)
|
|
273
|
+
const sourceEntry = sliceEntry(next, anchor)
|
|
274
|
+
if (!sourceEntry.includes('svg')) {
|
|
275
|
+
throw new GlyphPatchError(`the ${anchor} entry does not look like a glyph`)
|
|
276
|
+
}
|
|
277
|
+
const clonedEntry = sourceEntry.replace(anchor, `["${target}",`)
|
|
278
|
+
// A function replacement: the entry is spliced verbatim, `$` never expanded.
|
|
279
|
+
next = next.replace(sourceEntry, () => `${sourceEntry},\n\t\t\t${clonedEntry}`)
|
|
280
|
+
added.push(target)
|
|
131
281
|
}
|
|
132
|
-
|
|
133
|
-
return { changed:
|
|
282
|
+
|
|
283
|
+
return { changed: added.length > 0, source: next, added, already }
|
|
134
284
|
}
|
|
135
285
|
|
|
136
286
|
/** Resolve the bundle to patch, or throw with an explanation. */
|
|
@@ -151,8 +301,29 @@ export function findBundle(argv, where) {
|
|
|
151
301
|
)
|
|
152
302
|
}
|
|
153
303
|
|
|
304
|
+
/**
|
|
305
|
+
* A warning block naming the other DSH installs that carry this bundle.
|
|
306
|
+
*
|
|
307
|
+
* Reported rather than patched: on a host with a global CLI install and a
|
|
308
|
+
* launcher-managed runtime, only one of them serves the UI in front of the user,
|
|
309
|
+
* and picking the wrong one is invisible — the patch succeeds and the picker
|
|
310
|
+
* stays icon-free. Naming the others turns that into a one-line fix (pass the
|
|
311
|
+
* path explicitly).
|
|
312
|
+
*
|
|
313
|
+
* @param {string[]} others bundle paths that exist but were not chosen
|
|
314
|
+
* @returns {string} an empty string when there is nothing to report
|
|
315
|
+
*/
|
|
316
|
+
function describeOtherInstalls(others) {
|
|
317
|
+
if (others.length === 0) return ''
|
|
318
|
+
return ` note: ${others.length} other DSH install(s) carry this bundle — NOT patched:\n` +
|
|
319
|
+
others.map((path) => ` ${path}\n`).join('') +
|
|
320
|
+
' If the tier still has no icon, the UI is served by one of those; re-run with\n' +
|
|
321
|
+
' that path as the argument.\n'
|
|
322
|
+
}
|
|
323
|
+
|
|
154
324
|
function main(argv) {
|
|
155
325
|
const check = argv.includes('--check')
|
|
326
|
+
const explicit = argv.slice(2).find((arg) => !arg.startsWith('-'))
|
|
156
327
|
const where = {
|
|
157
328
|
execDir: dirname(process.execPath),
|
|
158
329
|
cwd: process.cwd(),
|
|
@@ -160,17 +331,27 @@ function main(argv) {
|
|
|
160
331
|
}
|
|
161
332
|
|
|
162
333
|
const bundle = findBundle(argv, where)
|
|
334
|
+
const others = explicit === undefined
|
|
335
|
+
? candidateBundles(where).filter((candidate) => candidate !== bundle && existsSync(candidate))
|
|
336
|
+
: []
|
|
337
|
+
const otherNote = describeOtherInstalls(others)
|
|
163
338
|
const original = readFileSync(bundle, 'utf8')
|
|
164
|
-
const { changed, source: patched } = applyGlyphPatch(original)
|
|
339
|
+
const { changed, source: patched, added, already } = applyGlyphPatch(original)
|
|
165
340
|
|
|
166
341
|
if (!changed) {
|
|
167
|
-
process.stdout.write(
|
|
342
|
+
process.stdout.write(
|
|
343
|
+
`patch-permission-glyph: all glyphs already present in\n ${bundle}\n` +
|
|
344
|
+
` present: ${already.map((key) => `["${key}",]`).join(' ')}\n` +
|
|
345
|
+
otherNote,
|
|
346
|
+
)
|
|
168
347
|
return 0
|
|
169
348
|
}
|
|
170
349
|
if (check) {
|
|
171
350
|
process.stdout.write(
|
|
172
|
-
`patch-permission-glyph: ["${
|
|
173
|
-
|
|
351
|
+
`patch-permission-glyph: MISSING ${added.map((key) => `["${key}",]`).join(' ')} from\n ${bundle}\n` +
|
|
352
|
+
` present: ${already.length > 0 ? already.map((key) => `["${key}",]`).join(' ') : '(none)'}\n` +
|
|
353
|
+
' (--check: nothing written; re-run without --check to apply)\n' +
|
|
354
|
+
otherNote,
|
|
174
355
|
)
|
|
175
356
|
return 1
|
|
176
357
|
}
|
|
@@ -189,9 +370,11 @@ function main(argv) {
|
|
|
189
370
|
}
|
|
190
371
|
|
|
191
372
|
process.stdout.write(
|
|
192
|
-
`patch-permission-glyph: added ["${
|
|
373
|
+
`patch-permission-glyph: added ${added.map((key) => `["${key}",]`).join(' ')} to\n ${bundle}\n` +
|
|
374
|
+
already.map((key) => ` already present: ["${key}",]\n`).join('') +
|
|
193
375
|
` backup: ${backup}\n` +
|
|
194
|
-
' NOTE: this edits a DSH package — re-run after any DSH upgrade or reinstall.\n'
|
|
376
|
+
' NOTE: this edits a DSH package — re-run after any DSH upgrade or reinstall.\n' +
|
|
377
|
+
otherNote,
|
|
195
378
|
)
|
|
196
379
|
return 0
|
|
197
380
|
}
|