@gotgenes/pi-permission-system 7.3.0 → 7.3.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ 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
+ ## [7.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.3.0...pi-permission-system-v7.3.1) (2026-05-26)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * resolve pre-existing lint errors in pi-autoformat and pi-permission-system ([68fd516](https://github.com/gotgenes/pi-packages/commit/68fd516e33ddbb9a5e37ef19e949ee9ecdc37252))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * update subagent integration docs for native permission bridge ([#101](https://github.com/gotgenes/pi-packages/issues/101)) ([0bd456b](https://github.com/gotgenes/pi-packages/commit/0bd456befa8ea6918e74f4393d844868795edc77))
19
+
8
20
  ## [7.3.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v7.2.0...pi-permission-system-v7.3.0) (2026-05-25)
9
21
 
10
22
 
package/README.md CHANGED
@@ -20,6 +20,7 @@ Permission enforcement extension for the [Pi](https://pi.mariozechner.at/) codin
20
20
  - **Protects sensitive file patterns** — cross-cutting `path` rules deny `.env`, `~/.ssh/*`, etc. across all tools and bash at once
21
21
  - **Guards external paths** — prompts before file tools or bash commands reach outside `cwd`
22
22
  - **Forwards prompts from subagents** — `ask` policies work even in non-UI execution contexts
23
+ - **Native [`@gotgenes/pi-subagents`](https://github.com/gotgenes/pi-subagents) integration** — in-process child sessions register with the permission system automatically, enabling per-agent policy enforcement and `ask`-state forwarding to the parent UI without configuration
23
24
 
24
25
  ## Install
25
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "7.3.0",
3
+ "version": "7.3.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,4 +1,4 @@
1
- import { existsSync, mkdirSync } from "node:fs";
1
+ import { mkdirSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
@@ -41,6 +41,7 @@ export function shouldExposeTool(
41
41
  */
42
42
  export class AgentPrepHandler {
43
43
  constructor(
44
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
44
45
  private readonly session: PermissionSession,
45
46
  private readonly toolRegistry: ToolRegistry,
46
47
  ) {}
@@ -48,14 +48,6 @@ function getParser(): Promise<TSParser> {
48
48
  return parserPromise;
49
49
  }
50
50
 
51
- /**
52
- * Reset the cached parser promise. Only used by tests to avoid
53
- * cross-test pollution or to inject a mock parser.
54
- */
55
- function resetParserForTesting(): void {
56
- parserPromise = null;
57
- }
58
-
59
51
  // ── AST walker ─────────────────────────────────────────────────────────────
60
52
 
61
53
  /**
@@ -22,6 +22,7 @@ interface ResourcesDiscoverPayload {
22
22
  */
23
23
  export class SessionLifecycleHandler {
24
24
  constructor(
25
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
25
26
  private readonly session: PermissionSession,
26
27
  private readonly cleanupRpc: () => void,
27
28
  ) {}
@@ -47,6 +47,7 @@ interface InputPayload {
47
47
  */
48
48
  export class PermissionGateHandler {
49
49
  constructor(
50
+ // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via destructuring (const { session } = this)
50
51
  private readonly session: PermissionSession,
51
52
  private readonly events: PermissionEventBus,
52
53
  private readonly toolRegistry: ToolRegistry,
@@ -25,12 +25,6 @@ const APPROVE_OPTION = "Yes";
25
25
  const APPROVE_FOR_SESSION_OPTION = "Yes, for this session";
26
26
  const DENY_OPTION = "No";
27
27
  const DENY_WITH_REASON_OPTION = "No, provide reason";
28
- const PERMISSION_DECISION_OPTIONS = [
29
- APPROVE_OPTION,
30
- APPROVE_FOR_SESSION_OPTION,
31
- DENY_OPTION,
32
- DENY_WITH_REASON_OPTION,
33
- ] as const;
34
28
 
35
29
  export function normalizePermissionDenialReason(
36
30
  value: unknown,
@@ -120,7 +120,7 @@ export class PermissionManager {
120
120
  // existing patterns from lower scopes keep their earlier origin.
121
121
  if (!origins.has(surface)) origins.set(surface, new Map());
122
122
  for (const pattern of Object.keys(value)) {
123
- origins.get(surface)!.set(pattern, scopeName);
123
+ origins.get(surface)?.set(pattern, scopeName);
124
124
  }
125
125
  } else {
126
126
  // Full replacement: this scope takes over the entire surface entry.
@@ -1,4 +1,4 @@
1
- import type { FlatPermissionConfig, PermissionState } from "./types";
1
+ import type { FlatPermissionConfig } from "./types";
2
2
 
3
3
  /**
4
4
  * Deep-shallow merge two flat permission configs.
@@ -92,33 +92,6 @@ function parseSkillEntries(sectionBody: string): ParsedSkillPromptEntry[] {
92
92
  return entries;
93
93
  }
94
94
 
95
- function parseSkillPromptSection(prompt: string): SkillPromptSection | null {
96
- const start = prompt.indexOf(AVAILABLE_SKILLS_OPEN_TAG);
97
- if (start === -1) {
98
- return null;
99
- }
100
-
101
- const closeStart = prompt.indexOf(
102
- AVAILABLE_SKILLS_CLOSE_TAG,
103
- start + AVAILABLE_SKILLS_OPEN_TAG.length,
104
- );
105
- if (closeStart === -1) {
106
- return null;
107
- }
108
-
109
- const end = closeStart + AVAILABLE_SKILLS_CLOSE_TAG.length;
110
- const sectionBody = prompt.slice(
111
- start + AVAILABLE_SKILLS_OPEN_TAG.length,
112
- closeStart,
113
- );
114
-
115
- return {
116
- start,
117
- end,
118
- entries: parseSkillEntries(sectionBody),
119
- };
120
- }
121
-
122
95
  export function parseAllSkillPromptSections(
123
96
  prompt: string,
124
97
  ): SkillPromptSection[] {
package/src/types.ts CHANGED
@@ -14,17 +14,6 @@ export type FlatPermissionConfig = Record<
14
14
  PermissionState | Record<string, PermissionState>
15
15
  >;
16
16
 
17
- type BuiltInToolName =
18
- | "bash"
19
- | "read"
20
- | "write"
21
- | "edit"
22
- | "grep"
23
- | "find"
24
- | "ls";
25
-
26
- type SpecialPermissionName = "external_directory";
27
-
28
17
  /**
29
18
  * Per-scope permission config shape after loading and validation.
30
19
  * Holds only the flat permission map — all policy is expressed there.
@@ -7,7 +7,6 @@ import {
7
7
  } from "#src/handlers/before-agent-start";
8
8
  import type { PermissionSession } from "#src/permission-session";
9
9
  import type { ToolRegistry } from "#src/tool-registry";
10
- import type { PermissionState } from "#src/types";
11
10
 
12
11
  // ── SDK stubs ──────────────────────────────────────────────────────────────
13
12
  vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
@@ -101,12 +101,15 @@ describe("describeBashExternalDirectoryGate", () => {
101
101
  it("uses config-level checkPermission for the policy state", async () => {
102
102
  const checkPermission = vi
103
103
  .fn()
104
- .mockImplementation((surface: string, input: Record<string, unknown>) => {
105
- // Path-specific check returns session for coverage filtering
106
- if (input.path) return makeCheckResult("allow", { source: "special" });
107
- // Config-level check (no path) returns deny
108
- return makeCheckResult("deny");
109
- });
104
+ .mockImplementation(
105
+ (_surface: string, input: Record<string, unknown>) => {
106
+ // Path-specific check returns session for coverage filtering
107
+ if (input.path)
108
+ return makeCheckResult("allow", { source: "special" });
109
+ // Config-level check (no path) returns deny
110
+ return makeCheckResult("deny");
111
+ },
112
+ );
110
113
  const result = await describeBashExternalDirectoryGate(
111
114
  makeTcc(),
112
115
  checkPermission,
@@ -172,12 +175,14 @@ describe("describeBashExternalDirectoryGate", () => {
172
175
  it("only includes uncovered paths when some are session-covered", async () => {
173
176
  const checkPermission = vi
174
177
  .fn()
175
- .mockImplementation((surface: string, input: Record<string, unknown>) => {
176
- if (input.path === "/outside/a.ts") {
177
- return makeCheckResult("allow", { source: "session" });
178
- }
179
- return makeCheckResult("ask");
180
- });
178
+ .mockImplementation(
179
+ (_surface: string, input: Record<string, unknown>) => {
180
+ if (input.path === "/outside/a.ts") {
181
+ return makeCheckResult("allow", { source: "session" });
182
+ }
183
+ return makeCheckResult("ask");
184
+ },
185
+ );
181
186
  const result = await describeBashExternalDirectoryGate(
182
187
  makeTcc({ input: { command: "diff /outside/a.ts /outside/b.ts" } }),
183
188
  checkPermission,
@@ -1,6 +1,4 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
-
3
- import type { GateDescriptor } from "#src/handlers/gates/descriptor";
4
2
  import { describeSkillReadGate } from "#src/handlers/gates/skill-read";
5
3
  import type { ToolCallContext } from "#src/handlers/gates/types";
6
4
  import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
@@ -9,7 +9,6 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
9
9
  import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
10
10
  import type { PermissionSession } from "#src/permission-session";
11
11
  import type { ToolRegistry } from "#src/tool-registry";
12
- import type { PermissionState } from "#src/types";
13
12
 
14
13
  // ── helpers ────────────────────────────────────────────────────────────────
15
14
 
@@ -7,7 +7,6 @@ import {
7
7
  } from "#src/handlers/permission-gate-handler";
8
8
  import type { PermissionSession } from "#src/permission-session";
9
9
  import type { ToolRegistry } from "#src/tool-registry";
10
- import type { PermissionState } from "#src/types";
11
10
 
12
11
  // ── helpers ────────────────────────────────────────────────────────────────
13
12
 
@@ -10,7 +10,7 @@ import type { PermissionDecisionEvent } from "#src/permission-events";
10
10
  import { PERMISSIONS_DECISION_CHANNEL } from "#src/permission-events";
11
11
  import type { PermissionSession } from "#src/permission-session";
12
12
  import type { ToolRegistry } from "#src/tool-registry";
13
- import type { PermissionCheckResult, PermissionState } from "#src/types";
13
+ import type { PermissionCheckResult } from "#src/types";
14
14
 
15
15
  // ── helpers ────────────────────────────────────────────────────────────────
16
16
 
@@ -7,7 +7,7 @@ import {
7
7
  } from "#src/handlers/permission-gate-handler";
8
8
  import type { PermissionSession } from "#src/permission-session";
9
9
  import type { ToolRegistry } from "#src/tool-registry";
10
- import type { PermissionCheckResult, PermissionState } from "#src/types";
10
+ import type { PermissionCheckResult } from "#src/types";
11
11
 
12
12
  // ── SDK stubs ──────────────────────────────────────────────────────────────
13
13
  vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
@@ -264,7 +264,7 @@ describe("piPermissionSystemExtension ready event wiring", () => {
264
264
  mkdirSync(join(baseDir, "agents"), { recursive: true });
265
265
  writeFileSync(
266
266
  globalConfigPath,
267
- JSON.stringify({ permission: { "*": "ask" } }) + "\n",
267
+ `${JSON.stringify({ permission: { "*": "ask" } })}\n`,
268
268
  "utf8",
269
269
  );
270
270
  process.env.PI_CODING_AGENT_DIR = baseDir;
@@ -38,7 +38,6 @@ import {
38
38
  } from "#src/permission-session";
39
39
  import type { SessionLogger } from "#src/session-logger";
40
40
  import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
41
- import type { PermissionCheckResult } from "#src/types";
42
41
 
43
42
  function makeSkillEntry(
44
43
  name: string,