@gotgenes/pi-permission-system 19.0.1 → 20.0.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.
- package/CHANGELOG.md +16 -0
- package/docs/cross-extension-api.md +17 -126
- package/docs/guides/permission-frontmatter-for-subagent-extensions.md +18 -50
- package/docs/guides/upstream-issue-template.md +2 -2
- package/docs/subagent-integration.md +0 -1
- package/package.json +1 -1
- package/src/authority/forwarding-io.ts +0 -1
- package/src/index.ts +3 -11
- package/src/permission-events.ts +7 -103
- package/src/permission-ui-prompt.ts +1 -25
- package/src/service.ts +3 -8
- package/src/permission-event-rpc.ts +0 -227
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,22 @@ 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
|
+
## [20.0.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v19.0.1...pi-permission-system-v20.0.0) (2026-07-07)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### ⚠ BREAKING CHANGES
|
|
12
|
+
|
|
13
|
+
* **pi-permission-system:** The permissions:rpc:check and permissions:rpc:prompt event-bus channels are removed, along with their request/reply types (PermissionsCheckRequest, PermissionsCheckReplyData, PermissionsPromptRequest, PermissionsPromptReplyData), the PermissionsRpcReply envelope, PERMISSIONS_PROTOCOL_VERSION, and the rpc_prompt member of PermissionUiPromptSource. permissions:rpc:check consumers migrate to getPermissionsService().checkPermission(surface, value?, agentName?). permissions:rpc:prompt is removed with no public replacement; prompt forwarding is an internal subagent-to-parent mechanism.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **pi-permission-system:** remove deprecated event-bus RPC channel ([557ea91](https://github.com/gotgenes/pi-packages/commit/557ea913c54f21ef64b0812169bbc7d5224072e2))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* **pi-permission-system:** repoint cross-extension docs off the removed RPC channel ([b3d06f7](https://github.com/gotgenes/pi-packages/commit/b3d06f77aea43758a4e820e66d1111608b86879b)), closes [#531](https://github.com/gotgenes/pi-packages/issues/531)
|
|
23
|
+
|
|
8
24
|
## [19.0.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v19.0.0...pi-permission-system-v19.0.1) (2026-07-07)
|
|
9
25
|
|
|
10
26
|
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
The extension provides two cross-extension integration surfaces:
|
|
4
4
|
|
|
5
5
|
1. **Service accessor** (preferred) — a `Symbol.for()`-backed synchronous API on `globalThis` for direct policy queries.
|
|
6
|
-
2. **Event bus** — broadcasts
|
|
6
|
+
2. **Event bus** — broadcasts on `pi.events` for observation.
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## Service Accessor
|
|
11
11
|
|
|
12
12
|
The preferred way for other extensions to query the permission policy is the `Symbol.for()`-backed service accessor.
|
|
13
|
-
It provides direct, synchronous, type-safe function calls
|
|
13
|
+
It provides direct, synchronous, type-safe function calls.
|
|
14
14
|
|
|
15
15
|
### Quick Start
|
|
16
16
|
|
|
@@ -286,22 +286,17 @@ The extension also emits events on Pi's `pi.events` bus so other extensions can
|
|
|
286
286
|
|
|
287
287
|
Fields may be added to any payload, but existing fields will not be removed or renamed without a semver-major version bump.
|
|
288
288
|
The broadcast contract is defined by the published TypeScript types plus package semver — broadcast payloads (`permissions:ready`, `permissions:ui_prompt`, `permissions:decision`) carry no `protocolVersion`.
|
|
289
|
-
The `PERMISSIONS_PROTOCOL_VERSION` constant is exported from `src/permission-events.ts` and embedded only in the RPC reply envelope, where per-call request/reply negotiation is load-bearing.
|
|
290
289
|
Consumers should read broadcast payloads defensively (field-presence checks) rather than version-gating — that is robust to any shape skew between independently-versioned sibling extensions.
|
|
291
290
|
|
|
292
291
|
All three broadcasts are best-effort: a throwing listener cannot block permission handling, session startup, or gate resolution.
|
|
293
292
|
|
|
294
293
|
## Channel Reference
|
|
295
294
|
|
|
296
|
-
| Channel
|
|
297
|
-
|
|
|
298
|
-
| `permissions:ready`
|
|
299
|
-
| `permissions:ui_prompt`
|
|
300
|
-
| `permissions:decision`
|
|
301
|
-
| `permissions:rpc:check` | Request | On-demand | `PermissionsCheckRequest` |
|
|
302
|
-
| `permissions:rpc:check:reply:<requestId>` | Reply | After each check request | `PermissionsRpcReply<PermissionsCheckReplyData>` |
|
|
303
|
-
| `permissions:rpc:prompt` | Request | On-demand | `PermissionsPromptRequest` |
|
|
304
|
-
| `permissions:rpc:prompt:reply:<requestId>` | Reply | After prompt is resolved | `PermissionsRpcReply<PermissionsPromptReplyData>` |
|
|
295
|
+
| Channel | Direction | When | Payload type |
|
|
296
|
+
| ----------------------- | --------- | --------------------------------- | ------------------------- |
|
|
297
|
+
| `permissions:ready` | Broadcast | At `session_start`, after publish | `PermissionsReadyEvent` |
|
|
298
|
+
| `permissions:ui_prompt` | Broadcast | Before active UI prompt | `PermissionUiPromptEvent` |
|
|
299
|
+
| `permissions:decision` | Broadcast | After every gate resolution | `PermissionDecisionEvent` |
|
|
305
300
|
|
|
306
301
|
---
|
|
307
302
|
|
|
@@ -333,15 +328,15 @@ pi.events.on("permissions:ui_prompt", (raw) => {
|
|
|
333
328
|
|
|
334
329
|
### Payload Fields
|
|
335
330
|
|
|
336
|
-
| Field | Type | Description
|
|
337
|
-
| ------------ | -------------------------------- |
|
|
338
|
-
| `requestId` | `string` | Unique ID for the permission request being prompted
|
|
339
|
-
| `source` | `PermissionUiPromptSource` | Prompt origin: `"tool_call"`, `"skill_input"`,
|
|
340
|
-
| `surface` | `string \| null` | Normalized display surface (e.g. `"bash"`, `"skill"`), when known
|
|
341
|
-
| `value` | `string \| null` | Normalized display value (command, path, skill name, etc.), when known
|
|
342
|
-
| `agentName` | `string \| null` | Active/requesting agent name, when known
|
|
343
|
-
| `message` | `string` | Message displayed in the permission prompt
|
|
344
|
-
| `forwarding` | `ForwardedPromptContext \| null` | Forwarding context, or `null` for a direct prompt
|
|
331
|
+
| Field | Type | Description |
|
|
332
|
+
| ------------ | -------------------------------- | ---------------------------------------------------------------------- |
|
|
333
|
+
| `requestId` | `string` | Unique ID for the permission request being prompted |
|
|
334
|
+
| `source` | `PermissionUiPromptSource` | Prompt origin: `"tool_call"`, `"skill_input"`, or `"skill_read"` |
|
|
335
|
+
| `surface` | `string \| null` | Normalized display surface (e.g. `"bash"`, `"skill"`), when known |
|
|
336
|
+
| `value` | `string \| null` | Normalized display value (command, path, skill name, etc.), when known |
|
|
337
|
+
| `agentName` | `string \| null` | Active/requesting agent name, when known |
|
|
338
|
+
| `message` | `string` | Message displayed in the permission prompt |
|
|
339
|
+
| `forwarding` | `ForwardedPromptContext \| null` | Forwarding context, or `null` for a direct prompt |
|
|
345
340
|
|
|
346
341
|
Forwarding is orthogonal to origin: a forwarded subagent prompt keeps its original `source` and is identified by a non-null `forwarding` field, not by a dedicated source value.
|
|
347
342
|
|
|
@@ -400,117 +395,13 @@ pi.events.on("permissions:decision", (raw) => {
|
|
|
400
395
|
|
|
401
396
|
---
|
|
402
397
|
|
|
403
|
-
## Policy Query RPC (deprecated)
|
|
404
|
-
|
|
405
|
-
> **Deprecated**: prefer the [Service Accessor](#service-accessor) above.
|
|
406
|
-
> The event-bus RPC remains available as a zero-dependency fallback.
|
|
407
|
-
|
|
408
|
-
Other extensions can evaluate the current permission policy without importing this package.
|
|
409
|
-
The call is synchronous-style: emit a request, listen on a scoped reply channel.
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
const requestId = crypto.randomUUID();
|
|
413
|
-
|
|
414
|
-
// Listen for the reply first
|
|
415
|
-
const unsub = pi.events.on(
|
|
416
|
-
`permissions:rpc:check:reply:${requestId}`,
|
|
417
|
-
(raw) => {
|
|
418
|
-
unsub();
|
|
419
|
-
const reply = raw as import("@gotgenes/pi-permission-system").PermissionsRpcReply<
|
|
420
|
-
import("@gotgenes/pi-permission-system").PermissionsCheckReplyData
|
|
421
|
-
>;
|
|
422
|
-
if (reply.success) {
|
|
423
|
-
console.log(reply.data?.result); // "allow" | "deny" | "ask"
|
|
424
|
-
}
|
|
425
|
-
},
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
// Then emit the request
|
|
429
|
-
pi.events.emit("permissions:rpc:check", {
|
|
430
|
-
requestId,
|
|
431
|
-
surface: "bash",
|
|
432
|
-
value: "git push",
|
|
433
|
-
agentName: "Worker", // optional
|
|
434
|
-
});
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
If the extension is not loaded, no reply arrives.
|
|
438
|
-
Callers should implement a timeout and treat no-reply as `deny` (graceful degradation).
|
|
439
|
-
|
|
440
|
-
### Request Fields
|
|
441
|
-
|
|
442
|
-
| Field | Required | Description |
|
|
443
|
-
| ----------- | -------- | ---------------------------------------------------------- |
|
|
444
|
-
| `requestId` | Yes | Unique string; scopes the reply channel |
|
|
445
|
-
| `surface` | Yes | Permission surface to evaluate |
|
|
446
|
-
| `value` | No | Value to evaluate (command, name, path); defaults to `"*"` |
|
|
447
|
-
| `agentName` | No | Agent name for per-agent policy resolution |
|
|
448
|
-
|
|
449
|
-
As with the `checkPermission` service method, a path-shaped surface (`path`, `external_directory`, or a path-bearing tool) matches the `value` against both the path as given and its canonical (symlink-resolved) form.
|
|
450
|
-
|
|
451
|
-
### Reply Data Fields (`PermissionsCheckReplyData`)
|
|
452
|
-
|
|
453
|
-
| Field | Type | Description |
|
|
454
|
-
| ---------------- | ---------------------------- | ------------------------------------------------ |
|
|
455
|
-
| `result` | `"allow" \| "deny" \| "ask"` | Policy decision (including active session rules) |
|
|
456
|
-
| `matchedPattern` | `string \| null` | Matched rule pattern |
|
|
457
|
-
| `origin` | `string \| null` | Config scope of the winning rule |
|
|
458
|
-
|
|
459
|
-
---
|
|
460
|
-
|
|
461
|
-
## Prompt Forwarding RPC
|
|
462
|
-
|
|
463
|
-
In-process child sessions (e.g. tintinweb/pi-subagents running via `createAgentSession()`) cannot use file-based permission forwarding because no child process is spawned.
|
|
464
|
-
They can instead forward permission prompts to the parent session's UI via this RPC.
|
|
465
|
-
|
|
466
|
-
```typescript
|
|
467
|
-
const requestId = crypto.randomUUID();
|
|
468
|
-
|
|
469
|
-
const unsub = pi.events.on(
|
|
470
|
-
`permissions:rpc:prompt:reply:${requestId}`,
|
|
471
|
-
(raw) => {
|
|
472
|
-
unsub();
|
|
473
|
-
const reply = raw as import("@gotgenes/pi-permission-system").PermissionsRpcReply<
|
|
474
|
-
import("@gotgenes/pi-permission-system").PermissionsPromptReplyData
|
|
475
|
-
>;
|
|
476
|
-
if (reply.success && reply.data?.approved) {
|
|
477
|
-
// proceed
|
|
478
|
-
} else {
|
|
479
|
-
// deny — either user denied or no UI was available (error: "no_ui")
|
|
480
|
-
}
|
|
481
|
-
},
|
|
482
|
-
);
|
|
483
|
-
|
|
484
|
-
pi.events.emit("permissions:rpc:prompt", {
|
|
485
|
-
requestId,
|
|
486
|
-
surface: "bash",
|
|
487
|
-
value: "rm -rf /tmp/build",
|
|
488
|
-
message: "Allow rm -rf /tmp/build?",
|
|
489
|
-
agentName: "Explore", // optional
|
|
490
|
-
sessionLabel: "Allow rm *", // optional — label for the "for this session" option
|
|
491
|
-
});
|
|
492
|
-
```
|
|
493
|
-
|
|
494
|
-
The handler replies with `{ success: false, error: "no_ui" }` when no interactive session is available.
|
|
495
|
-
|
|
496
|
-
### Successful Reply Fields
|
|
497
|
-
|
|
498
|
-
| Field | Type | Description |
|
|
499
|
-
| -------------- | ------------------- | ----------------------------------------------------------------------------- |
|
|
500
|
-
| `approved` | `boolean` | Whether the user approved |
|
|
501
|
-
| `state` | `string` | `"approved"`, `"approved_for_session"`, `"denied"`, or `"denied_with_reason"` |
|
|
502
|
-
| `denialReason` | `string` (optional) | User-provided denial reason |
|
|
503
|
-
|
|
504
|
-
---
|
|
505
|
-
|
|
506
398
|
## Ready Event
|
|
507
399
|
|
|
508
400
|
The extension emits `permissions:ready` at `session_start`, right after the service is published — so a consumer reacting to it can immediately resolve `getPermissionsService()`.
|
|
509
401
|
It fires once per `session_start` (including `/reload`).
|
|
510
|
-
Consumers that start after the extension can check via a ping-style RPC check — the `permissions:rpc:check` handler is active as long as the extension is loaded.
|
|
511
402
|
|
|
512
403
|
The payload is intentionally empty (`Record<string, never>`): the channel is a pure readiness signal.
|
|
513
|
-
It carries no `protocolVersion` —
|
|
404
|
+
It carries no `protocolVersion` — the broadcast contract is defined by the published types plus package semver.
|
|
514
405
|
|
|
515
406
|
```typescript
|
|
516
407
|
pi.events.on("permissions:ready", () => {
|
|
@@ -127,61 +127,29 @@ The permission system handles all evaluation, prompt dialogs, and policy enforce
|
|
|
127
127
|
|
|
128
128
|
## Runtime Integration (Optional)
|
|
129
129
|
|
|
130
|
-
If your extension runs subagents in-process (e.g. via `createAgentSession()`), you can optionally query the permission system's policy at runtime via the
|
|
130
|
+
If your extension runs subagents in-process (e.g. via `createAgentSession()`), you can optionally query the permission system's policy at runtime via the `Symbol.for()`-backed service accessor — no required peer dependency, just a dynamic `import()`.
|
|
131
131
|
|
|
132
132
|
### Querying policy
|
|
133
133
|
|
|
134
134
|
```typescript
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
pi
|
|
138
|
-
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
pi.events.emit("permissions:rpc:check", {
|
|
148
|
-
requestId,
|
|
149
|
-
surface: "bash",
|
|
150
|
-
value: "git push",
|
|
151
|
-
agentName: "Worker",
|
|
152
|
-
});
|
|
135
|
+
try {
|
|
136
|
+
const { getPermissionsService } = await import(
|
|
137
|
+
"@gotgenes/pi-permission-system"
|
|
138
|
+
);
|
|
139
|
+
const permissions = getPermissionsService();
|
|
140
|
+
if (permissions) {
|
|
141
|
+
const result = permissions.checkPermission("bash", "git push", "Worker");
|
|
142
|
+
console.log(result.state); // "allow" | "deny" | "ask"
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Not installed — graceful degradation
|
|
146
|
+
}
|
|
153
147
|
```
|
|
154
148
|
|
|
155
|
-
|
|
149
|
+
If `pi-permission-system` is not installed, `import()` throws; if it has not published a service yet (or has been unloaded), `getPermissionsService()` returns `undefined`.
|
|
150
|
+
Guard both cases as shown above.
|
|
156
151
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
```typescript
|
|
160
|
-
const requestId = crypto.randomUUID();
|
|
161
|
-
|
|
162
|
-
pi.events.on(
|
|
163
|
-
`permissions:rpc:prompt:reply:${requestId}`,
|
|
164
|
-
(raw) => {
|
|
165
|
-
const reply = raw as { success: boolean; data?: { approved: boolean } };
|
|
166
|
-
if (reply.success && reply.data?.approved) {
|
|
167
|
-
// proceed
|
|
168
|
-
} else {
|
|
169
|
-
// deny
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
pi.events.emit("permissions:rpc:prompt", {
|
|
175
|
-
requestId,
|
|
176
|
-
surface: "bash",
|
|
177
|
-
value: "rm -rf /tmp/build",
|
|
178
|
-
message: "Allow rm -rf /tmp/build?",
|
|
179
|
-
agentName: "Worker",
|
|
180
|
-
});
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
If `pi-permission-system` is not installed, no reply arrives.
|
|
184
|
-
Implement a timeout and treat no-reply as `deny` for graceful degradation.
|
|
152
|
+
Prompt forwarding for headless child agents is an internal subagent-to-parent mechanism, not a public cross-extension operation — there is no service-accessor equivalent to call directly.
|
|
185
153
|
|
|
186
154
|
For full API documentation, see [Cross-extension API](../cross-extension-api.md).
|
|
187
155
|
|
|
@@ -191,11 +159,11 @@ For full API documentation, see [Cross-extension API](../cross-extension-api.md)
|
|
|
191
159
|
2. **Unified config** — one `permission:` block per agent instead of separate restriction keys in multiple extensions.
|
|
192
160
|
3. **Surface coverage** — policy covers bash patterns, MCP tools, skills, external directories, and special operations, not just tool names.
|
|
193
161
|
4. **Forwarding** — permission prompts from headless child agents surface in the parent session's UI.
|
|
194
|
-
5. **Programmatic access** — the
|
|
162
|
+
5. **Programmatic access** — the `Symbol.for()` service accessor lets your extension query policy at runtime with only a dynamic `import()`, no required peer dependency.
|
|
195
163
|
|
|
196
164
|
## Further Reading
|
|
197
165
|
|
|
198
166
|
- [Subagent Integration](../subagent-integration.md) — full coexistence documentation and interaction rules
|
|
199
|
-
- [Cross-extension API](../cross-extension-api.md) — service accessor, event bus reference (decision
|
|
167
|
+
- [Cross-extension API](../cross-extension-api.md) — service accessor, event bus reference (decision and UI-prompt broadcasts)
|
|
200
168
|
- [Configuration](../configuration.md) — full policy reference including merge precedence
|
|
201
169
|
- [Schema](../../schemas/permissions.schema.json) — canonical JSON Schema for the flat permission format
|
|
@@ -31,7 +31,7 @@ This is purely a documentation change — no code dependency on pi-permission-sy
|
|
|
31
31
|
- **Three-state policy**: `allow`, `ask` (prompt the user), or `deny` — richer than binary allow/deny
|
|
32
32
|
- **Multiple surfaces**: tools, bash commands (glob patterns), MCP operations, skills, external directories
|
|
33
33
|
- **Prompt forwarding**: `ask` decisions in headless child agents surface in the parent session's UI
|
|
34
|
-
- **
|
|
34
|
+
- **Service accessor API**: other extensions can query policy at runtime via a `Symbol.for()`-backed accessor, with only a dynamic `import()` and no required peer dependency
|
|
35
35
|
|
|
36
36
|
### Example
|
|
37
37
|
|
|
@@ -99,7 +99,7 @@ No code changes, no new dependency, no schema enforcement.
|
|
|
99
99
|
| `{{your-key}}` | `disallowed_tools` |
|
|
100
100
|
| `{{example-value}}` | `write_file,bash` |
|
|
101
101
|
|
|
102
|
-
Additional note for tintinweb: since this extension runs subagents in-process via `createAgentSession()`, mention the [
|
|
102
|
+
Additional note for tintinweb: since this extension runs subagents in-process via `createAgentSession()`, mention the [service accessor](https://github.com/gotgenes/pi-permission-system/blob/main/docs/cross-extension-api.md#service-accessor) as an optional runtime integration path for querying policy without spawning a subprocess.
|
|
103
103
|
|
|
104
104
|
### HazAT/pi-interactive-subagents
|
|
105
105
|
|
|
@@ -29,7 +29,6 @@ The main interactive session polls for forwarded requests, shows the confirmatio
|
|
|
29
29
|
This keeps `ask` policies usable even when the original permission check happens inside a non-UI execution context.
|
|
30
30
|
|
|
31
31
|
For in-process child sessions, detection and forwarding use the event-driven registration described above.
|
|
32
|
-
The [Prompt Forwarding RPC](cross-extension-api.md#prompt-forwarding-rpc) remains available as an alternative for extensions that cannot use the service accessor.
|
|
33
32
|
|
|
34
33
|
---
|
|
35
34
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -28,7 +28,6 @@ import { SkillInputGatePipeline } from "./handlers/gates/skill-input-gate-pipeli
|
|
|
28
28
|
import { ToolCallGatePipeline } from "./handlers/gates/tool-call-gate-pipeline";
|
|
29
29
|
import { createFailClosedToolCall } from "./handlers/tool-call-boundary";
|
|
30
30
|
import { requestPermissionDecisionFromUi } from "./permission-dialog";
|
|
31
|
-
import { registerPermissionRpcHandlers } from "./permission-event-rpc";
|
|
32
31
|
import { PermissionManager } from "./permission-manager";
|
|
33
32
|
import { PermissionPrompter } from "./permission-prompter";
|
|
34
33
|
import { PermissionResolver } from "./permission-resolver";
|
|
@@ -150,17 +149,10 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
150
149
|
});
|
|
151
150
|
|
|
152
151
|
// Resolver composes the manager + session ruleset and owns the
|
|
153
|
-
// access-path → path-values unwrap; the
|
|
154
|
-
// queries through it, so it is constructed before
|
|
152
|
+
// access-path → path-values unwrap; the service routes its policy
|
|
153
|
+
// queries through it, so it is constructed before it.
|
|
155
154
|
const resolver = new PermissionResolver(permissionManager, sessionRules);
|
|
156
155
|
|
|
157
|
-
const rpcHandles = registerPermissionRpcHandlers(pi.events, {
|
|
158
|
-
resolver,
|
|
159
|
-
session,
|
|
160
|
-
requestPermissionDecisionFromUi,
|
|
161
|
-
logger,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
156
|
const permissionsService = new LocalPermissionsService(
|
|
165
157
|
resolver,
|
|
166
158
|
session,
|
|
@@ -184,7 +176,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
|
184
176
|
permissionsService,
|
|
185
177
|
subagentDetection,
|
|
186
178
|
pi.events,
|
|
187
|
-
[
|
|
179
|
+
[unsubSubagentLifecycle],
|
|
188
180
|
);
|
|
189
181
|
|
|
190
182
|
const toolRegistry = {
|
package/src/permission-events.ts
CHANGED
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Permission event channel — public contract.
|
|
3
3
|
*
|
|
4
|
-
* Exports channel name constants,
|
|
5
|
-
*
|
|
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
|
|
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.
|
|
71
|
-
*
|
|
72
|
-
*
|
|
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
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This module is a leaf: it owns narrow input types that each call site's
|
|
9
9
|
* domain object satisfies structurally, so it imports nothing from the
|
|
10
|
-
* prompter
|
|
10
|
+
* prompter or forwarding modules (no import cycles, correct layering).
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type {
|
|
@@ -28,15 +28,6 @@ export interface DirectPromptInput {
|
|
|
28
28
|
target?: string;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
/** Input for a `permissions:rpc:prompt` forwarded UI prompt. */
|
|
32
|
-
export interface RpcPromptInput {
|
|
33
|
-
requestId: string;
|
|
34
|
-
surface?: string | null;
|
|
35
|
-
value?: string | null;
|
|
36
|
-
agentName?: string | null;
|
|
37
|
-
message: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
31
|
/** Input for a file-forwarded subagent prompt shown by the parent UI. */
|
|
41
32
|
export interface ForwardedPromptInput {
|
|
42
33
|
requestId: string;
|
|
@@ -86,21 +77,6 @@ export function buildDirectUiPrompt(
|
|
|
86
77
|
};
|
|
87
78
|
}
|
|
88
79
|
|
|
89
|
-
/** Build the UI prompt event for an RPC-forwarded prompt. */
|
|
90
|
-
export function buildRpcUiPrompt(
|
|
91
|
-
input: RpcPromptInput,
|
|
92
|
-
): PermissionUiPromptEvent {
|
|
93
|
-
return {
|
|
94
|
-
requestId: input.requestId,
|
|
95
|
-
source: "rpc_prompt",
|
|
96
|
-
surface: input.surface ?? null,
|
|
97
|
-
value: input.value ?? null,
|
|
98
|
-
agentName: input.agentName ?? null,
|
|
99
|
-
message: input.message,
|
|
100
|
-
forwarding: null,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
80
|
/**
|
|
105
81
|
* Build the UI prompt event for a file-forwarded subagent prompt.
|
|
106
82
|
*
|
package/src/service.ts
CHANGED
|
@@ -18,18 +18,13 @@ import type { PermissionCheckResult, PermissionState } from "./types";
|
|
|
18
18
|
export type {
|
|
19
19
|
ForwardedPromptContext,
|
|
20
20
|
PermissionDecisionEvent,
|
|
21
|
-
PermissionsPromptReplyData,
|
|
22
|
-
PermissionsPromptRequest,
|
|
23
21
|
PermissionsReadyEvent,
|
|
24
|
-
PermissionsRpcReply,
|
|
25
22
|
PermissionUiPromptEvent,
|
|
26
23
|
PermissionUiPromptSource,
|
|
27
24
|
} from "./permission-events";
|
|
28
25
|
export {
|
|
29
26
|
PERMISSIONS_DECISION_CHANNEL,
|
|
30
|
-
PERMISSIONS_PROTOCOL_VERSION,
|
|
31
27
|
PERMISSIONS_READY_CHANNEL,
|
|
32
|
-
PERMISSIONS_RPC_PROMPT_CHANNEL,
|
|
33
28
|
PERMISSIONS_UI_PROMPT_CHANNEL,
|
|
34
29
|
} from "./permission-events";
|
|
35
30
|
export type { PermissionCheckResult, PermissionState, ToolInputFormatter };
|
|
@@ -40,9 +35,9 @@ const SERVICE_KEY = Symbol.for("@gotgenes/pi-permission-system:service");
|
|
|
40
35
|
/**
|
|
41
36
|
* Public interface exposed to other extensions via `getPermissionsService()`.
|
|
42
37
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
38
|
+
* `checkPermission` takes a surface + optional value + optional agent name,
|
|
39
|
+
* and delegates to `PermissionManager.checkPermission()` with current session
|
|
40
|
+
* rules internally.
|
|
46
41
|
*/
|
|
47
42
|
export interface PermissionsService {
|
|
48
43
|
/**
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-deprecated -- this module implements the deprecated event-bus RPC channel; references to its own deprecated symbols are intentional */
|
|
2
|
-
/**
|
|
3
|
-
* Permission event bus RPC handlers.
|
|
4
|
-
*
|
|
5
|
-
* Registers `permissions:rpc:check` and `permissions:rpc:prompt` handlers on
|
|
6
|
-
* the Pi event bus so other extensions can query our policy and forward
|
|
7
|
-
* permission prompts without importing this package.
|
|
8
|
-
*/
|
|
9
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { buildAccessIntentForSurface } from "./input-normalizer";
|
|
11
|
-
import type { PathNormalizer } from "./path-normalizer";
|
|
12
|
-
import type {
|
|
13
|
-
PermissionPromptDecision,
|
|
14
|
-
RequestPermissionOptions,
|
|
15
|
-
} from "./permission-dialog";
|
|
16
|
-
import type {
|
|
17
|
-
PermissionEventBus,
|
|
18
|
-
PermissionsCheckReplyData,
|
|
19
|
-
PermissionsCheckRequest,
|
|
20
|
-
PermissionsPromptReplyData,
|
|
21
|
-
PermissionsPromptRequest,
|
|
22
|
-
PermissionsRpcReply,
|
|
23
|
-
} from "./permission-events";
|
|
24
|
-
import {
|
|
25
|
-
emitUiPromptEvent,
|
|
26
|
-
PERMISSIONS_PROTOCOL_VERSION,
|
|
27
|
-
PERMISSIONS_RPC_CHECK_CHANNEL,
|
|
28
|
-
PERMISSIONS_RPC_PROMPT_CHANNEL,
|
|
29
|
-
} from "./permission-events";
|
|
30
|
-
import type { ScopedPermissionResolver } from "./permission-resolver";
|
|
31
|
-
import { buildRpcUiPrompt } from "./permission-ui-prompt";
|
|
32
|
-
import type { ReviewLogger } from "./session-logger";
|
|
33
|
-
|
|
34
|
-
/** Dependencies injected into the RPC handler registry. */
|
|
35
|
-
export interface PermissionRpcDeps {
|
|
36
|
-
/**
|
|
37
|
-
* The shared resolver: answers an access intent, composing the session
|
|
38
|
-
* ruleset and unwrapping `access-path` → `path-values` internally so the
|
|
39
|
-
* RPC check matches the same lexical ∪ canonical set the gates do (#503).
|
|
40
|
-
*/
|
|
41
|
-
resolver: Pick<ScopedPermissionResolver, "resolve">;
|
|
42
|
-
/**
|
|
43
|
-
* Narrow session view: runtime context for the prompt handler (hasUI / UI
|
|
44
|
-
* dialog) and the cwd-bound path normalizer for path-surface check queries.
|
|
45
|
-
*/
|
|
46
|
-
session: {
|
|
47
|
-
getRuntimeContext(): ExtensionContext | null;
|
|
48
|
-
getPathNormalizer(): PathNormalizer;
|
|
49
|
-
};
|
|
50
|
-
/** Show the interactive permission dialog in the parent session UI. */
|
|
51
|
-
requestPermissionDecisionFromUi(
|
|
52
|
-
ui: ExtensionContext["ui"],
|
|
53
|
-
title: string,
|
|
54
|
-
message: string,
|
|
55
|
-
options?: RequestPermissionOptions,
|
|
56
|
-
): Promise<PermissionPromptDecision>;
|
|
57
|
-
/** Write review-log entries for prompted decisions. */
|
|
58
|
-
logger: ReviewLogger;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Unsubscribe handles returned from registerPermissionRpcHandlers. */
|
|
62
|
-
export interface PermissionRpcHandles {
|
|
63
|
-
/** Stop the permissions:rpc:check handler. */
|
|
64
|
-
unsubCheck: () => void;
|
|
65
|
-
/** Stop the permissions:rpc:prompt handler. */
|
|
66
|
-
unsubPrompt: () => void;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ── Internal helpers ───────────────────────────────────────────────────────
|
|
70
|
-
|
|
71
|
-
/** Build a success reply envelope. */
|
|
72
|
-
function successReply<T>(data?: T): PermissionsRpcReply<T> {
|
|
73
|
-
if (data !== undefined) {
|
|
74
|
-
return {
|
|
75
|
-
success: true,
|
|
76
|
-
protocolVersion: PERMISSIONS_PROTOCOL_VERSION,
|
|
77
|
-
data,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
return { success: true, protocolVersion: PERMISSIONS_PROTOCOL_VERSION };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/** Build an error reply envelope. */
|
|
84
|
-
function errorReply(error: string): PermissionsRpcReply {
|
|
85
|
-
return {
|
|
86
|
-
success: false,
|
|
87
|
-
protocolVersion: PERMISSIONS_PROTOCOL_VERSION,
|
|
88
|
-
error,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ── RPC handler: permissions:rpc:check ────────────────────────────────────
|
|
93
|
-
|
|
94
|
-
function handleCheckRpc(
|
|
95
|
-
raw: unknown,
|
|
96
|
-
events: PermissionEventBus,
|
|
97
|
-
deps: PermissionRpcDeps,
|
|
98
|
-
): void {
|
|
99
|
-
const req = raw as Partial<PermissionsCheckRequest>;
|
|
100
|
-
const { requestId, surface, value, agentName } = req;
|
|
101
|
-
|
|
102
|
-
if (typeof requestId !== "string" || !requestId) {
|
|
103
|
-
// Cannot reply without a requestId — silently discard.
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const replyChannel = `${PERMISSIONS_RPC_CHECK_CHANNEL}:reply:${requestId}`;
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
if (typeof surface !== "string" || !surface) {
|
|
111
|
-
events.emit(replyChannel, errorReply("surface is required"));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const intent = buildAccessIntentForSurface(
|
|
116
|
-
surface,
|
|
117
|
-
value,
|
|
118
|
-
deps.session.getPathNormalizer(),
|
|
119
|
-
agentName ?? undefined,
|
|
120
|
-
);
|
|
121
|
-
const result = deps.resolver.resolve(intent);
|
|
122
|
-
|
|
123
|
-
const data: PermissionsCheckReplyData = {
|
|
124
|
-
result: result.state,
|
|
125
|
-
matchedPattern: result.matchedPattern ?? null,
|
|
126
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- ?? null normalises undefined to null for the reply record
|
|
127
|
-
origin: result.origin ?? null,
|
|
128
|
-
};
|
|
129
|
-
events.emit(replyChannel, successReply(data));
|
|
130
|
-
} catch (err) {
|
|
131
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
132
|
-
events.emit(replyChannel, errorReply(message));
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ── RPC handler: permissions:rpc:prompt ───────────────────────────────────
|
|
137
|
-
|
|
138
|
-
async function handlePromptRpc(
|
|
139
|
-
raw: unknown,
|
|
140
|
-
events: PermissionEventBus,
|
|
141
|
-
deps: PermissionRpcDeps,
|
|
142
|
-
): Promise<void> {
|
|
143
|
-
const req = raw as Partial<PermissionsPromptRequest>;
|
|
144
|
-
const { requestId, surface, value, agentName, message, sessionLabel } = req;
|
|
145
|
-
|
|
146
|
-
if (typeof requestId !== "string" || !requestId) {
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const replyChannel = `${PERMISSIONS_RPC_PROMPT_CHANNEL}:reply:${requestId}`;
|
|
151
|
-
|
|
152
|
-
const ctx = deps.session.getRuntimeContext();
|
|
153
|
-
if (!ctx?.hasUI) {
|
|
154
|
-
events.emit(replyChannel, errorReply("no_ui"));
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (typeof message !== "string" || !message) {
|
|
159
|
-
events.emit(replyChannel, errorReply("message is required"));
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
try {
|
|
164
|
-
const title = surface
|
|
165
|
-
? `Permission request${agentName ? ` from ${agentName}` : ""}`
|
|
166
|
-
: "Permission request";
|
|
167
|
-
|
|
168
|
-
emitUiPromptEvent(
|
|
169
|
-
events,
|
|
170
|
-
buildRpcUiPrompt({ requestId, surface, value, agentName, message }),
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
const decision = await deps.requestPermissionDecisionFromUi(
|
|
174
|
-
ctx.ui,
|
|
175
|
-
title,
|
|
176
|
-
message,
|
|
177
|
-
sessionLabel ? { sessionLabel } : undefined,
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
deps.logger.review("permission_request.rpc_prompt", {
|
|
181
|
-
requestId,
|
|
182
|
-
surface: surface ?? null,
|
|
183
|
-
value: value ?? null,
|
|
184
|
-
agentName: agentName ?? null,
|
|
185
|
-
message,
|
|
186
|
-
approved: decision.approved,
|
|
187
|
-
resolution: decision.state,
|
|
188
|
-
denialReason: decision.denialReason ?? null,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const data: PermissionsPromptReplyData = {
|
|
192
|
-
approved: decision.approved,
|
|
193
|
-
state: decision.state,
|
|
194
|
-
...(decision.denialReason !== undefined
|
|
195
|
-
? { denialReason: decision.denialReason }
|
|
196
|
-
: {}),
|
|
197
|
-
};
|
|
198
|
-
events.emit(replyChannel, successReply(data));
|
|
199
|
-
} catch (err) {
|
|
200
|
-
const message_ = err instanceof Error ? err.message : String(err);
|
|
201
|
-
events.emit(replyChannel, errorReply(message_));
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Register `permissions:rpc:check` and `permissions:rpc:prompt` handlers on
|
|
209
|
-
* the event bus.
|
|
210
|
-
*
|
|
211
|
-
* Returns unsubscribe handles — call them in session_shutdown to stop the
|
|
212
|
-
* handlers and prevent memory leaks.
|
|
213
|
-
*/
|
|
214
|
-
export function registerPermissionRpcHandlers(
|
|
215
|
-
events: PermissionEventBus,
|
|
216
|
-
deps: PermissionRpcDeps,
|
|
217
|
-
): PermissionRpcHandles {
|
|
218
|
-
const unsubCheck = events.on(PERMISSIONS_RPC_CHECK_CHANNEL, (raw) => {
|
|
219
|
-
handleCheckRpc(raw, events, deps);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const unsubPrompt = events.on(PERMISSIONS_RPC_PROMPT_CHANNEL, (raw) => {
|
|
223
|
-
void handlePromptRpc(raw, events, deps);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
return { unsubCheck, unsubPrompt };
|
|
227
|
-
}
|