@gotgenes/pi-permission-system 2.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +92 -35
  3. package/config/config.example.json +6 -0
  4. package/package.json +1 -1
  5. package/schemas/permissions.schema.json +114 -16
  6. package/src/active-agent.ts +58 -0
  7. package/src/config-loader.ts +398 -0
  8. package/src/config-paths.ts +34 -0
  9. package/src/config-reporter.ts +16 -8
  10. package/src/external-directory.ts +113 -0
  11. package/src/forwarded-permissions/io.ts +328 -0
  12. package/src/forwarded-permissions/polling.ts +334 -0
  13. package/src/index.ts +153 -1095
  14. package/src/permission-manager.ts +25 -111
  15. package/src/permission-prompts.ts +131 -0
  16. package/src/subagent-context.ts +52 -0
  17. package/src/tool-input-preview.ts +206 -0
  18. package/tests/active-agent.test.ts +160 -0
  19. package/tests/bash-filter.test.ts +137 -0
  20. package/tests/common.test.ts +189 -0
  21. package/tests/config-loader.test.ts +364 -0
  22. package/tests/config-paths.test.ts +78 -0
  23. package/tests/config-reporter.test.ts +42 -33
  24. package/tests/extension-config.test.ts +51 -0
  25. package/tests/external-directory.test.ts +250 -0
  26. package/tests/permission-prompts.test.ts +301 -0
  27. package/tests/permission-system.test.ts +9 -26
  28. package/tests/session-start.test.ts +8 -33
  29. package/tests/skill-prompt-sanitizer.test.ts +244 -0
  30. package/tests/subagent-context.test.ts +124 -0
  31. package/tests/system-prompt-sanitizer.test.ts +186 -0
  32. package/tests/tool-input-preview.test.ts +452 -0
  33. package/tests/tool-registry.test.ts +155 -0
  34. package/tests/wildcard-matcher.test.ts +180 -0
  35. package/tests/yolo-mode.test.ts +110 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,42 @@ 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
+ ## [3.0.1](https://github.com/gotgenes/pi-permission-system/compare/v3.0.0...v3.0.1) (2026-05-03)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * add descriptions to all JSON schema entities ([cb3a7ce](https://github.com/gotgenes/pi-permission-system/commit/cb3a7ce5257111159312ebb95a9f75dd4e4a9527))
14
+ * enrich JSON schema with examples, defaults, and markdown descriptions ([6f38d7e](https://github.com/gotgenes/pi-permission-system/commit/6f38d7edf01b950f20e2fab8604a398bf725a6c4))
15
+ * plan index.ts split into focused modules ([#21](https://github.com/gotgenes/pi-permission-system/issues/21)) ([ccd736a](https://github.com/gotgenes/pi-permission-system/commit/ccd736a83a4af208044eec925c80555ee645e344))
16
+ * **retro:** add retro notes for issue [#10](https://github.com/gotgenes/pi-permission-system/issues/10) ([31e59d6](https://github.com/gotgenes/pi-permission-system/commit/31e59d6172da7b0e9f02894afd2ba66a292de168))
17
+ * **retro:** correct formatting friction attribution ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([2e96b7b](https://github.com/gotgenes/pi-permission-system/commit/2e96b7bc1007a2ef55f95b29c40b8417c2c6c52f))
18
+ * update plan with Phase 2 unit tests using DI and vitest mocks ([#21](https://github.com/gotgenes/pi-permission-system/issues/21)) ([ad7f5fe](https://github.com/gotgenes/pi-permission-system/commit/ad7f5feeb856c84142a2a4258a9e173c8006532d))
19
+
20
+ ## [3.0.0](https://github.com/gotgenes/pi-permission-system/compare/v2.0.0...v3.0.0) (2026-05-03)
21
+
22
+
23
+ ### ⚠ BREAKING CHANGES
24
+
25
+ * Config is now loaded from ~/.pi/agent/extensions/pi-permission-system/config.json (global) and <cwd>/.pi/extensions/pi-permission-system/config.json (project). Legacy paths are detected and merged with migration warnings.
26
+ * Config and log file paths move from the extension install directory and ~/.pi/agent/ to the extensions/<id>/ convention.
27
+
28
+ ### Features
29
+
30
+ * add config-paths module with new layout paths ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([532d2a1](https://github.com/gotgenes/pi-permission-system/commit/532d2a1f1d816c1cfba5419f7d0041382c848b31))
31
+ * add unified config loader ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([20143e0](https://github.com/gotgenes/pi-permission-system/commit/20143e0f7b608965d889433a6b9bbd6f9ab8b4cc))
32
+ * detect and merge legacy config paths ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([95046de](https://github.com/gotgenes/pi-permission-system/commit/95046de6a57d604c5f0d9fa8c13a64478ba15c89))
33
+ * implement config merge in unified loader ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([30b9afe](https://github.com/gotgenes/pi-permission-system/commit/30b9afe3d83940aa3ad708c9d6783bb2d4337743))
34
+ * update config-reporter for consolidated layout ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([96c9ef4](https://github.com/gotgenes/pi-permission-system/commit/96c9ef4964f9551b0fa89bbbc506f7660c055d74))
35
+ * wire index.ts to consolidated config layout ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([e7f8e5f](https://github.com/gotgenes/pi-permission-system/commit/e7f8e5f2fb094f0d291561258190d1892a1e6856))
36
+
37
+
38
+ ### Documentation
39
+
40
+ * plan config layout consolidation ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([eb2924d](https://github.com/gotgenes/pi-permission-system/commit/eb2924d57655d06f67891574839aebfa0586a43d))
41
+ * **retro:** add retro notes for issue [#20](https://github.com/gotgenes/pi-permission-system/issues/20) ([4735f0c](https://github.com/gotgenes/pi-permission-system/commit/4735f0c646a0ede11c9a76083822969eb6ca4a8f))
42
+ * update schema, example, and docs for consolidated config ([#10](https://github.com/gotgenes/pi-permission-system/issues/10)) ([39b5c01](https://github.com/gotgenes/pi-permission-system/commit/39b5c01de1c8c721e998b244e9c825a6eb05f858))
43
+
8
44
  ## [2.0.0](https://github.com/gotgenes/pi-permission-system/compare/v1.2.1...v2.0.0) (2026-05-03)
9
45
 
10
46
 
package/README.md CHANGED
@@ -48,7 +48,7 @@ Pi auto-discovers extensions in these paths.
48
48
 
49
49
  ### Quick Start
50
50
 
51
- 1. Create the global policy file at the Pi agent runtime root (default: `~/.pi/agent/pi-permissions.jsonc`, respects `PI_CODING_AGENT_DIR`):
51
+ 1. Create the global config file (default: `~/.pi/agent/extensions/pi-permission-system/config.json`, respects `PI_CODING_AGENT_DIR`):
52
52
 
53
53
  ```jsonc
54
54
  {
@@ -57,12 +57,12 @@ Pi auto-discovers extensions in these paths.
57
57
  "bash": "ask",
58
58
  "mcp": "ask",
59
59
  "skills": "ask",
60
- "special": "ask",
60
+ "special": "ask"
61
61
  },
62
62
  "tools": {
63
63
  "read": "allow",
64
- "write": "deny",
65
- },
64
+ "write": "deny"
65
+ }
66
66
  }
67
67
  ```
68
68
 
@@ -100,36 +100,60 @@ The extension integrates via Pi's lifecycle hooks:
100
100
 
101
101
  ## Configuration
102
102
 
103
- ### Extension Config File
103
+ ### Config File
104
+
105
+ **Location:** one unified config file per scope, following the `pi-autoformat` convention:
104
106
 
105
- **Location:** global Pi extension config (default: `~/.pi/agent/extensions/pi-permission-system/config.json`, respects `PI_CODING_AGENT_DIR`)
107
+ | Scope | Path |
108
+ | ------- | ------------------------------------------------------------------------------------------------- |
109
+ | Global | `~/.pi/agent/extensions/pi-permission-system/config.json` (respects `PI_CODING_AGENT_DIR`) |
110
+ | Project | `<cwd>/.pi/extensions/pi-permission-system/config.json` |
106
111
 
107
- The extension creates this file automatically when it is missing. It controls only extension-local logging behavior:
112
+ Project config overrides global config; per-agent frontmatter overrides both.
113
+ Object-shaped fields (`defaultPolicy`, `tools`, `bash`, `mcp`, `skills`, `special`) use shallow-merge (later source wins per-key).
114
+ Scalar fields (`debugLog`, `permissionReviewLog`, `yoloMode`) use simple replacement.
108
115
 
109
- ```json
116
+ The config file combines runtime knobs and permission policy in one object:
117
+
118
+ ```jsonc
110
119
  {
120
+ "$schema": "https://raw.githubusercontent.com/gotgenes/pi-permission-system/main/schemas/permissions.schema.json",
121
+
122
+ // Runtime knobs
111
123
  "debugLog": false,
112
124
  "permissionReviewLog": true,
113
- "yoloMode": false
125
+ "yoloMode": false,
126
+
127
+ // Policy
128
+ "defaultPolicy": {
129
+ "tools": "ask",
130
+ "bash": "ask",
131
+ "mcp": "ask",
132
+ "skills": "ask",
133
+ "special": "ask"
134
+ },
135
+ "tools": { "read": "allow", "write": "deny" },
136
+ "bash": { "git status": "allow", "git *": "ask" },
137
+ "mcp": { "mcp_status": "allow" },
138
+ "skills": { "*": "ask" },
139
+ "special": { "doom_loop": "deny", "external_directory": "ask" }
114
140
  }
115
141
  ```
116
142
 
143
+ #### Runtime knobs
144
+
117
145
  | Key | Default | Description |
118
146
  | --------------------- | ------- | ------------------------------------------------------------------------------------------------------- |
119
147
  | `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl` |
120
148
  | `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
121
149
  | `yoloMode` | `false` | Auto-approves `ask` results instead of prompting when yolo mode is enabled |
122
150
 
123
- Both logs write to files only under the extension directory. No debug output is printed to the terminal.
151
+ Both logs write to `~/.pi/agent/extensions/pi-permission-system/logs/`.
152
+ No debug output is printed to the terminal.
124
153
 
125
- > **Note:** Permission-rule keys (`defaultPolicy`, `tools`, `bash`, `mcp`, `skills`, `special`, `external_directory`, `doom_loop`) placed in `config.json` are silently ignored — they belong in the policy file below.
126
- > The extension warns at startup when it detects misplaced keys.
154
+ #### Policy sections
127
155
 
128
- ### Global Policy File
129
-
130
- **Location:** global Pi policy file (default: `~/.pi/agent/pi-permissions.jsonc`, respects `PI_CODING_AGENT_DIR`)
131
-
132
- The policy file is a JSON object with these sections:
156
+ The config file is a JSON object with these policy sections:
133
157
 
134
158
  | Section | Description |
135
159
  | --------------- | ---------------------------------------------------------------------------- |
@@ -169,21 +193,22 @@ permission:
169
193
 
170
194
  **Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
171
195
 
172
- ### Project-Level Policy Files
196
+ ### Project-Level Config and Overrides
173
197
 
174
- The extension can also layer project-local permission files relative to the active session working directory:
198
+ Project-local config uses the same format as the global config file.
199
+ Per-agent overrides use YAML frontmatter in the project agents directory:
175
200
 
176
- | Scope | Path |
177
- | ---------------------- | -------------------------------------- |
178
- | Project policy | `<cwd>/.pi/agent/pi-permissions.jsonc` |
179
- | Project agent override | `<cwd>/.pi/agent/agents/<agent>.md` |
201
+ | Scope | Path |
202
+ | ---------------------- | ------------------------------------------------------------- |
203
+ | Project config | `<cwd>/.pi/extensions/pi-permission-system/config.json` |
204
+ | Project agent override | `<cwd>/.pi/agent/agents/<agent>.md` |
180
205
 
181
- Project-local files use the same formats as the global policy file and global agent frontmatter. These project files are resolved from Pi's current session `cwd`, so they are workspace-specific and do **not** move under `PI_CODING_AGENT_DIR`.
206
+ These project files are resolved from Pi's current session `cwd`, so they are workspace-specific and do **not** move under `PI_CODING_AGENT_DIR`.
182
207
 
183
208
  **Precedence order:**
184
209
 
185
- 1. Global policy file
186
- 2. Project policy file
210
+ 1. Global config file
211
+ 2. Project config file
187
212
  3. Global agent frontmatter
188
213
  4. Project agent frontmatter
189
214
 
@@ -454,7 +479,7 @@ When the extension prompts, denies, or forwards permission requests, it can appe
454
479
 
455
480
  ```text
456
481
  Default global logs directory: ~/.pi/agent/extensions/pi-permission-system/logs/
457
- Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs when PI_CODING_AGENT_DIR is set
482
+ Actual global logs directory: $PI_CODING_AGENT_DIR/extensions/pi-permission-system/logs/ when PI_CODING_AGENT_DIR is set
458
483
  ```
459
484
 
460
485
  - `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history, including bounded `toolInputPreview` values for non-bash/non-MCP tool calls
@@ -466,16 +491,17 @@ This makes it easy to verify which files the extension actually loaded:
466
491
  ```jsonc
467
492
  {
468
493
  "event": "config.resolved",
469
- "extensionConfigPath": "/…/pi-permission-system/config.json",
470
- "extensionConfigExists": true,
471
- "globalConfigPath": "/…/.pi/agent/pi-permissions.jsonc",
472
- "globalConfigExists": false,
473
- "projectConfigPath": "/…/my-project/.pi/agent/pi-permissions.jsonc",
474
- "projectConfigExists": true,
494
+ "globalConfigPath": "/…/.pi/agent/extensions/pi-permission-system/config.json",
495
+ "globalConfigExists": true,
496
+ "projectConfigPath": "/…/my-project/.pi/extensions/pi-permission-system/config.json",
497
+ "projectConfigExists": false,
475
498
  "agentsDir": "/…/.pi/agent/agents",
476
499
  "agentsDirExists": true,
477
500
  "projectAgentsDir": "/…/my-project/.pi/agent/agents",
478
501
  "projectAgentsDirExists": false,
502
+ "legacyGlobalPolicyDetected": false,
503
+ "legacyProjectPolicyDetected": false,
504
+ "legacyExtensionConfigDetected": false
479
505
  }
480
506
  ```
481
507
 
@@ -485,8 +511,10 @@ This makes it easy to verify which files the extension actually loaded:
485
511
  index.ts → Root Pi entrypoint shim
486
512
  src/
487
513
  ├── index.ts → Extension bootstrap, permission checks, readable prompts, review logging, reload handling, and subagent forwarding
514
+ ├── config-loader.ts → Unified config loader, merger, and legacy-path detection
515
+ ├── config-paths.ts → Path derivation for global, project, and legacy config locations
488
516
  ├── config-reporter.ts → Resolved config path reporting for diagnostic logs
489
- ├── extension-config.ts → Extension-local config loading and default creation
517
+ ├── extension-config.ts → Runtime config normalization and defaults
490
518
  ├── logging.ts → File-only debug/review logging helpers
491
519
  ├── permission-manager.ts → Global/project policy loading, merging, and resolution with caching
492
520
  ├── skill-prompt-sanitizer.ts → Skill prompt parsing, multi-block sanitization, and skill-read path matching
@@ -553,11 +581,40 @@ npx --yes ajv-cli@5 validate \
553
581
 
554
582
  ---
555
583
 
584
+ ## Migration from pre-v2 layout
585
+
586
+ Before v2, config was split across two files:
587
+
588
+ - Policy: `~/.pi/agent/pi-permissions.jsonc`
589
+ - Runtime knobs: `<extension-install-dir>/config.json`
590
+
591
+ These are now consolidated into one file.
592
+ The extension detects legacy files and merges them with a warning for one release.
593
+ To migrate manually:
594
+
595
+ ```bash
596
+ # Move the global policy file
597
+ mkdir -p ~/.pi/agent/extensions/pi-permission-system
598
+ mv ~/.pi/agent/pi-permissions.jsonc ~/.pi/agent/extensions/pi-permission-system/config.json
599
+
600
+ # If you had project-level policy:
601
+ mkdir -p .pi/extensions/pi-permission-system
602
+ mv .pi/agent/pi-permissions.jsonc .pi/extensions/pi-permission-system/config.json
603
+ ```
604
+
605
+ Then add any runtime knobs (`debugLog`, `permissionReviewLog`, `yoloMode`) to the same file.
606
+ The old extension-root `config.json` is no longer read from the install directory.
607
+
608
+ > **Note:** Logs also moved from `<extension-install-dir>/logs/` to `~/.pi/agent/extensions/pi-permission-system/logs/`.
609
+ > Old log files are not deleted or migrated — they remain readable but no new entries are appended.
610
+
611
+ ---
612
+
556
613
  ## Troubleshooting
557
614
 
558
615
  | Problem | Cause | Solution |
559
616
  | ------------------------------------ | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
560
- | Config not applied (everything asks) | File not found or parse error | Verify the global Pi policy file (default: `~/.pi/agent/pi-permissions.jsonc`, respects `PI_CODING_AGENT_DIR`); check for trailing commas |
617
+ | Config not applied (everything asks) | File not found or parse error | Verify the global config at `~/.pi/agent/extensions/pi-permission-system/config.json` (respects `PI_CODING_AGENT_DIR`); check for trailing commas |
561
618
  | Per-agent override not applied | Frontmatter parsing issue | Ensure `---` delimiters at file top; keep YAML simple; restart session |
562
619
  | Tool blocked as unregistered | Unknown tool name | Use a registered `mcp` tool for server tools: `{ "tool": "server:tool" }` |
563
620
  | `/skill:<name>` blocked | Deny policy or confirmation unavailable | Check merged `skills` policy (global/project/agent layers). Active agent context is optional in the main session; `ask` still requires UI or forwarded confirmation. |
@@ -1,4 +1,10 @@
1
1
  {
2
+ "$schema": "https://raw.githubusercontent.com/gotgenes/pi-permission-system/main/schemas/permissions.schema.json",
3
+
4
+ "debugLog": false,
5
+ "permissionReviewLog": true,
6
+ "yoloMode": false,
7
+
2
8
  "defaultPolicy": {
3
9
  "tools": "ask",
4
10
  "bash": "ask",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "2.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -1,71 +1,169 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://pi-coding-agent.local/schemas/permissions.schema.json",
4
- "title": "PI Permission Configuration",
3
+ "$id": "https://raw.githubusercontent.com/gotgenes/pi-permission-system/main/schemas/permissions.schema.json",
4
+ "title": "PI Permission System Configuration",
5
+ "description": "Unified config file combining runtime knobs and permission policy for pi-permission-system.",
6
+ "markdownDescription": "Unified config file combining runtime knobs and permission policy for [pi-permission-system](https://github.com/gotgenes/pi-permission-system).\n\nPlace at `~/.pi/agent/extensions/pi-permission-system/config.json` (global) or `<project>/.pi/extensions/pi-permission-system/config.json` (project).",
5
7
  "type": "object",
6
8
  "additionalProperties": false,
7
9
  "properties": {
8
10
  "$schema": {
11
+ "description": "JSON Schema URI for editor autocomplete and validation.",
9
12
  "type": "string"
10
13
  },
14
+ "debugLog": {
15
+ "description": "Write verbose permission-system diagnostics to the extension logs directory.",
16
+ "markdownDescription": "Write verbose permission-system diagnostics to `logs/pi-permission-system-debug.jsonl` under the extension config directory.",
17
+ "type": "boolean",
18
+ "default": false
19
+ },
20
+ "permissionReviewLog": {
21
+ "description": "Write permission request and decision audit events to the extension logs directory.",
22
+ "markdownDescription": "Write permission request and decision audit events to `logs/pi-permission-system-permission-review.jsonl` under the extension config directory.",
23
+ "type": "boolean",
24
+ "default": true
25
+ },
26
+ "yoloMode": {
27
+ "description": "Auto-approve ask-state permission checks, including subagent approval forwarding.",
28
+ "markdownDescription": "Auto-approve `ask`-state permission checks, including subagent approval forwarding.\n\n⚠️ **Use with caution** — this disables all interactive confirmation prompts.",
29
+ "type": "boolean",
30
+ "default": false
31
+ },
11
32
  "defaultPolicy": {
33
+ "description": "Fallback permission state per category when no specific rule matches.",
34
+ "markdownDescription": "Fallback permission state per category when no specific rule matches.\n\nAll fields default to `\"ask\"` when omitted.",
12
35
  "type": "object",
13
36
  "additionalProperties": false,
14
- "required": ["tools", "bash", "mcp", "skills"],
15
37
  "properties": {
16
38
  "tools": {
17
- "$ref": "#/$defs/permissionState"
39
+ "description": "Default permission for registered tools when no exact-name rule matches in the tools map.",
40
+ "markdownDescription": "Default permission for registered tools when no exact-name rule matches in the `tools` map.",
41
+ "$ref": "#/$defs/permissionState",
42
+ "default": "ask"
18
43
  },
19
44
  "bash": {
20
- "$ref": "#/$defs/permissionState"
45
+ "description": "Default permission for bash commands when no pattern in the bash map matches.",
46
+ "markdownDescription": "Default permission for bash commands when no pattern in the `bash` map matches.",
47
+ "$ref": "#/$defs/permissionState",
48
+ "default": "ask"
21
49
  },
22
50
  "mcp": {
23
- "$ref": "#/$defs/permissionState"
51
+ "description": "Default permission for MCP operations when no target pattern in the mcp map matches.",
52
+ "markdownDescription": "Default permission for MCP operations when no target pattern in the `mcp` map matches.",
53
+ "$ref": "#/$defs/permissionState",
54
+ "default": "ask"
24
55
  },
25
56
  "skills": {
26
- "$ref": "#/$defs/permissionState"
57
+ "description": "Default permission for skill loading when no pattern in the skills map matches.",
58
+ "markdownDescription": "Default permission for skill loading when no pattern in the `skills` map matches.",
59
+ "$ref": "#/$defs/permissionState",
60
+ "default": "ask"
27
61
  },
28
62
  "special": {
29
- "$ref": "#/$defs/permissionState"
63
+ "description": "Default permission for special checks (doom_loop, external_directory) when no explicit rule matches.",
64
+ "markdownDescription": "Default permission for special checks (`doom_loop`, `external_directory`) when no explicit rule matches.",
65
+ "$ref": "#/$defs/permissionState",
66
+ "default": "ask"
30
67
  }
31
68
  }
32
69
  },
33
70
  "tools": {
34
71
  "description": "Exact-name permissions for registered tools. Use this map for the canonical Pi built-ins and any extension-provided or third-party tools.",
35
- "$ref": "#/$defs/permissionMap"
72
+ "markdownDescription": "Exact-name permissions for registered tools. Use this map for the canonical Pi built-ins (`read`, `write`, `edit`, `bash`, `grep`, `find`, `ls`) and any extension-provided or third-party tools.\n\nNo wildcards — keys must match the registered tool name exactly.",
73
+ "$ref": "#/$defs/permissionMap",
74
+ "examples": [
75
+ {
76
+ "read": "allow",
77
+ "write": "deny",
78
+ "edit": "ask",
79
+ "mcp": "allow"
80
+ }
81
+ ]
36
82
  },
37
83
  "bash": {
38
- "$ref": "#/$defs/permissionMap"
84
+ "description": "Wildcard pattern permissions for bash commands. Patterns use * globs matched against the full command string. Last matching rule wins.",
85
+ "markdownDescription": "Wildcard pattern permissions for bash commands. Patterns use `*` globs matched against the full command string.\n\n**Last matching rule wins** — put broad catch-all rules first and specific overrides after them.",
86
+ "$ref": "#/$defs/permissionMap",
87
+ "examples": [
88
+ {
89
+ "git status": "allow",
90
+ "git diff": "allow",
91
+ "git *": "ask",
92
+ "rm -rf *": "deny"
93
+ }
94
+ ]
39
95
  },
40
96
  "mcp": {
41
- "description": "Pattern-based permissions for targets invoked through a registered `mcp` tool when available.",
42
- "$ref": "#/$defs/permissionMap"
97
+ "description": "Pattern-based permissions for targets invoked through a registered mcp tool when available.",
98
+ "markdownDescription": "Pattern-based permissions for targets invoked through a registered `mcp` tool when available.\n\nTargets include server names (`myServer`), qualified tool names (`myServer:search`, `myServer_search`), and baseline operations (`mcp_status`, `mcp_list`, `mcp_connect`, `mcp_describe`, `mcp_search`).",
99
+ "$ref": "#/$defs/permissionMap",
100
+ "examples": [
101
+ {
102
+ "mcp_status": "allow",
103
+ "mcp_list": "allow",
104
+ "myServer:*": "ask",
105
+ "dangerousServer": "deny"
106
+ }
107
+ ]
43
108
  },
44
109
  "skills": {
45
- "$ref": "#/$defs/permissionMap"
110
+ "description": "Wildcard pattern permissions for skill names. Controls which skills can be loaded or read from disk.",
111
+ "markdownDescription": "Wildcard pattern permissions for skill names. Controls which skills can be loaded or read from disk.\n\nUse `\"*\": \"ask\"` to require confirmation for all skills, or `\"dangerous-*\": \"deny\"` to block a family of skills.",
112
+ "$ref": "#/$defs/permissionMap",
113
+ "examples": [
114
+ {
115
+ "*": "ask",
116
+ "dangerous-*": "deny"
117
+ }
118
+ ]
46
119
  },
47
120
  "special": {
121
+ "description": "Reserved permission checks for special runtime behaviors.",
48
122
  "type": "object",
49
123
  "additionalProperties": false,
50
124
  "properties": {
51
125
  "doom_loop": {
126
+ "description": "Controls doom loop detection behavior.",
52
127
  "$ref": "#/$defs/permissionState"
53
128
  },
54
129
  "external_directory": {
130
+ "description": "Enforces permission checks for path-bearing file tools (read, write, edit, find, grep, ls) when they target paths outside the active working directory.",
131
+ "markdownDescription": "Enforces permission checks for path-bearing file tools (`read`, `write`, `edit`, `find`, `grep`, `ls`) when they target paths outside the active working directory.\n\nEvaluated **before** the normal tool permission check — so `tools.read: \"allow\"` can permit ordinary reads while `external_directory: \"ask\"` still requires confirmation for paths outside `cwd`.",
132
+ "$ref": "#/$defs/permissionState"
133
+ },
134
+ "tool_call_limit": {
135
+ "description": "Deprecated and ignored. This key has no effect and should be removed from your config.",
136
+ "markdownDescription": "⚠️ **Deprecated and ignored.** This key has no effect and should be removed from your config.",
137
+ "deprecated": true,
55
138
  "$ref": "#/$defs/permissionState"
56
139
  }
57
140
  }
58
141
  }
59
142
  },
60
- "required": ["defaultPolicy"],
61
143
  "$defs": {
62
144
  "permissionState": {
63
- "type": "string",
64
- "enum": ["allow", "deny", "ask"]
145
+ "description": "A permission decision: allow (permit silently), deny (block with error), or ask (prompt the user for confirmation).",
146
+ "oneOf": [
147
+ {
148
+ "const": "allow",
149
+ "description": "Permit the action silently with no user interaction."
150
+ },
151
+ {
152
+ "const": "deny",
153
+ "description": "Block the action with an error message. The agent is told not to retry."
154
+ },
155
+ {
156
+ "const": "ask",
157
+ "description": "Prompt the user for confirmation via the interactive UI before proceeding."
158
+ }
159
+ ]
65
160
  },
66
161
  "permissionMap": {
162
+ "description": "A map of name or wildcard patterns to permission states. Keys are matched against the relevant target (tool name, bash command, MCP target, or skill name).",
163
+ "markdownDescription": "A map of name or wildcard patterns to permission states.\n\nKeys are matched against the relevant target. Use `*` for wildcard matching. When multiple patterns match, the **last matching rule wins**.",
67
164
  "type": "object",
68
165
  "propertyNames": {
166
+ "description": "A non-empty pattern string. Use * for wildcard matching.",
69
167
  "type": "string",
70
168
  "minLength": 1
71
169
  },
@@ -0,0 +1,58 @@
1
+ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+
3
+ /**
4
+ * Matches the `<active_agent name="...">` tag injected by pi-agent-router
5
+ * into the system prompt to identify which agent definition is active.
6
+ */
7
+ export const ACTIVE_AGENT_TAG_REGEX =
8
+ /<active_agent\s+name=["']([^"']+)["'][^>]*>/i;
9
+
10
+ export function normalizeAgentName(value: unknown): string | null {
11
+ if (typeof value !== "string") {
12
+ return null;
13
+ }
14
+
15
+ const trimmed = value.trim();
16
+ return trimmed ? trimmed : null;
17
+ }
18
+
19
+ export function getActiveAgentName(ctx: ExtensionContext): string | null {
20
+ const entries = ctx.sessionManager.getEntries();
21
+ for (let i = entries.length - 1; i >= 0; i--) {
22
+ const entry = entries[i] as {
23
+ type: string;
24
+ customType?: string;
25
+ data?: unknown;
26
+ };
27
+ if (entry.type !== "custom" || entry.customType !== "active_agent") {
28
+ continue;
29
+ }
30
+
31
+ const data = entry.data as { name?: unknown } | undefined;
32
+ const normalizedName = normalizeAgentName(data?.name);
33
+ if (normalizedName) {
34
+ return normalizedName;
35
+ }
36
+
37
+ if (data?.name === null) {
38
+ return null;
39
+ }
40
+ }
41
+
42
+ return null;
43
+ }
44
+
45
+ export function getActiveAgentNameFromSystemPrompt(
46
+ systemPrompt: string | undefined,
47
+ ): string | null {
48
+ if (!systemPrompt) {
49
+ return null;
50
+ }
51
+
52
+ const match = systemPrompt.match(ACTIVE_AGENT_TAG_REGEX);
53
+ if (!match?.[1]) {
54
+ return null;
55
+ }
56
+
57
+ return normalizeAgentName(match[1]);
58
+ }