@gotgenes/pi-permission-system 5.10.0 → 5.11.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 (39) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/package.json +5 -5
  3. package/src/active-agent.ts +1 -1
  4. package/src/config-modal.ts +2 -2
  5. package/src/forwarded-permissions/polling.ts +1 -1
  6. package/src/forwarding-manager.ts +2 -2
  7. package/src/handlers/before-agent-start.ts +74 -61
  8. package/src/handlers/gates/descriptor.ts +1 -1
  9. package/src/handlers/index.ts +6 -15
  10. package/src/handlers/lifecycle.ts +55 -43
  11. package/src/handlers/permission-gate-handler.ts +346 -0
  12. package/src/index.ts +34 -39
  13. package/src/permission-event-rpc.ts +1 -1
  14. package/src/permission-prompter.ts +24 -7
  15. package/src/permission-session.ts +30 -1
  16. package/src/policy-loader.ts +1 -1
  17. package/src/runtime.ts +1 -1
  18. package/src/session-logger.ts +1 -1
  19. package/src/status.ts +1 -1
  20. package/src/subagent-context.ts +1 -1
  21. package/src/tool-registry.ts +6 -0
  22. package/tests/active-agent.test.ts +1 -1
  23. package/tests/config-modal.test.ts +2 -2
  24. package/tests/forwarding-manager.test.ts +1 -1
  25. package/tests/handlers/before-agent-start.test.ts +73 -93
  26. package/tests/handlers/gates/skill-read.test.ts +2 -2
  27. package/tests/handlers/input-events.test.ts +71 -64
  28. package/tests/handlers/input.test.ts +86 -84
  29. package/tests/handlers/lifecycle.test.ts +61 -73
  30. package/tests/handlers/tool-call-events.test.ts +129 -123
  31. package/tests/handlers/tool-call.test.ts +87 -61
  32. package/tests/permission-event-rpc.test.ts +1 -1
  33. package/tests/permission-prompter.test.ts +2 -2
  34. package/tests/permission-session.test.ts +62 -1
  35. package/tests/runtime.test.ts +1 -1
  36. package/tests/subagent-context.test.ts +1 -1
  37. package/src/handlers/input.ts +0 -126
  38. package/src/handlers/tool-call.ts +0 -203
  39. package/src/handlers/types.ts +0 -63
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ 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
+ ## [5.11.1](https://github.com/gotgenes/pi-permission-system/compare/v5.11.0...v5.11.1) (2026-05-08)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * **retro:** add retro notes for issue [#130](https://github.com/gotgenes/pi-permission-system/issues/130) ([e2ed7cb](https://github.com/gotgenes/pi-permission-system/commit/e2ed7cbbabe2dabf5689704c14b59fc662c1e7d4))
14
+
15
+
16
+ ### Miscellaneous Chores
17
+
18
+ * migrate @mariozechner/* deps to @earendil-works/* ([8908be1](https://github.com/gotgenes/pi-permission-system/commit/8908be17624a60ff3272b9e0e0a720a239de2de5))
19
+
20
+ ## [5.11.0](https://github.com/gotgenes/pi-permission-system/compare/v5.10.0...v5.11.0) (2026-05-08)
21
+
22
+
23
+ ### Features
24
+
25
+ * add ToolRegistry interface ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([5e886fd](https://github.com/gotgenes/pi-permission-system/commit/5e886fd4bc67ddac56a7f2b7b445f6f172e60668))
26
+ * PermissionSession absorbs prompting methods ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([4ae81e6](https://github.com/gotgenes/pi-permission-system/commit/4ae81e6e32164926202633ec7831a4f3db69fc70))
27
+
28
+
29
+ ### Documentation
30
+
31
+ * plan handler classes to replace HandlerDeps ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([e8bc1a4](https://github.com/gotgenes/pi-permission-system/commit/e8bc1a40596aa6a032b367e642d19a03ca622394))
32
+ * **retro:** add retro notes for issue [#129](https://github.com/gotgenes/pi-permission-system/issues/129) ([23c29a2](https://github.com/gotgenes/pi-permission-system/commit/23c29a2148f6647565e6997d9c56341b43e74118))
33
+ * update architecture for handler classes ([#130](https://github.com/gotgenes/pi-permission-system/issues/130)) ([02d02b6](https://github.com/gotgenes/pi-permission-system/commit/02d02b647ec366eee3dc572afbaf436b1264b052))
34
+
8
35
  ## [5.10.0](https://github.com/gotgenes/pi-permission-system/compare/v5.9.0...v5.10.0) (2026-05-08)
9
36
 
10
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "5.10.0",
3
+ "version": "5.11.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -48,13 +48,13 @@
48
48
  ]
49
49
  },
50
50
  "peerDependencies": {
51
- "@mariozechner/pi-coding-agent": "*",
52
- "@mariozechner/pi-tui": "*"
51
+ "@earendil-works/pi-coding-agent": "*",
52
+ "@earendil-works/pi-tui": "*"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@biomejs/biome": "^2.4.13",
56
- "@mariozechner/pi-coding-agent": "^0.72.1",
57
- "@mariozechner/pi-tui": "^0.72.1",
56
+ "@earendil-works/pi-coding-agent": "^0.74.0",
57
+ "@earendil-works/pi-tui": "^0.74.0",
58
58
  "@types/node": "^25.6.0",
59
59
  "markdownlint-cli2": "^0.22.1",
60
60
  "typescript": "6.0.3",
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  /**
4
4
  * Matches the `<active_agent name="...">` tag injected by pi-agent-router
@@ -2,8 +2,8 @@ import {
2
2
  type ExtensionAPI,
3
3
  type ExtensionCommandContext,
4
4
  getSettingsListTheme,
5
- } from "@mariozechner/pi-coding-agent";
6
- import { type SettingItem, SettingsList } from "@mariozechner/pi-tui";
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { type SettingItem, SettingsList } from "@earendil-works/pi-tui";
7
7
 
8
8
  import {
9
9
  DEFAULT_EXTENSION_CONFIG,
@@ -1,6 +1,6 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
3
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
4
 
5
5
  import {
6
6
  getActiveAgentName,
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  import type { PermissionForwardingDeps } from "./forwarded-permissions/polling";
4
4
  import { processForwardedPermissionRequests } from "./forwarded-permissions/polling";
@@ -6,7 +6,7 @@ import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding"
6
6
  import { isSubagentExecutionContext } from "./subagent-context";
7
7
 
8
8
  /**
9
- * Narrow interface for the forwarding lifecycle used by `HandlerDeps`.
9
+ * Narrow interface for the forwarding lifecycle used by `PermissionSession`.
10
10
  * `ForwardingManager` satisfies it; tests can provide a plain object mock.
11
11
  */
12
12
  export interface ForwardingController {
@@ -1,22 +1,22 @@
1
1
  import type {
2
2
  BeforeAgentStartEventResult,
3
3
  ExtensionContext,
4
- } from "@mariozechner/pi-coding-agent";
5
-
6
- /** Minimal subset of BeforeAgentStartEvent used by this handler. */
7
- interface BeforeAgentStartPayload {
8
- systemPrompt: string;
9
- }
4
+ } from "@earendil-works/pi-coding-agent";
10
5
 
11
6
  import {
12
7
  createActiveToolsCacheKey,
13
8
  createBeforeAgentStartPromptStateKey,
14
9
  } from "../before-agent-start-cache";
10
+ import type { PermissionSession } from "../permission-session";
15
11
  import { resolveSkillPromptEntries } from "../skill-prompt-sanitizer";
16
12
  import { sanitizeAvailableToolsSection } from "../system-prompt-sanitizer";
17
- import { getToolNameFromValue } from "../tool-registry";
13
+ import { getToolNameFromValue, type ToolRegistry } from "../tool-registry";
18
14
  import type { PermissionState } from "../types";
19
- import type { HandlerDeps } from "./types";
15
+
16
+ /** Minimal subset of BeforeAgentStartEvent used by this handler. */
17
+ interface BeforeAgentStartPayload {
18
+ systemPrompt: string;
19
+ }
20
20
 
21
21
  /**
22
22
  * Pure helper: returns true when the tool should be exposed to the agent.
@@ -32,68 +32,81 @@ export function shouldExposeTool(
32
32
  return toolPermission !== "deny";
33
33
  }
34
34
 
35
- export async function handleBeforeAgentStart(
36
- deps: HandlerDeps,
37
- event: BeforeAgentStartPayload,
38
- ctx: ExtensionContext,
39
- ): Promise<BeforeAgentStartEventResult> {
40
- const { session } = deps;
41
- session.activate(ctx);
42
- session.refreshConfig(ctx);
35
+ /**
36
+ * Handles the `before_agent_start` event: tool filtering + prompt sanitization.
37
+ *
38
+ * Constructor deps:
39
+ * - `session` — encapsulates all mutable session state
40
+ * - `toolRegistry` Pi tool API subset (getAll + setActive)
41
+ */
42
+ export class AgentPrepHandler {
43
+ constructor(
44
+ private readonly session: PermissionSession,
45
+ private readonly toolRegistry: ToolRegistry,
46
+ ) {}
47
+
48
+ async handle(
49
+ event: BeforeAgentStartPayload,
50
+ ctx: ExtensionContext,
51
+ ): Promise<BeforeAgentStartEventResult> {
52
+ const { session } = this;
53
+ session.activate(ctx);
54
+ session.refreshConfig(ctx);
43
55
 
44
- const agentName = session.resolveAgentName(ctx, event.systemPrompt);
45
- const allTools = deps.getAllTools();
46
- const allowedTools: string[] = [];
56
+ const agentName = session.resolveAgentName(ctx, event.systemPrompt);
57
+ const allTools = this.toolRegistry.getAll();
58
+ const allowedTools: string[] = [];
47
59
 
48
- for (const tool of allTools) {
49
- const toolName = getToolNameFromValue(tool);
50
- if (!toolName) {
51
- continue;
60
+ for (const tool of allTools) {
61
+ const toolName = getToolNameFromValue(tool);
62
+ if (!toolName) {
63
+ continue;
64
+ }
65
+ if (
66
+ shouldExposeTool(toolName, agentName, (t, a) =>
67
+ session.getToolPermission(t, a),
68
+ )
69
+ ) {
70
+ allowedTools.push(toolName);
71
+ }
52
72
  }
53
- if (
54
- shouldExposeTool(toolName, agentName, (t, a) =>
55
- session.getToolPermission(t, a),
56
- )
57
- ) {
58
- allowedTools.push(toolName);
73
+
74
+ const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
75
+ if (session.shouldUpdateActiveTools(activeToolsCacheKey)) {
76
+ this.toolRegistry.setActive(allowedTools);
77
+ session.commitActiveToolsCacheKey(activeToolsCacheKey);
59
78
  }
60
- }
61
79
 
62
- const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
63
- if (session.shouldUpdateActiveTools(activeToolsCacheKey)) {
64
- deps.setActiveTools(allowedTools);
65
- session.commitActiveToolsCacheKey(activeToolsCacheKey);
66
- }
80
+ const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
81
+ agentName,
82
+ cwd: ctx.cwd,
83
+ permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
84
+ systemPrompt: event.systemPrompt,
85
+ allowedToolNames: allowedTools,
86
+ });
67
87
 
68
- const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
69
- agentName,
70
- cwd: ctx.cwd,
71
- permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
72
- systemPrompt: event.systemPrompt,
73
- allowedToolNames: allowedTools,
74
- });
88
+ if (!session.shouldUpdatePromptState(promptStateCacheKey)) {
89
+ return {};
90
+ }
75
91
 
76
- if (!session.shouldUpdatePromptState(promptStateCacheKey)) {
77
- return {};
78
- }
92
+ session.commitPromptStateCacheKey(promptStateCacheKey);
79
93
 
80
- session.commitPromptStateCacheKey(promptStateCacheKey);
94
+ const toolPromptResult = sanitizeAvailableToolsSection(
95
+ event.systemPrompt,
96
+ allowedTools,
97
+ );
98
+ const skillPromptResult = resolveSkillPromptEntries(
99
+ toolPromptResult.prompt,
100
+ session,
101
+ agentName,
102
+ ctx.cwd,
103
+ );
104
+ session.setActiveSkillEntries(skillPromptResult.entries);
81
105
 
82
- const toolPromptResult = sanitizeAvailableToolsSection(
83
- event.systemPrompt,
84
- allowedTools,
85
- );
86
- const skillPromptResult = resolveSkillPromptEntries(
87
- toolPromptResult.prompt,
88
- session,
89
- agentName,
90
- ctx.cwd,
91
- );
92
- session.setActiveSkillEntries(skillPromptResult.entries);
106
+ if (skillPromptResult.prompt !== event.systemPrompt) {
107
+ return { systemPrompt: skillPromptResult.prompt };
108
+ }
93
109
 
94
- if (skillPromptResult.prompt !== event.systemPrompt) {
95
- return { systemPrompt: skillPromptResult.prompt };
110
+ return {};
96
111
  }
97
-
98
- return {};
99
112
  }
@@ -3,9 +3,9 @@ import type {
3
3
  PermissionDecisionEvent,
4
4
  PermissionDecisionResolution,
5
5
  } from "../../permission-events";
6
+ import type { PromptPermissionDetails } from "../../permission-prompter";
6
7
  import type { Rule } from "../../rule";
7
8
  import type { PermissionCheckResult, PermissionState } from "../../types";
8
- import type { PromptPermissionDetails } from "../types";
9
9
 
10
10
  // ── Descriptor types ───────────────────────────────────────────────────────
11
11
 
@@ -1,16 +1,7 @@
1
+ export { AgentPrepHandler, shouldExposeTool } from "./before-agent-start";
2
+ export { SessionLifecycleHandler } from "./lifecycle";
1
3
  export {
2
- handleBeforeAgentStart,
3
- shouldExposeTool,
4
- } from "./before-agent-start";
5
- export { extractSkillNameFromInput, handleInput } from "./input";
6
- export {
7
- handleResourcesDiscover,
8
- handleSessionShutdown,
9
- handleSessionStart,
10
- } from "./lifecycle";
11
- export { getEventInput, handleToolCall } from "./tool-call";
12
- export type {
13
- HandlerDeps,
14
- PermissionReviewSource,
15
- PromptPermissionDetails,
16
- } from "./types";
4
+ extractSkillNameFromInput,
5
+ getEventInput,
6
+ PermissionGateHandler,
7
+ } from "./permission-gate-handler";
@@ -1,7 +1,7 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
+ import type { PermissionSession } from "../permission-session";
3
4
  import { PERMISSION_SYSTEM_STATUS_KEY } from "../status";
4
- import type { HandlerDeps } from "./types";
5
5
 
6
6
  /** Minimal subset of SessionStartEvent used by this handler. */
7
7
  interface SessionStartPayload {
@@ -13,54 +13,66 @@ interface ResourcesDiscoverPayload {
13
13
  reason: string;
14
14
  }
15
15
 
16
- export async function handleSessionStart(
17
- deps: HandlerDeps,
18
- event: SessionStartPayload,
19
- ctx: ExtensionContext,
20
- ): Promise<void> {
21
- const { session } = deps;
22
- session.refreshConfig(ctx);
23
- session.resetForNewSession(ctx);
24
- session.logResolvedConfigPaths();
16
+ /**
17
+ * Handles session lifecycle events: start, reload, and shutdown.
18
+ *
19
+ * Constructor deps:
20
+ * - `session` — encapsulates all mutable session state
21
+ * - `cleanupRpc` unsubscribes RPC handlers on shutdown
22
+ */
23
+ export class SessionLifecycleHandler {
24
+ constructor(
25
+ private readonly session: PermissionSession,
26
+ private readonly cleanupRpc: () => void,
27
+ ) {}
25
28
 
26
- const agentName = session.resolveAgentName(ctx);
27
- const policyIssues = session.getConfigIssues(agentName);
28
- for (const issue of policyIssues) {
29
- session.logger.warn(issue);
29
+ async handleSessionStart(
30
+ event: SessionStartPayload,
31
+ ctx: ExtensionContext,
32
+ ): Promise<void> {
33
+ const { session } = this;
34
+ session.refreshConfig(ctx);
35
+ session.resetForNewSession(ctx);
36
+ session.logResolvedConfigPaths();
37
+
38
+ const agentName = session.resolveAgentName(ctx);
39
+ const policyIssues = session.getConfigIssues(agentName);
40
+ for (const issue of policyIssues) {
41
+ session.logger.warn(issue);
42
+ }
43
+
44
+ if (event.reason === "reload") {
45
+ session.logger.debug("lifecycle.reload", {
46
+ triggeredBy: "session_start",
47
+ reason: event.reason,
48
+ cwd: ctx.cwd,
49
+ });
50
+ }
30
51
  }
31
52
 
32
- if (event.reason === "reload") {
53
+ async handleResourcesDiscover(
54
+ event: ResourcesDiscoverPayload,
55
+ ): Promise<void> {
56
+ if (event.reason !== "reload") {
57
+ return;
58
+ }
59
+
60
+ const { session } = this;
61
+ session.reload();
33
62
  session.logger.debug("lifecycle.reload", {
34
- triggeredBy: "session_start",
63
+ triggeredBy: "resources_discover",
35
64
  reason: event.reason,
36
- cwd: ctx.cwd,
65
+ cwd: session.getRuntimeContext()?.cwd ?? null,
37
66
  });
38
67
  }
39
- }
40
-
41
- export async function handleResourcesDiscover(
42
- deps: HandlerDeps,
43
- event: ResourcesDiscoverPayload,
44
- ): Promise<void> {
45
- if (event.reason !== "reload") {
46
- return;
47
- }
48
-
49
- const { session } = deps;
50
- session.reload();
51
- session.logger.debug("lifecycle.reload", {
52
- triggeredBy: "resources_discover",
53
- reason: event.reason,
54
- cwd: session.getRuntimeContext()?.cwd ?? null,
55
- });
56
- }
57
68
 
58
- export async function handleSessionShutdown(deps: HandlerDeps): Promise<void> {
59
- const { session } = deps;
60
- const ctx = session.getRuntimeContext();
61
- if (ctx) {
62
- ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
69
+ async handleSessionShutdown(): Promise<void> {
70
+ const { session } = this;
71
+ const ctx = session.getRuntimeContext();
72
+ if (ctx) {
73
+ ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
74
+ }
75
+ session.shutdown();
76
+ this.cleanupRpc();
63
77
  }
64
- session.shutdown();
65
- deps.stopPermissionRpcHandlers();
66
78
  }