@gotgenes/pi-permission-system 19.0.1 → 20.1.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.
@@ -0,0 +1,87 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type { GatePrompter } from "#src/gate-prompter";
3
+ import type { PermissionPromptDecision } from "#src/permission-dialog";
4
+ import {
5
+ type Authorizer,
6
+ type AuthorizerSelectionDeps,
7
+ selectAuthorizer,
8
+ } from "./authorizer";
9
+ import type {
10
+ PermissionPrompterApi,
11
+ PromptPermissionDetails,
12
+ } from "./permission-prompter";
13
+
14
+ /**
15
+ * The lifecycle slice of the selection owner that PermissionSession drives.
16
+ *
17
+ * PermissionSession calls activate/deactivate to keep the selection's stored
18
+ * context in sync with its own — the same pattern the former
19
+ * PromptingGatewayLifecycle used.
20
+ */
21
+ export interface AuthorizerSelectionLifecycle {
22
+ activate(ctx: ExtensionContext): void;
23
+ deactivate(): void;
24
+ }
25
+
26
+ /**
27
+ * Context-owning selection root for the Authorizer spine.
28
+ *
29
+ * The rewrite of `PromptingGateway`: owns the stored `ExtensionContext`, runs
30
+ * `selectAuthorizer` once per activation, and implements `GatePrompter` by
31
+ * delegating to the selected `Authorizer` via `PermissionPrompter`.
32
+ *
33
+ * `canConfirm()` survives this step (dissolved in #556): it is recomputed
34
+ * transitionally alongside `selectAuthorizer`'s own branch, rather than
35
+ * derived from the selected authorizer, to keep the ask path byte-identical
36
+ * until #556 derives confirmability from a `DenyingAuthorizer` marker.
37
+ */
38
+ export class AuthorizerSelection
39
+ implements GatePrompter, AuthorizerSelectionLifecycle
40
+ {
41
+ private selected: Authorizer | null = null;
42
+ private confirmable = false;
43
+
44
+ constructor(
45
+ private readonly deps: AuthorizerSelectionDeps & {
46
+ prompter: PermissionPrompterApi;
47
+ },
48
+ ) {}
49
+
50
+ /** Select the Authorizer for `ctx` and store both it and the confirmable predicate. */
51
+ activate(ctx: ExtensionContext): void {
52
+ this.selected = selectAuthorizer(ctx, this.deps);
53
+ this.confirmable = ctx.hasUI || this.deps.detection.isSubagent(ctx);
54
+ }
55
+
56
+ /** Clear the stored selection. */
57
+ deactivate(): void {
58
+ this.selected = null;
59
+ this.confirmable = false;
60
+ }
61
+
62
+ /**
63
+ * Whether an interactive permission prompt can be shown.
64
+ *
65
+ * Returns false when no authorizer has been selected. Otherwise true when
66
+ * the context had UI or was a forwarding subagent at selection time — the
67
+ * two Authorizer-selection predicates, evaluated once at `activate`.
68
+ */
69
+ canConfirm(): boolean {
70
+ return this.selected !== null && this.confirmable;
71
+ }
72
+
73
+ /**
74
+ * Prompt for a permission decision using the selected authorizer.
75
+ *
76
+ * Rejects if no authorizer has been selected — `canConfirm()` guards this
77
+ * in normal use. Implements {@link GatePrompter}.
78
+ */
79
+ prompt(details: PromptPermissionDetails): Promise<PermissionPromptDecision> {
80
+ if (this.selected === null) {
81
+ return Promise.reject(
82
+ new Error("prompt called before the session was activated"),
83
+ );
84
+ }
85
+ return this.deps.prompter.prompt(this.selected, details);
86
+ }
87
+ }
@@ -0,0 +1,72 @@
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import type {
3
+ PermissionPromptDecision,
4
+ requestPermissionDecisionFromUi,
5
+ } from "#src/permission-dialog";
6
+ import type { PermissionEventBus } from "#src/permission-events";
7
+ import type { DebugReviewLogger } from "#src/session-logger";
8
+ import type { SubagentSessionRegistry } from "#src/subagent-registry";
9
+ import { ParentAuthorizer } from "./approval-escalator";
10
+ import { DenyingAuthorizer } from "./denying-authorizer";
11
+ import { LocalUserAuthorizer } from "./local-user-authorizer";
12
+ import type { PromptPermissionDetails } from "./permission-prompter";
13
+ import type { SubagentDetector } from "./subagent-detection";
14
+
15
+ /**
16
+ * The live-authority role: on `ask`, an `Authorizer` rules on a single
17
+ * request and is told the decision.
18
+ *
19
+ * One method, one responsibility. `DenyingAuthorizer` ignores `details`;
20
+ * `LocalUserAuthorizer` reads `message`/`sessionLabel` and derives the UI
21
+ * event from it; `ParentAuthorizer` reads `message` and derives the
22
+ * forwarded display from it.
23
+ */
24
+ export interface Authorizer {
25
+ authorize(
26
+ details: PromptPermissionDetails,
27
+ ): Promise<PermissionPromptDecision>;
28
+ }
29
+
30
+ /** Construction inputs for {@link selectAuthorizer}. */
31
+ export interface AuthorizerSelectionDeps {
32
+ /** Single owner of subagent detection; the ParentAuthorizer-selection predicate. */
33
+ detection: SubagentDetector;
34
+ /** Event bus used by `LocalUserAuthorizer` for the `permissions:ui_prompt` broadcast. */
35
+ events: PermissionEventBus;
36
+ /** Injected for testability; production callers pass the real function. */
37
+ requestPermissionDecisionFromUi: typeof requestPermissionDecisionFromUi;
38
+ /** Forwarding directory `ParentAuthorizer` reads/writes request and response files under. */
39
+ forwardingDir: string;
40
+ /** In-process subagent session registry for forwarding target resolution. */
41
+ registry?: SubagentSessionRegistry;
42
+ logger: DebugReviewLogger;
43
+ }
44
+
45
+ /**
46
+ * Select the `Authorizer` for the current context: the single owner of the
47
+ * three-way `hasUI` / `isSubagent` / deny dispatch.
48
+ *
49
+ * Evaluated once per session activation (`AuthorizerSelection.activate`),
50
+ * replacing the re-derivation of the same predicates across
51
+ * `PromptingGateway`, `PermissionPrompter`, and `ApprovalEscalator`.
52
+ */
53
+ export function selectAuthorizer(
54
+ ctx: ExtensionContext,
55
+ deps: AuthorizerSelectionDeps,
56
+ ): Authorizer {
57
+ if (ctx.hasUI) {
58
+ return new LocalUserAuthorizer({
59
+ ui: ctx.ui,
60
+ events: deps.events,
61
+ requestPermissionDecisionFromUi: deps.requestPermissionDecisionFromUi,
62
+ });
63
+ }
64
+ if (deps.detection.isSubagent(ctx)) {
65
+ return new ParentAuthorizer(ctx, {
66
+ forwardingDir: deps.forwardingDir,
67
+ registry: deps.registry,
68
+ logger: deps.logger,
69
+ });
70
+ }
71
+ return new DenyingAuthorizer();
72
+ }
@@ -0,0 +1,12 @@
1
+ import type { PermissionPromptDecision } from "#src/permission-dialog";
2
+ import type { Authorizer } from "./authorizer";
3
+
4
+ /**
5
+ * Least-privilege Authorizer: no authority is reachable for this session
6
+ * (no UI, not a subagent), so every ask is denied.
7
+ */
8
+ export class DenyingAuthorizer implements Authorizer {
9
+ authorize(): Promise<PermissionPromptDecision> {
10
+ return Promise.resolve({ approved: false, state: "denied" });
11
+ }
12
+ }
@@ -24,7 +24,6 @@ const UI_PROMPT_SOURCES = [
24
24
  "tool_call",
25
25
  "skill_input",
26
26
  "skill_read",
27
- "rpc_prompt",
28
27
  ] as const satisfies readonly PermissionUiPromptSource[];
29
28
 
30
29
  /** Narrow an unknown value to a valid prompt source, or `undefined`. */
@@ -0,0 +1,46 @@
1
+ import type {
2
+ PermissionDecisionUi,
3
+ PermissionPromptDecision,
4
+ requestPermissionDecisionFromUi,
5
+ } from "#src/permission-dialog";
6
+ import {
7
+ emitUiPromptEvent,
8
+ type PermissionEventBus,
9
+ } from "#src/permission-events";
10
+ import { buildDirectUiPrompt } from "#src/permission-ui-prompt";
11
+ import type { Authorizer } from "./authorizer";
12
+ import type { PromptPermissionDetails } from "./permission-prompter";
13
+
14
+ /** Dependencies required by {@link LocalUserAuthorizer}. */
15
+ export interface LocalUserAuthorizerDeps {
16
+ /** The active session's UI surface. */
17
+ ui: PermissionDecisionUi;
18
+ /** Event bus used for the `permissions:ui_prompt` broadcast. */
19
+ events: PermissionEventBus;
20
+ /** Injected for testability; production callers pass the real function. */
21
+ requestPermissionDecisionFromUi: typeof requestPermissionDecisionFromUi;
22
+ }
23
+
24
+ /**
25
+ * Authorizer for a session with an active UI: prompt the human here.
26
+ *
27
+ * Emits the `permissions:ui_prompt` broadcast (moved here from
28
+ * `PermissionPrompter`'s `ctx.hasUI` arm) before showing the dialog, so
29
+ * observers know a decision is imminent.
30
+ */
31
+ export class LocalUserAuthorizer implements Authorizer {
32
+ constructor(private readonly deps: LocalUserAuthorizerDeps) {}
33
+
34
+ authorize(
35
+ details: PromptPermissionDetails,
36
+ ): Promise<PermissionPromptDecision> {
37
+ const uiPrompt = buildDirectUiPrompt(details);
38
+ emitUiPromptEvent(this.deps.events, uiPrompt);
39
+ return this.deps.requestPermissionDecisionFromUi(
40
+ this.deps.ui,
41
+ "Permission Required",
42
+ details.message,
43
+ details.sessionLabel ? { sessionLabel: details.sessionLabel } : undefined,
44
+ );
45
+ }
46
+ }
@@ -1,12 +1,6 @@
1
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
- import type { ApprovalRequester } from "./authority/approval-escalator";
3
- import type { PermissionPromptDecision } from "./permission-dialog";
4
- import {
5
- emitUiPromptEvent,
6
- type PermissionEventBus,
7
- } from "./permission-events";
8
- import { buildDirectUiPrompt } from "./permission-ui-prompt";
9
- import type { ReviewLogger } from "./session-logger";
1
+ import type { PermissionPromptDecision } from "#src/permission-dialog";
2
+ import type { ReviewLogger } from "#src/session-logger";
3
+ import type { Authorizer } from "./authorizer";
10
4
 
11
5
  export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
12
6
 
@@ -27,72 +21,53 @@ export interface PromptPermissionDetails {
27
21
  sessionLabel?: string;
28
22
  }
29
23
 
30
- /** Mockable contract for permission prompting. */
24
+ /**
25
+ * Narrow seam onto {@link PermissionPrompter}.
26
+ *
27
+ * Kept separate from the concrete class so consumers (e.g. `AuthorizerSelection`)
28
+ * can inject a plain `{ prompt: vi.fn() }` mock in tests — a private field on
29
+ * the concrete class would create a nominal brand that a structural mock
30
+ * cannot satisfy without a cast.
31
+ */
31
32
  export interface PermissionPrompterApi {
32
33
  prompt(
33
- ctx: ExtensionContext,
34
+ authorizer: Authorizer,
34
35
  details: PromptPermissionDetails,
35
36
  ): Promise<PermissionPromptDecision>;
36
37
  }
37
38
 
38
- /**
39
- * Dependencies required by PermissionPrompter.
40
- *
41
- * Keeps the prompter's external surface narrow: callers provide a review
42
- * logger, the UI-prompt event bus, and the forwarder that owns the
43
- * UI/subagent-forwarding branching logic.
44
- */
39
+ /** Dependencies required by {@link PermissionPrompter}. */
45
40
  export interface PermissionPrompterDeps {
46
41
  /** Write structured entries to the permission review log. */
47
42
  logger: ReviewLogger;
48
- /** Event bus used for UI prompt broadcasts. */
49
- events: PermissionEventBus;
50
- /** Resolves the permission decision: direct UI dialog or forwarded to parent. */
51
- forwarder: ApprovalRequester;
52
43
  }
53
44
 
54
45
  /**
55
- * Encapsulates the full permission-prompt flow:
46
+ * Brackets the ask-path flow with review-log entries and delegates the
47
+ * live decision to the selected {@link Authorizer}:
56
48
  * 1. Review-log "waiting" entry.
57
- * 2. UI-present vs. subagent-forwarding branching (via confirmPermission).
49
+ * 2. `authorizer.authorize(details)`.
58
50
  * 3. Review-log "approved" / "denied" entry.
59
51
  *
52
+ * The UI/forwarding branching this class previously owned now lives on the
53
+ * individual `Authorizer` implementations (`LocalUserAuthorizer`,
54
+ * `ParentAuthorizer`, `DenyingAuthorizer`) — this class no longer threads
55
+ * `ExtensionContext` per call.
56
+ *
60
57
  * Yolo-mode auto-approval happens upstream, at the composition stage
61
58
  * (`PermissionManager.check`'s `rewriteAsksToYolo`) — an `ask` never reaches
62
59
  * this class under yolo, so this class has no yolo-mode knowledge.
63
- *
64
- * Injecting a single PermissionPrompter instance means adding a new prompt
65
- * parameter (e.g. a future sessionLabel variant) only requires changing
66
- * PromptPermissionDetails and this class — not the full threading chain.
67
60
  */
68
61
  export class PermissionPrompter implements PermissionPrompterApi {
69
62
  constructor(private readonly deps: PermissionPrompterDeps) {}
70
63
 
71
64
  async prompt(
72
- ctx: ExtensionContext,
65
+ authorizer: Authorizer,
73
66
  details: PromptPermissionDetails,
74
67
  ): Promise<PermissionPromptDecision> {
75
68
  this.writeReviewEntry("permission_request.waiting", details);
76
69
 
77
- // Build the event once. When this session has UI it broadcasts directly;
78
- // when it does not (a forwarding subagent), the display fields ride along
79
- // to the parent so the parent emits a non-degraded event from the
80
- // forwarded path instead of here.
81
- const uiPrompt = buildDirectUiPrompt(details);
82
- if (ctx.hasUI) {
83
- emitUiPromptEvent(this.deps.events, uiPrompt);
84
- }
85
-
86
- const decision = await this.deps.forwarder.requestApproval(
87
- ctx,
88
- details.message,
89
- details.sessionLabel ? { sessionLabel: details.sessionLabel } : undefined,
90
- {
91
- source: uiPrompt.source,
92
- surface: uiPrompt.surface,
93
- value: uiPrompt.value,
94
- },
95
- );
70
+ const decision = await authorizer.authorize(details);
96
71
 
97
72
  this.writeReviewEntry(
98
73
  decision.approved
@@ -8,7 +8,7 @@ import type { SubagentSessionRegistry } from "#src/subagent-registry";
8
8
  /**
9
9
  * Narrow seam for the ask-path consumers: "is the current session a subagent?"
10
10
  *
11
- * `PromptingGateway`, `ForwardingManager`, and `ApprovalEscalator` depend on
11
+ * `selectAuthorizer`/`AuthorizerSelection` and `ForwardingManager` depend on
12
12
  * this single-method view so their unit tests inject a one-field fake without
13
13
  * casts. It is the Authorizer-selection predicate the Phase 9 spine consumes.
14
14
  */
@@ -14,7 +14,7 @@ import {
14
14
  } from "./config-schema";
15
15
  import { mergeFlatPermissions } from "./permission-merge";
16
16
  import type { FlatPermissionConfig, PatternValue } from "./types";
17
- import { isDenyWithReason, isPermissionState } from "./value-guards";
17
+ import { isDenyWithReason, isPermissionState } from "./types";
18
18
 
19
19
  // The unified config shape is derived from the zod schema (config-schema.ts,
20
20
  // the single source of truth) and re-exported so existing importers keep their
@@ -1,5 +1,5 @@
1
+ import type { PromptPermissionDetails } from "./authority/permission-prompter";
1
2
  import type { PermissionPromptDecision } from "./permission-dialog";
2
- import type { PromptPermissionDetails } from "./permission-prompter";
3
3
 
4
4
  /**
5
5
  * The prompting role the gate runner needs: a yes/no on whether an
@@ -1,6 +1,6 @@
1
+ import type { PromptPermissionDetails } from "#src/authority/permission-prompter";
1
2
  import type { DenialContext } from "#src/denial-messages";
2
3
  import type { PermissionDecisionEvent } from "#src/permission-events";
3
- import type { PromptPermissionDetails } from "#src/permission-prompter";
4
4
  import type { SessionApproval } from "#src/session-approval";
5
5
  import type { PermissionCheckResult, PermissionState } from "#src/types";
6
6
 
package/src/index.ts CHANGED
@@ -1,13 +1,11 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { getAgentDir, getPackageDir } from "@earendil-works/pi-coding-agent";
3
- import {
4
- ApprovalEscalator,
5
- type ApprovalEscalatorDeps,
6
- } from "./authority/approval-escalator";
3
+ import { AuthorizerSelection } from "./authority/authorizer-selection";
7
4
  import {
8
5
  ForwardedRequestServer,
9
6
  type ForwardedRequestServerDeps,
10
7
  } from "./authority/forwarded-request-server";
8
+ import { PermissionPrompter } from "./authority/permission-prompter";
11
9
  import { SubagentDetection } from "./authority/subagent-detection";
12
10
  import { registerBuiltinToolInputFormatters } from "./builtin-tool-input-formatters";
13
11
  import { registerPermissionSystemCommand } from "./config-modal";
@@ -28,13 +26,10 @@ import { SkillInputGatePipeline } from "./handlers/gates/skill-input-gate-pipeli
28
26
  import { ToolCallGatePipeline } from "./handlers/gates/tool-call-gate-pipeline";
29
27
  import { createFailClosedToolCall } from "./handlers/tool-call-boundary";
30
28
  import { requestPermissionDecisionFromUi } from "./permission-dialog";
31
- import { registerPermissionRpcHandlers } from "./permission-event-rpc";
32
29
  import { PermissionManager } from "./permission-manager";
33
- import { PermissionPrompter } from "./permission-prompter";
34
30
  import { PermissionResolver } from "./permission-resolver";
35
31
  import { PermissionSession } from "./permission-session";
36
32
  import { LocalPermissionsService } from "./permissions-service";
37
- import { PromptingGateway } from "./prompting-gateway";
38
33
  import { PermissionServiceLifecycle } from "./service-lifecycle";
39
34
  import { PermissionSessionLogger } from "./session-logger";
40
35
  import { SessionRules } from "./session-rules";
@@ -95,15 +90,6 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
95
90
  logger,
96
91
  });
97
92
 
98
- const escalatorDeps: ApprovalEscalatorDeps = {
99
- forwardingDir: paths.forwardingDir,
100
- detection: subagentDetection,
101
- registry: subagentRegistry,
102
- logger,
103
- requestPermissionDecisionFromUi,
104
- };
105
- const escalator = new ApprovalEscalator(escalatorDeps);
106
-
107
93
  const requestServerDeps: ForwardedRequestServerDeps = {
108
94
  forwardingDir: paths.forwardingDir,
109
95
  logger,
@@ -113,14 +99,15 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
113
99
  };
114
100
  const requestServer = new ForwardedRequestServer(requestServerDeps);
115
101
 
116
- const prompter = new PermissionPrompter({
117
- logger,
118
- events: pi.events,
119
- forwarder: escalator,
120
- });
102
+ const prompter = new PermissionPrompter({ logger });
121
103
 
122
- const gateway = new PromptingGateway({
104
+ const authorizerSelection = new AuthorizerSelection({
123
105
  detection: subagentDetection,
106
+ events: pi.events,
107
+ requestPermissionDecisionFromUi,
108
+ forwardingDir: paths.forwardingDir,
109
+ registry: subagentRegistry,
110
+ logger,
124
111
  prompter,
125
112
  });
126
113
 
@@ -130,7 +117,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
130
117
  permissionManager,
131
118
  sessionRules,
132
119
  configStore,
133
- gateway,
120
+ authorizerSelection,
134
121
  hostPlatform,
135
122
  );
136
123
 
@@ -150,17 +137,10 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
150
137
  });
151
138
 
152
139
  // Resolver composes the manager + session ruleset and owns the
153
- // access-path → path-values unwrap; the RPC and service route their policy
154
- // queries through it, so it is constructed before both.
140
+ // access-path → path-values unwrap; the service routes its policy
141
+ // queries through it, so it is constructed before it.
155
142
  const resolver = new PermissionResolver(permissionManager, sessionRules);
156
143
 
157
- const rpcHandles = registerPermissionRpcHandlers(pi.events, {
158
- resolver,
159
- session,
160
- requestPermissionDecisionFromUi,
161
- logger,
162
- });
163
-
164
144
  const permissionsService = new LocalPermissionsService(
165
145
  resolver,
166
146
  session,
@@ -184,7 +164,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
184
164
  permissionsService,
185
165
  subagentDetection,
186
166
  pi.events,
187
- [rpcHandles.unsubCheck, rpcHandles.unsubPrompt, unsubSubagentLifecycle],
167
+ [unsubSubagentLifecycle],
188
168
  );
189
169
 
190
170
  const toolRegistry = {
@@ -204,7 +184,12 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
204
184
  const agentPrep = new AgentPrepHandler(session, resolver, toolRegistry);
205
185
 
206
186
  const reporter = new GateDecisionReporter(logger, pi.events);
207
- const gateRunner = new GateRunner(resolver, sessionRules, gateway, reporter);
187
+ const gateRunner = new GateRunner(
188
+ resolver,
189
+ sessionRules,
190
+ authorizerSelection,
191
+ reporter,
192
+ );
208
193
  const toolCallGatePipeline = new ToolCallGatePipeline(
209
194
  resolver,
210
195
  session,
package/src/normalize.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Rule, Ruleset } from "./rule";
2
2
  import type { FlatPermissionConfig } from "./types";
3
- import { isDenyWithReason, isPermissionState } from "./value-guards";
3
+ import { isDenyWithReason, isPermissionState } from "./types";
4
4
 
5
5
  /**
6
6
  * Convert a flat permission config into a Ruleset.
@@ -112,6 +112,18 @@ export class PathNormalizer {
112
112
  return this.impl.isAbsolute(pathValue);
113
113
  }
114
114
 
115
+ /**
116
+ * True when the host platform treats a backslash as a path separator (win32).
117
+ *
118
+ * The bash rule-candidate classifier reads this to decide whether a
119
+ * backslash-relative token (`dir\file`) is path-shaped: on win32 `\` is a
120
+ * separator, but on POSIX it is a legal filename character (#520). Keeping the
121
+ * decision here means no bash-layer module re-reads `process.platform`.
122
+ */
123
+ usesWindowsSeparators(): boolean {
124
+ return this.platform === "win32";
125
+ }
126
+
115
127
  /**
116
128
  * Interpret a literal `cd` target's effect on the effective base.
117
129
  *
@@ -1,27 +1,19 @@
1
1
  /**
2
2
  * Permission event channel — public contract.
3
3
  *
4
- * Exports channel name constants, protocol version, TypeScript types for all
5
- * emitted events and RPC envelopes, and thin emit helpers.
4
+ * Exports channel name constants, TypeScript types for all emitted events,
5
+ * and thin emit helpers.
6
6
  *
7
7
  * Stability guarantee: fields may be added, but existing fields will not be
8
8
  * removed or renamed without a semver-major version bump.
9
9
  */
10
10
 
11
- /** Minimal event bus interface required by the emit helpers and RPC handlers. */
11
+ /** Minimal event bus interface required by the emit helpers. */
12
12
  export interface PermissionEventBus {
13
13
  emit(channel: string, data: unknown): void;
14
14
  on(channel: string, handler: (data: unknown) => void): () => void;
15
15
  }
16
16
 
17
- // ── Protocol version ───────────────────────────────────────────────────────
18
-
19
- /**
20
- * RPC protocol version.
21
- * Bumped when the envelope shape or method contracts change in a breaking way.
22
- */
23
- export const PERMISSIONS_PROTOCOL_VERSION = 1;
24
-
25
17
  // ── Channel name constants ─────────────────────────────────────────────────
26
18
 
27
19
  /** Emitted at `session_start`, after the service is published. */
@@ -33,43 +25,14 @@ export const PERMISSIONS_UI_PROMPT_CHANNEL = "permissions:ui_prompt";
33
25
  /** Emitted after every permission gate resolution. */
34
26
  export const PERMISSIONS_DECISION_CHANNEL = "permissions:decision";
35
27
 
36
- /**
37
- * RPC request channel — query the permission policy (no prompting).
38
- *
39
- * @deprecated Use the `Symbol.for()`-backed service accessor instead:
40
- * ```typescript
41
- * const { getPermissionsService } = await import("@gotgenes/pi-permission-system");
42
- * const service = getPermissionsService();
43
- * if (service) {
44
- * const result = service.checkPermission("bash", "git push");
45
- * }
46
- * ```
47
- * The event-bus RPC remains available as a zero-dependency fallback.
48
- */
49
- export const PERMISSIONS_RPC_CHECK_CHANNEL = "permissions:rpc:check";
50
-
51
- /** RPC request channel — forward a permission prompt to the parent UI. */
52
- export const PERMISSIONS_RPC_PROMPT_CHANNEL = "permissions:rpc:prompt";
53
-
54
- // ── Shared RPC envelope ────────────────────────────────────────────────────
55
-
56
- /**
57
- * Standard RPC reply envelope.
58
- * Success: `{ success: true, protocolVersion, data? }`.
59
- * Error: `{ success: false, protocolVersion, error }`.
60
- */
61
- export type PermissionsRpcReply<T = void> =
62
- | { success: true; protocolVersion: number; data?: T }
63
- | { success: false; protocolVersion: number; error: string };
64
-
65
28
  // ── permissions:ready ──────────────────────────────────────────────────────
66
29
 
67
30
  /**
68
31
  * Payload emitted on `permissions:ready`.
69
32
  *
70
- * Intentionally empty: the channel is a readiness signal. Version negotiation
71
- * lives in the RPC envelope (`PermissionsRpcReply`), not in broadcast payloads —
72
- * the published types plus package semver define the broadcast contract.
33
+ * Intentionally empty: the channel is a readiness signal. There is no
34
+ * `protocolVersion` the published types plus package semver define the
35
+ * broadcast contract.
73
36
  */
74
37
  export type PermissionsReadyEvent = Record<string, never>;
75
38
 
@@ -85,8 +48,7 @@ export type PermissionsReadyEvent = Record<string, never>;
85
48
  export type PermissionUiPromptSource =
86
49
  | "tool_call"
87
50
  | "skill_input"
88
- | "skill_read"
89
- | "rpc_prompt";
51
+ | "skill_read";
90
52
 
91
53
  /** Forwarding context, present only when a prompt was forwarded from a non-UI subagent. */
92
54
  export interface ForwardedPromptContext {
@@ -155,64 +117,6 @@ export interface PermissionDecisionEvent {
155
117
  matchedPattern: string | null;
156
118
  }
157
119
 
158
- // ── permissions:rpc:check ──────────────────────────────────────────────────
159
-
160
- /**
161
- * Request payload for `permissions:rpc:check`.
162
- *
163
- * @deprecated Prefer `getPermissionsService().checkPermission()` from the
164
- * service accessor module. See `PERMISSIONS_RPC_CHECK_CHANNEL` for details.
165
- */
166
- export interface PermissionsCheckRequest {
167
- requestId: string;
168
- /** Permission surface to evaluate. */
169
- surface: string;
170
- /** The value to evaluate: command string, tool name, skill name, or path. */
171
- value?: string;
172
- /** Optional agent name for per-agent policy resolution. */
173
- agentName?: string;
174
- }
175
-
176
- /**
177
- * Data field in a successful `permissions:rpc:check` reply.
178
- *
179
- * @deprecated Prefer `getPermissionsService().checkPermission()` from the
180
- * service accessor module. See `PERMISSIONS_RPC_CHECK_CHANNEL` for details.
181
- */
182
- export interface PermissionsCheckReplyData {
183
- result: "allow" | "deny" | "ask";
184
- matchedPattern: string | null;
185
- origin: string | null;
186
- }
187
-
188
- // ── permissions:rpc:prompt ─────────────────────────────────────────────────
189
-
190
- /** Request payload for `permissions:rpc:prompt`. */
191
- export interface PermissionsPromptRequest {
192
- requestId: string;
193
- /** Permission surface being evaluated. */
194
- surface: string;
195
- /** Value being evaluated (shown in the dialog). */
196
- value: string;
197
- /** Optional agent name for display. */
198
- agentName?: string;
199
- /** Message to display in the permission dialog. */
200
- message: string;
201
- /** Optional label for the "for this session" option. */
202
- sessionLabel?: string;
203
- }
204
-
205
- /** Data field in a successful `permissions:rpc:prompt` reply. */
206
- export interface PermissionsPromptReplyData {
207
- approved: boolean;
208
- /**
209
- * Detailed state: "approved", "approved_for_session",
210
- * "denied", or "denied_with_reason".
211
- */
212
- state: string;
213
- denialReason?: string;
214
- }
215
-
216
120
  // ── Emit helpers ───────────────────────────────────────────────────────────
217
121
 
218
122
  /**