@gotgenes/pi-permission-system 8.3.0 → 8.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,20 @@ 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
+ ## [8.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.3.0...pi-permission-system-v8.3.1) (2026-06-01)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * add process-global SubagentSessionRegistry accessor ([#296](https://github.com/gotgenes/pi-packages/issues/296)) ([d3fd3b0](https://github.com/gotgenes/pi-packages/commit/d3fd3b04223b2d276873094ad8c14f239654b8c8))
14
+ * share SubagentSessionRegistry across parent and child sessions ([#296](https://github.com/gotgenes/pi-packages/issues/296)) ([fed676a](https://github.com/gotgenes/pi-packages/commit/fed676aaa485abe8db158e522ba898705f3dff94))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * explain process-global subagent registry across session buses ([#296](https://github.com/gotgenes/pi-packages/issues/296)) ([1804dbb](https://github.com/gotgenes/pi-packages/commit/1804dbbb766d7b7fbc0e49da877f3238f5c3e8dc))
20
+ * use ADR-NNNN with links docs-wide ([c6b6431](https://github.com/gotgenes/pi-packages/commit/c6b6431c004f324931f23be46cf2e47e8fdac919))
21
+
8
22
  ## [8.3.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v8.2.1...pi-permission-system-v8.3.0) (2026-06-01)
9
23
 
10
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "8.3.0",
3
+ "version": "8.3.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
package/src/index.ts CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  import { createSessionLogger } from "./session-logger";
30
30
  import { isSubagentExecutionContext } from "./subagent-context";
31
31
  import { subscribeSubagentLifecycle } from "./subagent-lifecycle-events";
32
- import { SubagentSessionRegistry } from "./subagent-registry";
32
+ import { getSubagentSessionRegistry } from "./subagent-registry";
33
33
  import { ToolInputFormatterRegistry } from "./tool-input-formatter-registry";
34
34
  import {
35
35
  canResolveAskPermissionRequest,
@@ -38,7 +38,7 @@ import {
38
38
 
39
39
  export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
40
40
  const runtime = createExtensionRuntime();
41
- const subagentRegistry = new SubagentSessionRegistry();
41
+ const subagentRegistry = getSubagentSessionRegistry();
42
42
  const formatterRegistry = new ToolInputFormatterRegistry();
43
43
  registerBuiltinToolInputFormatters(formatterRegistry);
44
44
 
@@ -10,8 +10,45 @@
10
10
  * The registry is keyed by session directory path, which is unique per
11
11
  * session and available to both producer and consumer via
12
12
  * `ctx.sessionManager.getSessionDir()`.
13
+ *
14
+ * The single registry instance is stored on `globalThis` (via `Symbol.for()`)
15
+ * so that the parent's permission-system instance (which registers children
16
+ * on the parent's event bus) and each child's separate jiti instance (which
17
+ * reads the registry to detect itself and resolve its forwarding target) share
18
+ * one store across per-session event buses. See `getSubagentSessionRegistry()`.
13
19
  */
14
20
 
21
+ /** Process-global key for the shared registry slot. */
22
+ const SUBAGENT_SESSION_REGISTRY_KEY = Symbol.for(
23
+ "@gotgenes/pi-permission-system:subagent-registry",
24
+ );
25
+
26
+ /**
27
+ * Return the process-global SubagentSessionRegistry, creating it on first call.
28
+ *
29
+ * Backed by `globalThis` + `Symbol.for()` so the parent's permission-system
30
+ * instance (which registers children on the parent event bus) and each child's
31
+ * separate jiti instance (which reads the registry to detect itself and resolve
32
+ * its forwarding target) share one store across per-session event buses.
33
+ *
34
+ * Intentionally has no shutdown/unpublish hook — a child's `session_shutdown`
35
+ * must not be able to wipe the parent's registrations. Entries are added and
36
+ * removed exclusively by the parent's `subagents:child:session-created` /
37
+ * `subagents:child:disposed` subscription.
38
+ */
39
+ export function getSubagentSessionRegistry(): SubagentSessionRegistry {
40
+ const store = globalThis as Record<symbol, unknown>;
41
+ const existing = store[SUBAGENT_SESSION_REGISTRY_KEY] as
42
+ | SubagentSessionRegistry
43
+ | undefined;
44
+ if (existing) {
45
+ return existing;
46
+ }
47
+ const registry = new SubagentSessionRegistry();
48
+ store[SUBAGENT_SESSION_REGISTRY_KEY] = registry;
49
+ return registry;
50
+ }
51
+
15
52
  /** Signal stored per registered in-process subagent session. */
16
53
  export interface SubagentSessionInfo {
17
54
  /** Parent session ID for permission forwarding. Omit when unknown. */
@@ -1,9 +1,14 @@
1
- import { describe, expect, test } from "vitest";
1
+ import { afterEach, describe, expect, test } from "vitest";
2
2
  import {
3
+ getSubagentSessionRegistry,
3
4
  type SubagentSessionInfo,
4
5
  SubagentSessionRegistry,
5
6
  } from "#src/subagent-registry";
6
7
 
8
+ const REGISTRY_KEY = Symbol.for(
9
+ "@gotgenes/pi-permission-system:subagent-registry",
10
+ );
11
+
7
12
  function makeInfo(
8
13
  overrides: Partial<SubagentSessionInfo> = {},
9
14
  ): SubagentSessionInfo {
@@ -92,3 +97,42 @@ describe("SubagentSessionRegistry", () => {
92
97
  expect(registry.has("/sessions/task-2")).toBe(true);
93
98
  });
94
99
  });
100
+
101
+ // ── process-global accessor ────────────────────────────────────────────────
102
+
103
+ describe("getSubagentSessionRegistry (process-global accessor)", () => {
104
+ afterEach(() => {
105
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- Symbol-keyed global property; Map.delete() is not applicable
106
+ delete (globalThis as Record<symbol, unknown>)[REGISTRY_KEY];
107
+ });
108
+
109
+ test("returns a SubagentSessionRegistry instance", () => {
110
+ const registry = getSubagentSessionRegistry();
111
+ expect(registry).toBeInstanceOf(SubagentSessionRegistry);
112
+ });
113
+
114
+ test("returns the same instance on repeated calls", () => {
115
+ const first = getSubagentSessionRegistry();
116
+ const second = getSubagentSessionRegistry();
117
+ expect(first).toBe(second);
118
+ });
119
+
120
+ test("state registered through one call is visible through another call", () => {
121
+ const writer = getSubagentSessionRegistry();
122
+ writer.register("/sessions/child-tasks", {
123
+ agentName: "Explore",
124
+ parentSessionId: "parent-abc",
125
+ });
126
+
127
+ const reader = getSubagentSessionRegistry();
128
+ expect(reader.has("/sessions/child-tasks")).toBe(true);
129
+ expect(reader.get("/sessions/child-tasks")?.parentSessionId).toBe(
130
+ "parent-abc",
131
+ );
132
+ });
133
+
134
+ test("starts empty on first call", () => {
135
+ const registry = getSubagentSessionRegistry();
136
+ expect(registry.has("/sessions/any-key")).toBe(false);
137
+ });
138
+ });