pi-permission-system 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +13 -17
- package/package.json +71 -71
- package/src/config-modal.ts +136 -156
- package/src/evaluate-permission.ts +58 -0
- package/src/extension-config.ts +156 -170
- package/src/index.ts +234 -72
- package/src/logging.ts +108 -123
- package/src/permanent-approval-store.ts +93 -0
- package/src/permission-dialog.ts +46 -14
- package/src/permission-forwarding.ts +17 -1
- package/src/permission-manager.ts +19 -0
- package/src/session-approval-store.ts +42 -0
- package/src/skill-prompt-sanitizer.ts +51 -5
- package/src/types-shims.d.ts +2 -2
- package/src/wildcard-matcher.ts +79 -77
- package/tests/config-modal.test.ts +32 -34
- package/tests/permission-system.test.ts +572 -80
- package/tests/session-permanent-permission.test.ts +796 -0
- package/tests/types-shims-coverage.test.ts +416 -0
- package/tests/wildcard-multiline.test.ts +562 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-05-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Added `hasAllowedSkills()` method to `PermissionManager` that checks whether the resolved permission config has any explicitly allowed skills, returning true when the default skills policy is not "deny" or at least one individual skill entry has state "allow".
|
|
14
|
+
- Added skill-scoped `read` tool exception so the `read` tool remains exposed to agents with explicitly allowed skills even when the `read` tool-level permission is "deny", enabling skill file access without granting unrestricted read access.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Widened Pi peer dependency ranges to `^0.74.0 || ^0.75.0` and bumped dev dependencies to `^0.75.5`.
|
|
18
|
+
|
|
10
19
|
## [0.5.0] - 2026-05-22
|
|
11
20
|
|
|
12
21
|
### Added
|
package/README.md
CHANGED
|
@@ -99,8 +99,7 @@ If you are coming from OpenCode, you usually do **not** need to rewrite your who
|
|
|
99
99
|
- **Per-Agent Overrides** — Agent-specific permission policies via YAML frontmatter
|
|
100
100
|
- **Subagent Permission Forwarding** — Forwards `ask` confirmations from non-UI subagents back to the main interactive session
|
|
101
101
|
- **Runtime YOLO Control** — Lets users toggle yolo mode from the settings modal and lets other extensions toggle it through the runtime API
|
|
102
|
-
- **File-Based
|
|
103
|
-
- **Optional Debug Logging** — Keeps verbose extension diagnostics in a separate file when enabled in `config.json`
|
|
102
|
+
- **File-Based Debug Logging** — Writes verbose diagnostics and permission request/denial review entries to one debug file when enabled in `config.json`, including the responsible agent and raw tool-call input
|
|
104
103
|
- **JSON Schema Validation** — Full schema for editor autocomplete and config validation
|
|
105
104
|
- **External Directory Guard** — Enforces `special.external_directory` for path-bearing file tools that target paths outside the active working directory
|
|
106
105
|
|
|
@@ -159,6 +158,8 @@ All permissions use one of three states:
|
|
|
159
158
|
| `deny` | Blocks the action with an error message |
|
|
160
159
|
| `ask` | Prompts the user for confirmation via UI |
|
|
161
160
|
|
|
161
|
+
When an `ask` permission prompts, the confirmation UI offers `Allow Once`, `Allow Always`, `Reject`, and `Reject with Reason`. `Allow Once` records an in-memory approval for the current runtime, `Allow Always` persists a matching approval rule for future sessions, and rejected decisions can include an optional reason shown back to the agent.
|
|
162
|
+
|
|
162
163
|
### Pi Integration Hooks
|
|
163
164
|
|
|
164
165
|
The extension integrates via Pi's lifecycle hooks:
|
|
@@ -175,7 +176,7 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
175
176
|
- Extension-provided tools like `task`, `mcp`, and third-party tools are handled through the same registered-tool permission layer instead of private built-in hardcodes
|
|
176
177
|
- When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
|
|
177
178
|
- Generic extension-tool approval prompts include a bounded input preview; built-in file tools use concise human-readable summaries instead of raw multiline JSON
|
|
178
|
-
-
|
|
179
|
+
- Debug review entries include the responsible agent, raw prompt, raw tool-call input, command, target, and decision metadata for auditing.
|
|
179
180
|
- Path-bearing file tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) evaluate `special.external_directory` before their normal tool permission when an explicit path points outside `ctx.cwd`
|
|
180
181
|
- `read` calls under global and project Pi skill directories are checked against `skills` policy even when the skill entry is inferred from the path rather than an active prompt block.
|
|
181
182
|
- Structured edit payloads are summarized by operation and line count in prompts so permission decisions do not require raw multiline JSON.
|
|
@@ -188,25 +189,21 @@ The extension integrates via Pi's lifecycle hooks:
|
|
|
188
189
|
|
|
189
190
|
Set `PI_PERMISSION_SYSTEM_CONFIG_PATH` to point this extension at a specific config file when the default global path is not appropriate.
|
|
190
191
|
|
|
191
|
-
The extension creates this file automatically when it is missing. It controls extension-local logging behavior and yolo mode defaults:
|
|
192
|
+
The extension creates this file automatically when it is missing. It controls extension-local debug logging behavior and yolo mode defaults:
|
|
192
193
|
|
|
193
194
|
```json
|
|
194
195
|
{
|
|
195
|
-
"
|
|
196
|
-
"permissionReviewLog": true,
|
|
197
|
-
"logPlaintextBashCommands": false,
|
|
196
|
+
"debug": false,
|
|
198
197
|
"yoloMode": false
|
|
199
198
|
}
|
|
200
199
|
```
|
|
201
200
|
|
|
202
201
|
| Key | Default | Description |
|
|
203
202
|
|-----|---------|-------------|
|
|
204
|
-
| `
|
|
205
|
-
| `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
|
|
206
|
-
| `logPlaintextBashCommands` | `false` | Opts in to storing raw bash command strings in review logs; when disabled, bash commands are redacted and only safe metadata is retained |
|
|
203
|
+
| `debug` | `false` | Enables verbose diagnostics and permission review entries in `logs/pi-permission-system-debug.jsonl` |
|
|
207
204
|
| `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
|
|
208
205
|
|
|
209
|
-
|
|
206
|
+
Debug output writes only under the extension directory by default. Set `PI_PERMISSION_SYSTEM_LOGS_DIR` to redirect the debug file to a specific directory. No debug output is printed to the terminal.
|
|
210
207
|
|
|
211
208
|
### Runtime YOLO Control
|
|
212
209
|
|
|
@@ -554,21 +551,20 @@ Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-syst
|
|
|
554
551
|
Override logs directory: $PI_PERMISSION_SYSTEM_LOGS_DIR when set
|
|
555
552
|
```
|
|
556
553
|
|
|
557
|
-
- `pi-permission-system-
|
|
558
|
-
- `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
|
|
554
|
+
- `pi-permission-system-debug.jsonl` — disabled by default; includes troubleshooting diagnostics and permission review/audit entries with responsible agent metadata, raw prompts, raw tool-call inputs, commands, targets, and decisions
|
|
559
555
|
|
|
560
556
|
### Architecture
|
|
561
557
|
|
|
562
558
|
```
|
|
563
559
|
index.ts → Root Pi entrypoint shim
|
|
564
560
|
src/
|
|
565
|
-
├── index.ts → Extension bootstrap, permission checks, readable prompts, review
|
|
561
|
+
├── index.ts → Extension bootstrap, permission checks, readable prompts, debug review entries, reload handling, and subagent forwarding
|
|
566
562
|
├── before-agent-start-cache.ts → Caches prompt/tool filtering state between before_agent_start runs
|
|
567
563
|
├── bash-filter.ts → Bash command wildcard pattern matching
|
|
568
564
|
├── common.ts → Shared utilities (YAML parsing, type guards, etc.)
|
|
569
565
|
├── config-modal.ts → `/permission-system` modal registration and settings UI wiring
|
|
570
566
|
├── extension-config.ts → Extension-local config loading and default creation
|
|
571
|
-
├── logging.ts → File-only debug
|
|
567
|
+
├── logging.ts → File-only debug logging helpers
|
|
572
568
|
├── model-option-compatibility.ts → Guards unsupported provider/model options
|
|
573
569
|
├── permission-dialog.ts → Interactive permission approval UI helpers
|
|
574
570
|
├── permission-forwarding.ts → Subagent-to-parent permission forwarding utilities
|
|
@@ -653,13 +649,13 @@ npx --yes ajv-cli@5 validate \
|
|
|
653
649
|
|
|
654
650
|
## Development
|
|
655
651
|
|
|
656
|
-
Runtime checks require Node.js 20+; the test suite
|
|
652
|
+
Runtime checks require Node.js 20+; the test suite runs through Node.js with tsx and Node's experimental test module mocks (validated on Node.js 24).
|
|
657
653
|
|
|
658
654
|
```bash
|
|
659
655
|
npm run build # Run TypeScript type checks
|
|
660
656
|
npm run lint # Run local static checks
|
|
661
657
|
npm run validate:artifacts # Validate JSON/schema/example artifacts
|
|
662
|
-
npm run test # Run
|
|
658
|
+
npm run test # Run Node/tsx tests from ./tests
|
|
663
659
|
npm run check # Run static, artifact, and test checks
|
|
664
660
|
```
|
|
665
661
|
|
package/package.json
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-permission-system",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./index.ts"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"index.ts",
|
|
12
|
-
"src",
|
|
13
|
-
"tests",
|
|
14
|
-
"config/config.example.json",
|
|
15
|
-
"schemas/permissions.schema.json",
|
|
16
|
-
"README.md",
|
|
17
|
-
"CHANGELOG.md",
|
|
18
|
-
"LICENSE"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
22
|
-
"build": "npm run typecheck",
|
|
23
|
-
"lint": "npm run typecheck",
|
|
24
|
-
"validate:artifacts": "node ./scripts/validate-artifacts.mjs",
|
|
25
|
-
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
26
|
-
"check": "npm run lint && npm run validate:artifacts && npm run test"
|
|
27
|
-
},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"pi-package",
|
|
30
|
-
"pi",
|
|
31
|
-
"pi-extension",
|
|
32
|
-
"pi-coding-agent",
|
|
33
|
-
"coding-agent",
|
|
34
|
-
"permissions",
|
|
35
|
-
"policy",
|
|
36
|
-
"access-control",
|
|
37
|
-
"authorization",
|
|
38
|
-
"security"
|
|
39
|
-
],
|
|
40
|
-
"author": "MasuRii",
|
|
41
|
-
"license": "MIT",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
45
|
-
},
|
|
46
|
-
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
47
|
-
"bugs": {
|
|
48
|
-
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
49
|
-
},
|
|
50
|
-
"engines": {
|
|
51
|
-
"node": ">=20",
|
|
52
|
-
"bun": ">=1.1.0"
|
|
53
|
-
},
|
|
54
|
-
"publishConfig": {
|
|
55
|
-
"access": "public"
|
|
56
|
-
},
|
|
57
|
-
"pi": {
|
|
58
|
-
"extensions": [
|
|
59
|
-
"./index.ts"
|
|
60
|
-
]
|
|
61
|
-
},
|
|
62
|
-
"peerDependencies": {
|
|
63
|
-
"@sinclair/typebox": "^0.34.49",
|
|
64
|
-
"@earendil-works/pi-ai": "^0.75.
|
|
65
|
-
"@earendil-works/pi-coding-agent": "^0.75.
|
|
66
|
-
"@earendil-works/pi-tui": "^0.75.
|
|
67
|
-
},
|
|
68
|
-
"dependencies": {
|
|
69
|
-
"jsonc-parser": "^3.3.1"
|
|
70
|
-
}
|
|
71
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-permission-system",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Permission enforcement extension for the Pi coding agent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./index.ts"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.ts",
|
|
12
|
+
"src",
|
|
13
|
+
"tests",
|
|
14
|
+
"config/config.example.json",
|
|
15
|
+
"schemas/permissions.schema.json",
|
|
16
|
+
"README.md",
|
|
17
|
+
"CHANGELOG.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"typecheck": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noEmit",
|
|
22
|
+
"build": "npm run typecheck",
|
|
23
|
+
"lint": "npm run typecheck",
|
|
24
|
+
"validate:artifacts": "node ./scripts/validate-artifacts.mjs",
|
|
25
|
+
"test": "bun ./tests/permission-system.test.ts && bun ./tests/config-modal.test.ts",
|
|
26
|
+
"check": "npm run lint && npm run validate:artifacts && npm run test"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"pi-package",
|
|
30
|
+
"pi",
|
|
31
|
+
"pi-extension",
|
|
32
|
+
"pi-coding-agent",
|
|
33
|
+
"coding-agent",
|
|
34
|
+
"permissions",
|
|
35
|
+
"policy",
|
|
36
|
+
"access-control",
|
|
37
|
+
"authorization",
|
|
38
|
+
"security"
|
|
39
|
+
],
|
|
40
|
+
"author": "MasuRii",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/MasuRii/pi-permission-system.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/MasuRii/pi-permission-system#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/MasuRii/pi-permission-system/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20",
|
|
52
|
+
"bun": ">=1.1.0"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"pi": {
|
|
58
|
+
"extensions": [
|
|
59
|
+
"./index.ts"
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@sinclair/typebox": "^0.34.49",
|
|
64
|
+
"@earendil-works/pi-ai": "^0.74.0 || ^0.75.0",
|
|
65
|
+
"@earendil-works/pi-coding-agent": "^0.74.0 || ^0.75.0",
|
|
66
|
+
"@earendil-works/pi-tui": "^0.74.0 || ^0.75.0"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"jsonc-parser": "^3.3.1"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/config-modal.ts
CHANGED
|
@@ -1,156 +1,136 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
3
|
-
|
|
4
|
-
import type { PermissionSystemExtensionConfig } from "./extension-config.js";
|
|
5
|
-
import { ZellijModal, ZellijSettingsModal } from "./zellij-modal.js";
|
|
6
|
-
|
|
7
|
-
interface PermissionSystemConfigController {
|
|
8
|
-
getConfig(): PermissionSystemExtensionConfig;
|
|
9
|
-
setConfig(next: PermissionSystemExtensionConfig, ctx: ExtensionCommandContext): void;
|
|
10
|
-
getConfigPath(): string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface SettingValueSyncTarget {
|
|
14
|
-
updateValue(id: string, value: string): void;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const ON_OFF = ["on", "off"];
|
|
18
|
-
|
|
19
|
-
function toOnOff(value: boolean): string {
|
|
20
|
-
return value ? "on" : "off";
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function buildSettingItems(config: PermissionSystemExtensionConfig): SettingItem[] {
|
|
24
|
-
return [
|
|
25
|
-
{
|
|
26
|
-
id: "
|
|
27
|
-
label: "
|
|
28
|
-
description: "
|
|
29
|
-
currentValue: toOnOff(config.
|
|
30
|
-
values: ON_OFF,
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: "
|
|
34
|
-
label: "
|
|
35
|
-
description: "
|
|
36
|
-
currentValue: toOnOff(config.
|
|
37
|
-
values: ON_OFF,
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
-
{ overlay: true, overlayOptions },
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function registerPermissionSystemCommand(pi: ExtensionAPI, controller: PermissionSystemConfigController): void {
|
|
145
|
-
pi.registerCommand("permission-system", {
|
|
146
|
-
description: "Configure pi-permission-system logging and yolo-mode behavior",
|
|
147
|
-
handler: async (_args, ctx) => {
|
|
148
|
-
if (!ctx.hasUI) {
|
|
149
|
-
ctx.ui.notify("/permission-system requires interactive TUI mode.", "warning");
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
await openSettingsModal(ctx, controller);
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
}
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
import type { PermissionSystemExtensionConfig } from "./extension-config.js";
|
|
5
|
+
import { ZellijModal, ZellijSettingsModal } from "./zellij-modal.js";
|
|
6
|
+
|
|
7
|
+
interface PermissionSystemConfigController {
|
|
8
|
+
getConfig(): PermissionSystemExtensionConfig;
|
|
9
|
+
setConfig(next: PermissionSystemExtensionConfig, ctx: ExtensionCommandContext): void;
|
|
10
|
+
getConfigPath(): string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface SettingValueSyncTarget {
|
|
14
|
+
updateValue(id: string, value: string): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const ON_OFF = ["on", "off"];
|
|
18
|
+
|
|
19
|
+
function toOnOff(value: boolean): string {
|
|
20
|
+
return value ? "on" : "off";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function buildSettingItems(config: PermissionSystemExtensionConfig): SettingItem[] {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
id: "debug",
|
|
27
|
+
label: "Debug logging",
|
|
28
|
+
description: "Write diagnostics and permission review entries to the extension debug file",
|
|
29
|
+
currentValue: toOnOff(config.debug),
|
|
30
|
+
values: ON_OFF,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "yoloMode",
|
|
34
|
+
label: "YOLO mode",
|
|
35
|
+
description: "Auto-approve ask-state permission checks, including subagent approval forwarding",
|
|
36
|
+
currentValue: toOnOff(config.yoloMode),
|
|
37
|
+
values: ON_OFF,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function applySetting(
|
|
43
|
+
config: PermissionSystemExtensionConfig,
|
|
44
|
+
id: string,
|
|
45
|
+
value: string,
|
|
46
|
+
): PermissionSystemExtensionConfig {
|
|
47
|
+
switch (id) {
|
|
48
|
+
case "debug":
|
|
49
|
+
return { ...config, debug: value === "on" };
|
|
50
|
+
case "yoloMode":
|
|
51
|
+
return { ...config, yoloMode: value === "on" };
|
|
52
|
+
default:
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function syncSettingValues(settingsList: SettingValueSyncTarget, config: PermissionSystemExtensionConfig): void {
|
|
58
|
+
settingsList.updateValue("debug", toOnOff(config.debug));
|
|
59
|
+
settingsList.updateValue("yoloMode", toOnOff(config.yoloMode));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function openPermissionSystemSettingsModal(ctx: ExtensionCommandContext, controller: PermissionSystemConfigController): Promise<void> {
|
|
63
|
+
const overlayOptions = { anchor: "center" as const, width: 82, maxHeight: "85%" as const, margin: 1 };
|
|
64
|
+
|
|
65
|
+
await ctx.ui.custom<void>(
|
|
66
|
+
(tui, theme, _keybindings, done) => {
|
|
67
|
+
let current = controller.getConfig();
|
|
68
|
+
let settingsModal: ZellijSettingsModal | null = null;
|
|
69
|
+
|
|
70
|
+
settingsModal = new ZellijSettingsModal(
|
|
71
|
+
{
|
|
72
|
+
title: "Permission System Settings",
|
|
73
|
+
description: "Local extension options for debug logging and auto-approval behavior",
|
|
74
|
+
settings: buildSettingItems(current),
|
|
75
|
+
onChange: (id, newValue) => {
|
|
76
|
+
current = applySetting(current, id, newValue);
|
|
77
|
+
controller.setConfig(current, ctx);
|
|
78
|
+
current = controller.getConfig();
|
|
79
|
+
if (settingsModal) {
|
|
80
|
+
syncSettingValues(settingsModal, current);
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
onClose: () => done(),
|
|
84
|
+
helpText: `Config file: ${controller.getConfigPath()}`,
|
|
85
|
+
enableSearch: true,
|
|
86
|
+
},
|
|
87
|
+
theme,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const modal = new ZellijModal(
|
|
91
|
+
settingsModal,
|
|
92
|
+
{
|
|
93
|
+
borderStyle: "rounded",
|
|
94
|
+
titleBar: {
|
|
95
|
+
left: "Permission System Settings",
|
|
96
|
+
right: "pi-permission-system",
|
|
97
|
+
},
|
|
98
|
+
helpUndertitle: {
|
|
99
|
+
text: "Esc: close | ↑↓: navigate | Space: toggle",
|
|
100
|
+
color: "dim",
|
|
101
|
+
},
|
|
102
|
+
overlay: overlayOptions,
|
|
103
|
+
},
|
|
104
|
+
theme,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
render(width: number) {
|
|
109
|
+
return modal.renderModal(width).lines;
|
|
110
|
+
},
|
|
111
|
+
invalidate() {
|
|
112
|
+
modal.invalidate();
|
|
113
|
+
},
|
|
114
|
+
handleInput(data: string) {
|
|
115
|
+
modal.handleInput(data);
|
|
116
|
+
tui.requestRender();
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
{ overlay: true, overlayOptions },
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function registerPermissionSystemCommand(pi: ExtensionAPI, controller: PermissionSystemConfigController): void {
|
|
125
|
+
pi.registerCommand("permission-system", {
|
|
126
|
+
description: "Configure pi-permission-system debug logging and yolo-mode behavior",
|
|
127
|
+
handler: async (_args, ctx) => {
|
|
128
|
+
if (!ctx.hasUI) {
|
|
129
|
+
ctx.ui.notify("/permission-system requires interactive TUI mode.", "warning");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
await openPermissionSystemSettingsModal(ctx, controller);
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { PermissionState } from "./types.js";
|
|
2
|
+
import { compileWildcardPattern } from "./wildcard-matcher.js";
|
|
3
|
+
|
|
4
|
+
export type PatternPermissionRule = {
|
|
5
|
+
tool: string;
|
|
6
|
+
pattern: string;
|
|
7
|
+
action: PermissionState;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type PatternPermissionEvaluation = {
|
|
11
|
+
action: PermissionState;
|
|
12
|
+
matchedPattern?: string;
|
|
13
|
+
matchedTool?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function isPatternPermissionRule(value: unknown): value is PatternPermissionRule {
|
|
17
|
+
if (!value || typeof value !== "object") {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const candidate = value as Partial<PatternPermissionRule>;
|
|
22
|
+
return typeof candidate.tool === "string"
|
|
23
|
+
&& typeof candidate.pattern === "string"
|
|
24
|
+
&& (candidate.action === "allow" || candidate.action === "deny" || candidate.action === "ask");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function normalizeRuleset(value: unknown): PatternPermissionRule[] {
|
|
28
|
+
return Array.isArray(value) ? value.filter(isPatternPermissionRule) : [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function evaluatePermission(
|
|
32
|
+
tool: string,
|
|
33
|
+
command: string,
|
|
34
|
+
...rulesets: unknown[]
|
|
35
|
+
): PatternPermissionEvaluation {
|
|
36
|
+
const rules = rulesets.flatMap(normalizeRuleset);
|
|
37
|
+
|
|
38
|
+
for (let index = rules.length - 1; index >= 0; index -= 1) {
|
|
39
|
+
const rule = rules[index];
|
|
40
|
+
const toolPattern = compileWildcardPattern(rule.tool, rule.action);
|
|
41
|
+
if (!toolPattern.regex.test(tool)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const commandPattern = compileWildcardPattern(rule.pattern, rule.action);
|
|
46
|
+
if (!commandPattern.regex.test(command)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
action: rule.action,
|
|
52
|
+
matchedPattern: rule.pattern,
|
|
53
|
+
matchedTool: rule.tool,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { action: "ask" };
|
|
58
|
+
}
|