pi-permission-system 0.2.0 → 0.2.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.md CHANGED
@@ -5,6 +5,35 @@ 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
+ ## [0.2.2] - 2026-03-13
9
+
10
+ ### Changed
11
+ - Removed delegation task restriction logic — the `task` tool is no longer restricted to orchestrator agent only
12
+ - Simplified tool permission lookup to use explicit `tools` entries for arbitrary registered tools instead of MCP fallback
13
+ - Renamed `TOOL_PERMISSION_NAMES` to `BUILT_IN_TOOL_PERMISSION_NAMES` to clarify it covers only canonical Pi tools
14
+ - Updated schema descriptions for `tools` and `mcp` fields to guide configuration usage
15
+
16
+ ### Removed
17
+ - Removed delegation-specific permission checks (`isDelegationAllowedAgent`, `getDelegationBlockReason`) from permission evaluation
18
+
19
+ ### Tests
20
+ - Added comprehensive test coverage for tool permission lookup behavior
21
+
22
+ ## [0.2.1] - 2026-03-13
23
+
24
+ ### Added
25
+ - Extension configuration system (`config.json`) with `debugLog` and `permissionReviewLog` options
26
+ - JSONL debug logging to `logs/pi-permission-system-debug.jsonl` when `debugLog` is enabled
27
+ - JSONL permission review logging to `logs/pi-permission-system-permission-review.jsonl` for auditing
28
+ - Permission request event emission on `pi-permission-system:permission-request` channel for external consumers
29
+ - New `extension-config.ts` module for config file management and path resolution
30
+ - New `logging.ts` module with `createPermissionSystemLogger` for structured log output
31
+
32
+ ### Changed
33
+ - Replaced `console.warn`/`console.error` calls with structured logging to file
34
+ - Permission forwarding now logs request creation, response received, timeout, and user prompts
35
+ - Updated README documentation to cover extension config, logging, and event emission
36
+
8
37
  ## [0.2.0] - 2026-03-12
9
38
 
10
39
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🔐 pi-permission-system
2
2
 
3
- [![Version](https://img.shields.io/badge/version-0.2.0-blue.svg)](package.json)
3
+ [![Version](https://img.shields.io/badge/version-0.2.1-blue.svg)](package.json)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5
5
 
6
6
  Permission enforcement extension for the Pi coding agent that provides centralized, deterministic permission gates for tool, bash, MCP, skill, and special operations.
@@ -17,6 +17,8 @@ Permission enforcement extension for the Pi coding agent that provides centraliz
17
17
  - **Skill Protection** — Controls which skills can be loaded or read from disk
18
18
  - **Per-Agent Overrides** — Agent-specific permission policies via YAML frontmatter
19
19
  - **Subagent Permission Forwarding** — Forwards `ask` confirmations from non-UI subagents back to the main interactive session
20
+ - **File-Based Review Logging** — Writes permission request/denial review entries to a file by default for later auditing
21
+ - **Optional Debug Logging** — Keeps verbose extension diagnostics in a separate file when enabled in `config.json`
20
22
  - **JSON Schema Validation** — Full schema for editor autocomplete and config validation
21
23
 
22
24
  ## Installation
@@ -77,26 +79,46 @@ The extension integrates via Pi's lifecycle hooks:
77
79
  **Additional behaviors:**
78
80
  - Unknown/unregistered tools are blocked before permission checks (prevents bypass attempts)
79
81
  - The `Available tools:` system prompt section is rewritten to match the filtered active tool set
80
- - The `task` delegation tool is restricted to the `orchestrator` agent only
82
+ - Extension-provided tools like `task`, `mcp`, and third-party tools are handled by exact registered name instead of private built-in hardcodes
81
83
  - When a subagent hits an `ask` permission without direct UI access, the request can be forwarded to the main interactive session for confirmation
82
84
  - When a subagent triggers an `ask` permission without UI access, the request can be forwarded to the main session and answered there
83
85
 
84
86
  ## Configuration
85
87
 
88
+ ### Extension Config File
89
+
90
+ **Location:** `~/.pi/agent/extensions/pi-permission-system/config.json`
91
+
92
+ The extension creates this file automatically when it is missing. It controls only extension-local logging behavior:
93
+
94
+ ```json
95
+ {
96
+ "debugLog": false,
97
+ "permissionReviewLog": true
98
+ }
99
+ ```
100
+
101
+ | Key | Default | Description |
102
+ |-----|---------|-------------|
103
+ | `debugLog` | `false` | Enables verbose diagnostic logging to `logs/pi-permission-system-debug.jsonl` |
104
+ | `permissionReviewLog` | `true` | Enables the permission request/denial review log at `logs/pi-permission-system-permission-review.jsonl` |
105
+
106
+ Both logs write to files only under the extension directory. No debug output is printed to the terminal.
107
+
86
108
  ### Global Policy File
87
109
 
88
110
  **Location:** `~/.pi/agent/pi-permissions.jsonc`
89
111
 
90
112
  The policy file is a JSON object with these sections:
91
113
 
92
- | Section | Description |
93
- |-----------------|------------------------------------------|
94
- | `defaultPolicy` | Fallback permissions per category |
95
- | `tools` | Built-in tool permissions |
96
- | `bash` | Command pattern permissions |
97
- | `mcp` | MCP server/tool permissions |
98
- | `skills` | Skill name pattern permissions |
99
- | `special` | Reserved permission checks |
114
+ | Section | Description |
115
+ |-----------------|-----------------------------------------------------|
116
+ | `defaultPolicy` | Fallback permissions per category |
117
+ | `tools` | Exact-name tool permissions for registered tools |
118
+ | `bash` | Command pattern permissions |
119
+ | `mcp` | MCP server/tool permissions for calls routed through a registered `mcp` tool |
120
+ | `skills` | Skill name pattern permissions |
121
+ | `special` | Reserved permission checks |
100
122
 
101
123
  > **Note:** Trailing commas are **not** supported. If parsing fails, the extension falls back to `ask` for all categories.
102
124
 
@@ -125,7 +147,7 @@ permission:
125
147
 
126
148
  **Precedence:** Agent frontmatter overrides global config (shallow-merged per section).
127
149
 
128
- **MCP behavior:** `permission.tools.mcp` is the coarse entry/fallback permission for the built-in `mcp` tool. More specific `permission.mcp` target rules override that fallback when they match.
150
+ **MCP behavior:** `permission.tools.mcp` is the coarse entry/fallback permission for a registered `mcp` tool when one is available. More specific `permission.mcp` target rules override that fallback when they match.
129
151
 
130
152
  **Limitations:** The frontmatter parser is intentionally minimal. Use only `key: value` scalars and nested maps. Avoid arrays, multi-line scalars, and YAML anchors.
131
153
 
@@ -151,33 +173,34 @@ Sets fallback permissions when no specific rule matches:
151
173
 
152
174
  ### `tools`
153
175
 
154
- Controls built-in tools by exact name (no wildcards):
176
+ Controls tools by exact registered name (no wildcards). This is the recommended standalone format for **all** tool entries, including Pi built-ins and arbitrary third-party extension tools.
155
177
 
156
- | Tool | Description |
157
- |---------|--------------------------------|
158
- | `bash` | Shell command execution |
159
- | `read` | File reading |
160
- | `write` | File creation/overwriting |
161
- | `edit` | Surgical file edits |
162
- | `grep` | Pattern searching |
163
- | `find` | File discovery |
164
- | `ls` | Directory listing |
165
- | `mcp` | MCP proxy tool entry/fallback |
178
+ | Tool name example | Description |
179
+ |-----------------------|-------------|
180
+ | `bash` | Shell command execution (tool-level fallback before `bash` pattern rules) |
181
+ | `read` / `write` | Canonical Pi built-in file tools |
182
+ | `mcp` | Registered MCP proxy tool entry/fallback when available |
183
+ | `task` | Delegation tool handled like any other registered extension tool |
184
+ | `third_party_tool` | Arbitrary registered extension tool |
166
185
 
167
186
  ```jsonc
168
187
  {
169
188
  "tools": {
170
189
  "read": "allow",
171
190
  "write": "deny",
172
- "edit": "deny",
173
- "mcp": "allow"
191
+ "mcp": "allow",
192
+ "third_party_tool": "ask"
174
193
  }
175
194
  }
176
195
  ```
177
196
 
197
+ Unknown or absent tools are not required in the config. If another extension is not installed, its tool simply will not be registered at runtime, and this extension will block attempts to call that missing tool before permission checks run.
198
+
178
199
  > **Note:** Setting `tools.bash` affects the *default* for bash commands, but `bash` patterns can provide command-level overrides.
179
200
  >
180
- > **Note:** Setting `tools.mcp` controls coarse access to the built-in `mcp` tool. Specific `mcp` rules still override it when a target pattern matches.
201
+ > **Note:** Setting `tools.mcp` controls coarse access to a registered `mcp` tool when one is available. Specific `mcp` rules still override it when a target pattern matches.
202
+ >
203
+ > **Note:** Top-level shorthand is only supported for the canonical Pi built-ins (`bash`, `read`, `write`, `edit`, `grep`, `find`, `ls`) in agent frontmatter. Use `permission.tools.<name>` for `mcp`, `task`, and any third-party tool.
181
204
 
182
205
  ### `bash`
183
206
 
@@ -219,7 +242,7 @@ MCP permissions match against derived targets from tool input. These rules are m
219
242
 
220
243
  #### MCP Tool Fallback via `tools.mcp`
221
244
 
222
- The `mcp` built-in tool can use `tools.mcp` as an entry permission point. This provides a fallback when no specific MCP pattern matches:
245
+ A registered `mcp` tool can use `tools.mcp` as an entry permission point. This provides a fallback when no specific MCP pattern matches:
223
246
 
224
247
  ```jsonc
225
248
  {
@@ -352,12 +375,25 @@ When a delegated or routed subagent runs without direct UI access, `ask` permiss
352
375
 
353
376
  This keeps `ask` policies usable even when the original permission check happens inside a non-UI execution context.
354
377
 
378
+ ### Logging
379
+
380
+ When the extension prompts, denies, or forwards permission requests, it can append structured JSONL entries under:
381
+
382
+ ```text
383
+ ~/.pi/agent/extensions/pi-permission-system/logs/
384
+ ```
385
+
386
+ - `pi-permission-system-permission-review.jsonl` — enabled by default for permission review/audit history
387
+ - `pi-permission-system-debug.jsonl` — disabled by default and intended for troubleshooting
388
+
355
389
  ### Architecture
356
390
 
357
391
  ```
358
392
  index.ts → Root Pi entrypoint shim
359
393
  src/
360
- ├── index.ts → Extension bootstrap, permission checks, and subagent forwarding
394
+ ├── index.ts → Extension bootstrap, permission checks, review logging, and subagent forwarding
395
+ ├── extension-config.ts → Extension-local config loading and default creation
396
+ ├── logging.ts → File-only debug/review logging helpers
361
397
  ├── permission-manager.ts → Policy loading, merging, and resolution with caching
362
398
  ├── bash-filter.ts → Bash command wildcard pattern matching
363
399
  ├── wildcard-matcher.ts → Shared wildcard pattern compilation and matching
@@ -366,9 +402,9 @@ src/
366
402
  ├── types.ts → TypeScript type definitions
367
403
  └── test.ts → Test runner
368
404
  schemas/
369
- └── permissions.schema.json → JSON Schema for config validation
405
+ └── permissions.schema.json → JSON Schema for policy validation
370
406
  config/
371
- └── config.example.json → Starter configuration template
407
+ └── config.example.json → Starter global policy template
372
408
  ```
373
409
 
374
410
  #### Module Organization
@@ -421,7 +457,7 @@ npx --yes ajv-cli@5 validate \
421
457
  |---------|-------|----------|
422
458
  | Config not applied (everything asks) | File not found or parse error | Verify file at `~/.pi/agent/pi-permissions.jsonc`; check for trailing commas |
423
459
  | Per-agent override not applied | Frontmatter parsing issue | Ensure `---` delimiters at file top; keep YAML simple; restart session |
424
- | Tool blocked as unregistered | Unknown tool name | Use built-in `mcp` tool for server tools: `{ "tool": "server:tool" }` |
460
+ | Tool blocked as unregistered | Unknown tool name | Use a registered `mcp` tool for server tools: `{ "tool": "server:tool" }` |
425
461
  | `/skill:<name>` blocked | Missing context or deny policy | Requires active agent context; `ask` behaves as block in headless mode |
426
462
 
427
463
  ---
package/config.json ADDED
@@ -0,0 +1,4 @@
1
+ {
2
+ "debugLog": false,
3
+ "permissionReviewLog": true
4
+ }
package/package.json CHANGED
@@ -1,59 +1,60 @@
1
- {
2
- "name": "pi-permission-system",
3
- "version": "0.2.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
- "config/config.example.json",
14
- "schemas/permissions.schema.json",
15
- "asset",
16
- "README.md",
17
- "CHANGELOG.md",
18
- "LICENSE"
19
- ],
20
- "scripts": {
21
- "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
22
- "lint": "npm run build",
23
- "test": "bun ./src/test.ts",
24
- "check": "npm run lint && npm run test"
25
- },
26
- "keywords": [
27
- "pi-package",
28
- "pi",
29
- "pi-extension",
30
- "permissions",
31
- "policy",
32
- "coding-agent"
33
- ],
34
- "author": "MasuRii",
35
- "license": "MIT",
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://github.com/MasuRii/pi-permission-system.git"
39
- },
40
- "homepage": "https://github.com/MasuRii/pi-permission-system#readme",
41
- "bugs": {
42
- "url": "https://github.com/MasuRii/pi-permission-system/issues"
43
- },
44
- "engines": {
45
- "node": ">=20"
46
- },
47
- "publishConfig": {
48
- "access": "public"
49
- },
50
- "pi": {
51
- "extensions": [
52
- "./index.ts"
53
- ]
54
- },
55
- "peerDependencies": {
56
- "@mariozechner/pi-coding-agent": "*",
57
- "@sinclair/typebox": "*"
58
- }
59
- }
1
+ {
2
+ "name": "pi-permission-system",
3
+ "version": "0.2.2",
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
+ "config.json",
14
+ "config/config.example.json",
15
+ "schemas/permissions.schema.json",
16
+ "asset",
17
+ "README.md",
18
+ "CHANGELOG.md",
19
+ "LICENSE"
20
+ ],
21
+ "scripts": {
22
+ "build": "npx --yes -p typescript@5.7.3 tsc -p tsconfig.json --noCheck",
23
+ "lint": "npm run build",
24
+ "test": "bun ./src/test.ts",
25
+ "check": "npm run lint && npm run test"
26
+ },
27
+ "keywords": [
28
+ "pi-package",
29
+ "pi",
30
+ "pi-extension",
31
+ "permissions",
32
+ "policy",
33
+ "coding-agent"
34
+ ],
35
+ "author": "MasuRii",
36
+ "license": "MIT",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/MasuRii/pi-permission-system.git"
40
+ },
41
+ "homepage": "https://github.com/MasuRii/pi-permission-system#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/MasuRii/pi-permission-system/issues"
44
+ },
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "pi": {
52
+ "extensions": [
53
+ "./index.ts"
54
+ ]
55
+ },
56
+ "peerDependencies": {
57
+ "@mariozechner/pi-coding-agent": "*",
58
+ "@sinclair/typebox": "*"
59
+ }
60
+ }
@@ -31,12 +31,14 @@
31
31
  }
32
32
  },
33
33
  "tools": {
34
+ "description": "Exact-name permissions for registered tools. Use this map for the canonical Pi built-ins and any extension-provided or third-party tools.",
34
35
  "$ref": "#/$defs/permissionMap"
35
36
  },
36
37
  "bash": {
37
38
  "$ref": "#/$defs/permissionMap"
38
39
  },
39
40
  "mcp": {
41
+ "description": "Pattern-based permissions for targets invoked through a registered `mcp` tool when available.",
40
42
  "$ref": "#/$defs/permissionMap"
41
43
  },
42
44
  "skills": {
@@ -0,0 +1,106 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { toRecord } from "./common.js";
6
+
7
+ export const EXTENSION_ID = "pi-permission-system";
8
+
9
+ export interface PermissionSystemExtensionConfig {
10
+ debugLog: boolean;
11
+ permissionReviewLog: boolean;
12
+ }
13
+
14
+ export interface PermissionSystemConfigLoadResult {
15
+ config: PermissionSystemExtensionConfig;
16
+ created: boolean;
17
+ warning?: string;
18
+ }
19
+
20
+ export const DEFAULT_EXTENSION_CONFIG: PermissionSystemExtensionConfig = {
21
+ debugLog: false,
22
+ permissionReviewLog: true,
23
+ };
24
+
25
+ export function resolveExtensionRoot(moduleUrl = import.meta.url): string {
26
+ return join(dirname(fileURLToPath(moduleUrl)), "..");
27
+ }
28
+
29
+ export const EXTENSION_ROOT = resolveExtensionRoot();
30
+ export const CONFIG_PATH = join(EXTENSION_ROOT, "config.json");
31
+ export const LOGS_DIR = join(EXTENSION_ROOT, "logs");
32
+ export const DEBUG_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-debug.jsonl`);
33
+ export const PERMISSION_REVIEW_LOG_PATH = join(LOGS_DIR, `${EXTENSION_ID}-permission-review.jsonl`);
34
+
35
+ function cloneDefaultConfig(): PermissionSystemExtensionConfig {
36
+ return {
37
+ debugLog: DEFAULT_EXTENSION_CONFIG.debugLog,
38
+ permissionReviewLog: DEFAULT_EXTENSION_CONFIG.permissionReviewLog,
39
+ };
40
+ }
41
+
42
+ function createDefaultConfigContent(): string {
43
+ return `${JSON.stringify(DEFAULT_EXTENSION_CONFIG, null, 2)}\n`;
44
+ }
45
+
46
+ function normalizeConfig(raw: unknown): PermissionSystemExtensionConfig {
47
+ const record = toRecord(raw);
48
+ return {
49
+ debugLog: record.debugLog === true,
50
+ permissionReviewLog: record.permissionReviewLog !== false,
51
+ };
52
+ }
53
+
54
+ function ensureConfigDirectory(configPath: string): void {
55
+ mkdirSync(dirname(configPath), { recursive: true });
56
+ }
57
+
58
+ export function ensurePermissionSystemConfig(configPath = CONFIG_PATH): { created: boolean; warning?: string } {
59
+ if (existsSync(configPath)) {
60
+ return { created: false };
61
+ }
62
+
63
+ try {
64
+ ensureConfigDirectory(configPath);
65
+ writeFileSync(configPath, createDefaultConfigContent(), "utf-8");
66
+ return { created: true };
67
+ } catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ return {
70
+ created: false,
71
+ warning: `Failed to initialize permission-system config at '${configPath}': ${message}`,
72
+ };
73
+ }
74
+ }
75
+
76
+ export function loadPermissionSystemConfig(configPath = CONFIG_PATH): PermissionSystemConfigLoadResult {
77
+ const ensureResult = ensurePermissionSystemConfig(configPath);
78
+
79
+ try {
80
+ const raw = readFileSync(configPath, "utf-8");
81
+ const parsed = JSON.parse(raw) as unknown;
82
+ const config = normalizeConfig(parsed);
83
+ return {
84
+ config,
85
+ created: ensureResult.created,
86
+ warning: ensureResult.warning,
87
+ };
88
+ } catch (error) {
89
+ const message = error instanceof Error ? error.message : String(error);
90
+ return {
91
+ config: cloneDefaultConfig(),
92
+ created: ensureResult.created,
93
+ warning: ensureResult.warning ?? `Failed to read permission-system config at '${configPath}': ${message}`,
94
+ };
95
+ }
96
+ }
97
+
98
+ export function ensurePermissionSystemLogsDirectory(logsDir = LOGS_DIR): string | undefined {
99
+ try {
100
+ mkdirSync(logsDir, { recursive: true });
101
+ return undefined;
102
+ } catch (error) {
103
+ const message = error instanceof Error ? error.message : String(error);
104
+ return `Failed to create permission-system log directory '${logsDir}': ${message}`;
105
+ }
106
+ }