@gotgenes/pi-permission-system 5.8.0 → 5.10.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 CHANGED
@@ -5,6 +5,34 @@ 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.10.0](https://github.com/gotgenes/pi-permission-system/compare/v5.9.0...v5.10.0) (2026-05-08)
9
+
10
+
11
+ ### Features
12
+
13
+ * PermissionSession class with delegation methods ([#129](https://github.com/gotgenes/pi-permission-system/issues/129)) ([a8486ce](https://github.com/gotgenes/pi-permission-system/commit/a8486ce1d0678a7617e3cc6d8131e5f3080a1bea))
14
+ * PermissionSession lifecycle, cache, agent name, and infra methods ([#129](https://github.com/gotgenes/pi-permission-system/issues/129)) ([8f6edf7](https://github.com/gotgenes/pi-permission-system/commit/8f6edf727856a974dc8061c1ff6003838d916662))
15
+
16
+
17
+ ### Documentation
18
+
19
+ * plan PermissionSession extraction ([#129](https://github.com/gotgenes/pi-permission-system/issues/129)) ([9dc21b4](https://github.com/gotgenes/pi-permission-system/commit/9dc21b457ff5ca5e95b920eed1e5b48511175cdf))
20
+ * **retro:** add retro notes for issue [#128](https://github.com/gotgenes/pi-permission-system/issues/128) ([9794053](https://github.com/gotgenes/pi-permission-system/commit/979405305ee5ddeac97186ea949373aff947a210))
21
+ * update architecture for PermissionSession ([#129](https://github.com/gotgenes/pi-permission-system/issues/129)) ([d452c50](https://github.com/gotgenes/pi-permission-system/commit/d452c50a7edb7ca1c2c7715836b007386814e1b3))
22
+
23
+ ## [5.9.0](https://github.com/gotgenes/pi-permission-system/compare/v5.8.0...v5.9.0) (2026-05-08)
24
+
25
+
26
+ ### Features
27
+
28
+ * add ForwardingManager class ([#128](https://github.com/gotgenes/pi-permission-system/issues/128)) ([7790380](https://github.com/gotgenes/pi-permission-system/commit/7790380eb0291f55724425a0bd6bd0b45cf15d91))
29
+
30
+
31
+ ### Documentation
32
+
33
+ * plan ForwardingManager extraction ([#128](https://github.com/gotgenes/pi-permission-system/issues/128)) ([2f10450](https://github.com/gotgenes/pi-permission-system/commit/2f10450974adaedd7a43e8a7d986f8f61a0508db))
34
+ * **retro:** add retro notes for issue [#127](https://github.com/gotgenes/pi-permission-system/issues/127) ([2dde534](https://github.com/gotgenes/pi-permission-system/commit/2dde53416c535331972367ca2a44ba302b25d2a0))
35
+
8
36
  ## [5.8.0](https://github.com/gotgenes/pi-permission-system/compare/v5.7.0...v5.8.0) (2026-05-08)
9
37
 
10
38
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "5.8.0",
3
+ "version": "5.10.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -0,0 +1,76 @@
1
+ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
+
3
+ import type { PermissionForwardingDeps } from "./forwarded-permissions/polling";
4
+ import { processForwardedPermissionRequests } from "./forwarded-permissions/polling";
5
+ import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding";
6
+ import { isSubagentExecutionContext } from "./subagent-context";
7
+
8
+ /**
9
+ * Narrow interface for the forwarding lifecycle used by `HandlerDeps`.
10
+ * `ForwardingManager` satisfies it; tests can provide a plain object mock.
11
+ */
12
+ export interface ForwardingController {
13
+ start(ctx: ExtensionContext): void;
14
+ stop(): void;
15
+ }
16
+
17
+ /**
18
+ * Encapsulates the forwarded-permission polling lifecycle.
19
+ *
20
+ * Owns the timer, current context, and processing-lock state that previously
21
+ * lived as 3 mutable fields on `ExtensionRuntime`. Call `start(ctx)` on each
22
+ * session event that may activate forwarding; call `stop()` on session
23
+ * shutdown.
24
+ */
25
+ export class ForwardingManager {
26
+ private timer: NodeJS.Timeout | null = null;
27
+ private context: ExtensionContext | null = null;
28
+ private processing = false;
29
+
30
+ constructor(
31
+ private readonly subagentSessionsDir: string,
32
+ private readonly forwardingDeps: PermissionForwardingDeps,
33
+ ) {}
34
+
35
+ /**
36
+ * Start polling if `ctx` has UI and is not a subagent execution context.
37
+ * No-op (timer stays running) if already polling — updates the stored
38
+ * context so the next tick uses the latest session.
39
+ * Stops any existing poll when the context does not qualify for forwarding.
40
+ */
41
+ start(ctx: ExtensionContext): void {
42
+ if (
43
+ !ctx.hasUI ||
44
+ isSubagentExecutionContext(ctx, this.subagentSessionsDir)
45
+ ) {
46
+ this.stop();
47
+ return;
48
+ }
49
+ this.context = ctx;
50
+ if (this.timer) {
51
+ return;
52
+ }
53
+ this.timer = setInterval(() => {
54
+ if (!this.context || this.processing) {
55
+ return;
56
+ }
57
+ this.processing = true;
58
+ void processForwardedPermissionRequests(
59
+ this.context,
60
+ this.forwardingDeps,
61
+ ).finally(() => {
62
+ this.processing = false;
63
+ });
64
+ }, PERMISSION_FORWARDING_POLL_INTERVAL_MS);
65
+ }
66
+
67
+ /** Stop polling and clear all internal state. */
68
+ stop(): void {
69
+ if (this.timer) {
70
+ clearInterval(this.timer);
71
+ this.timer = null;
72
+ }
73
+ this.context = null;
74
+ this.processing = false;
75
+ }
76
+ }
@@ -11,12 +11,11 @@ interface BeforeAgentStartPayload {
11
11
  import {
12
12
  createActiveToolsCacheKey,
13
13
  createBeforeAgentStartPromptStateKey,
14
- shouldApplyCachedAgentStartState,
15
14
  } from "../before-agent-start-cache";
16
- import type { PermissionManager } from "../permission-manager";
17
15
  import { resolveSkillPromptEntries } from "../skill-prompt-sanitizer";
18
16
  import { sanitizeAvailableToolsSection } from "../system-prompt-sanitizer";
19
17
  import { getToolNameFromValue } from "../tool-registry";
18
+ import type { PermissionState } from "../types";
20
19
  import type { HandlerDeps } from "./types";
21
20
 
22
21
  /**
@@ -27,12 +26,9 @@ import type { HandlerDeps } from "./types";
27
26
  export function shouldExposeTool(
28
27
  toolName: string,
29
28
  agentName: string | null,
30
- permissionManager: PermissionManager,
29
+ getToolPermission: (toolName: string, agentName?: string) => PermissionState,
31
30
  ): boolean {
32
- const toolPermission = permissionManager.getToolPermission(
33
- toolName,
34
- agentName ?? undefined,
35
- );
31
+ const toolPermission = getToolPermission(toolName, agentName ?? undefined);
36
32
  return toolPermission !== "deny";
37
33
  }
38
34
 
@@ -41,12 +37,11 @@ export async function handleBeforeAgentStart(
41
37
  event: BeforeAgentStartPayload,
42
38
  ctx: ExtensionContext,
43
39
  ): Promise<BeforeAgentStartEventResult> {
44
- deps.session.runtimeContext = ctx;
45
- deps.refreshExtensionConfig(ctx);
46
- deps.startForwardedPermissionPolling(ctx);
40
+ const { session } = deps;
41
+ session.activate(ctx);
42
+ session.refreshConfig(ctx);
47
43
 
48
- const agentName = deps.resolveAgentName(ctx, event.systemPrompt);
49
- const { permissionManager } = deps.session;
44
+ const agentName = session.resolveAgentName(ctx, event.systemPrompt);
50
45
  const allTools = deps.getAllTools();
51
46
  const allowedTools: string[] = [];
52
47
 
@@ -55,42 +50,34 @@ export async function handleBeforeAgentStart(
55
50
  if (!toolName) {
56
51
  continue;
57
52
  }
58
- if (shouldExposeTool(toolName, agentName, permissionManager)) {
53
+ if (
54
+ shouldExposeTool(toolName, agentName, (t, a) =>
55
+ session.getToolPermission(t, a),
56
+ )
57
+ ) {
59
58
  allowedTools.push(toolName);
60
59
  }
61
60
  }
62
61
 
63
62
  const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
64
- if (
65
- shouldApplyCachedAgentStartState(
66
- deps.session.lastActiveToolsCacheKey,
67
- activeToolsCacheKey,
68
- )
69
- ) {
63
+ if (session.shouldUpdateActiveTools(activeToolsCacheKey)) {
70
64
  deps.setActiveTools(allowedTools);
71
- deps.session.lastActiveToolsCacheKey = activeToolsCacheKey;
65
+ session.commitActiveToolsCacheKey(activeToolsCacheKey);
72
66
  }
73
67
 
74
68
  const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
75
69
  agentName,
76
70
  cwd: ctx.cwd,
77
- permissionStamp: permissionManager.getPolicyCacheStamp(
78
- agentName ?? undefined,
79
- ),
71
+ permissionStamp: session.getPolicyCacheStamp(agentName ?? undefined),
80
72
  systemPrompt: event.systemPrompt,
81
73
  allowedToolNames: allowedTools,
82
74
  });
83
75
 
84
- if (
85
- !shouldApplyCachedAgentStartState(
86
- deps.session.lastPromptStateCacheKey,
87
- promptStateCacheKey,
88
- )
89
- ) {
76
+ if (!session.shouldUpdatePromptState(promptStateCacheKey)) {
90
77
  return {};
91
78
  }
92
79
 
93
- deps.session.lastPromptStateCacheKey = promptStateCacheKey;
80
+ session.commitPromptStateCacheKey(promptStateCacheKey);
94
81
 
95
82
  const toolPromptResult = sanitizeAvailableToolsSection(
96
83
  event.systemPrompt,
@@ -98,11 +85,11 @@ export async function handleBeforeAgentStart(
98
85
  );
99
86
  const skillPromptResult = resolveSkillPromptEntries(
100
87
  toolPromptResult.prompt,
101
- permissionManager,
88
+ session,
102
89
  agentName,
103
90
  ctx.cwd,
104
91
  );
105
- deps.session.activeSkillEntries = skillPromptResult.entries;
92
+ session.setActiveSkillEntries(skillPromptResult.entries);
106
93
 
107
94
  if (skillPromptResult.prompt !== event.systemPrompt) {
108
95
  return { systemPrompt: skillPromptResult.prompt };
@@ -40,16 +40,16 @@ export async function handleInput(
40
40
  event: InputPayload,
41
41
  ctx: ExtensionContext,
42
42
  ): Promise<InputEventResult> {
43
- deps.session.runtimeContext = ctx;
44
- deps.startForwardedPermissionPolling(ctx);
43
+ const { session } = deps;
44
+ session.activate(ctx);
45
45
 
46
46
  const skillName = extractSkillNameFromInput(event.text);
47
47
  if (!skillName) {
48
48
  return { action: "continue" };
49
49
  }
50
50
 
51
- const agentName = deps.resolveAgentName(ctx);
52
- const check = deps.session.permissionManager.checkPermission(
51
+ const agentName = session.resolveAgentName(ctx);
52
+ const check = session.checkPermission(
53
53
  "skill",
54
54
  { name: skillName },
55
55
  agentName ?? undefined,
@@ -82,7 +82,7 @@ export async function handleInput(
82
82
  skillInputAutoApproved = decision.autoApproved === true;
83
83
  return decision;
84
84
  },
85
- writeLog: deps.logger.review,
85
+ writeLog: session.logger.review,
86
86
  logContext: {
87
87
  source: "skill_input",
88
88
  skillName,
@@ -1,6 +1,5 @@
1
1
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
- import { getActiveAgentName } from "../active-agent";
4
3
  import { PERMISSION_SYSTEM_STATUS_KEY } from "../status";
5
4
  import type { HandlerDeps } from "./types";
6
5
 
@@ -19,25 +18,19 @@ export async function handleSessionStart(
19
18
  event: SessionStartPayload,
20
19
  ctx: ExtensionContext,
21
20
  ): Promise<void> {
22
- deps.session.runtimeContext = ctx;
23
- deps.refreshExtensionConfig(ctx);
24
- deps.session.permissionManager = deps.createPermissionManagerForCwd(ctx.cwd);
25
- deps.session.activeSkillEntries = [];
26
- deps.session.lastActiveToolsCacheKey = null;
27
- deps.session.lastPromptStateCacheKey = null;
28
- deps.session.lastKnownActiveAgentName = getActiveAgentName(ctx);
29
- deps.startForwardedPermissionPolling(ctx);
30
- deps.logResolvedConfigPaths();
21
+ const { session } = deps;
22
+ session.refreshConfig(ctx);
23
+ session.resetForNewSession(ctx);
24
+ session.logResolvedConfigPaths();
31
25
 
32
- const agentName = deps.session.lastKnownActiveAgentName;
33
- const policyIssues =
34
- deps.session.permissionManager.getConfigIssues(agentName);
26
+ const agentName = session.resolveAgentName(ctx);
27
+ const policyIssues = session.getConfigIssues(agentName);
35
28
  for (const issue of policyIssues) {
36
- deps.logger.warn(issue);
29
+ session.logger.warn(issue);
37
30
  }
38
31
 
39
32
  if (event.reason === "reload") {
40
- deps.logger.debug("lifecycle.reload", {
33
+ session.logger.debug("lifecycle.reload", {
41
34
  triggeredBy: "session_start",
42
35
  reason: event.reason,
43
36
  cwd: ctx.cwd,
@@ -53,30 +46,21 @@ export async function handleResourcesDiscover(
53
46
  return;
54
47
  }
55
48
 
56
- const { runtimeContext } = deps.session;
57
- deps.session.permissionManager = deps.createPermissionManagerForCwd(
58
- runtimeContext?.cwd,
59
- );
60
- deps.session.activeSkillEntries = [];
61
- deps.session.lastActiveToolsCacheKey = null;
62
- deps.session.lastPromptStateCacheKey = null;
63
- deps.logger.debug("lifecycle.reload", {
49
+ const { session } = deps;
50
+ session.reload();
51
+ session.logger.debug("lifecycle.reload", {
64
52
  triggeredBy: "resources_discover",
65
53
  reason: event.reason,
66
- cwd: runtimeContext?.cwd ?? null,
54
+ cwd: session.getRuntimeContext()?.cwd ?? null,
67
55
  });
68
56
  }
69
57
 
70
58
  export async function handleSessionShutdown(deps: HandlerDeps): Promise<void> {
71
- const { runtimeContext } = deps.session;
72
- if (runtimeContext) {
73
- runtimeContext.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
59
+ const { session } = deps;
60
+ const ctx = session.getRuntimeContext();
61
+ if (ctx) {
62
+ ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
74
63
  }
75
- deps.session.runtimeContext = null;
76
- deps.session.activeSkillEntries = [];
77
- deps.session.lastActiveToolsCacheKey = null;
78
- deps.session.lastPromptStateCacheKey = null;
79
- deps.session.sessionRules.clear();
80
- deps.stopForwardedPermissionPolling();
64
+ session.shutdown();
81
65
  deps.stopPermissionRpcHandlers();
82
66
  }
@@ -43,10 +43,10 @@ export async function handleToolCall(
43
43
  event: unknown,
44
44
  ctx: ExtensionContext,
45
45
  ): Promise<{ block?: true; reason?: string }> {
46
- deps.session.runtimeContext = ctx;
47
- deps.startForwardedPermissionPolling(ctx);
46
+ const { session } = deps;
47
+ session.activate(ctx);
48
48
 
49
- const agentName = deps.resolveAgentName(ctx);
49
+ const agentName = session.resolveAgentName(ctx);
50
50
  const toolName = getToolNameFromValue(event);
51
51
 
52
52
  if (!toolName) {
@@ -91,22 +91,16 @@ export async function handleToolCall(
91
91
  deps.promptPermission(ctx, details);
92
92
  const emitDecision: GateRunnerDeps["emitDecision"] = (e) =>
93
93
  emitDecisionEvent(deps.events, e);
94
- const { review: writeReviewLog } = deps.logger;
94
+ const writeReviewLog = session.logger.review;
95
95
  const checkPermission: GateRunnerDeps["checkPermission"] = (
96
96
  surface,
97
97
  input,
98
98
  agent,
99
99
  sessionRules,
100
- ) =>
101
- deps.session.permissionManager.checkPermission(
102
- surface,
103
- input,
104
- agent,
105
- sessionRules,
106
- );
107
- const getSessionRuleset = () => deps.session.sessionRules.getRuleset();
100
+ ) => session.checkPermission(surface, input, agent, sessionRules);
101
+ const getSessionRuleset = () => session.getSessionRuleset();
108
102
  const approveSessionRule = (surface: string, pattern: string) =>
109
- deps.session.sessionRules.approve(surface, pattern);
103
+ session.approveSessionRule(surface, pattern);
110
104
 
111
105
  // ── Shared runner deps (built once, reused for all gates) ─────────────
112
106
  const runnerDeps: GateRunnerDeps = {
@@ -120,9 +114,8 @@ export async function handleToolCall(
120
114
  };
121
115
 
122
116
  // ── Skill-read gate (descriptor + runner) ────────────────────────────────
123
- const skillDescriptor = describeSkillReadGate(
124
- tcc,
125
- () => deps.session.activeSkillEntries,
117
+ const skillDescriptor = describeSkillReadGate(tcc, () =>
118
+ session.getActiveSkillEntries(),
126
119
  );
127
120
  if (skillDescriptor) {
128
121
  const skillResult = await runGateCheck(
@@ -138,8 +131,8 @@ export async function handleToolCall(
138
131
 
139
132
  // ── External-directory gate (descriptor + runner) ─────────────────────────
140
133
  const infraDirs = [
141
- ...deps.piInfrastructureDirs,
142
- ...deps.getPiInfrastructureReadPaths(),
134
+ ...session.getInfrastructureDirs(),
135
+ ...session.getInfrastructureReadPaths(),
143
136
  ];
144
137
  const extDirDesc = describeExternalDirectoryGate(tcc, infraDirs);
145
138
  if (extDirDesc) {
@@ -2,9 +2,7 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
3
  import type { PermissionPromptDecision } from "../permission-dialog";
4
4
  import type { PermissionEventBus } from "../permission-events";
5
- import type { PermissionManager } from "../permission-manager";
6
- import type { SessionState } from "../runtime";
7
- import type { SessionLogger } from "../session-logger";
5
+ import type { PermissionSession } from "../permission-session";
8
6
 
9
7
  export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
10
8
 
@@ -28,46 +26,23 @@ export interface PromptPermissionDetails {
28
26
  /**
29
27
  * Explicit dependency bag passed to each extracted event handler.
30
28
  *
31
- * Mutable session state lives in `session`; handlers read and write
32
- * `deps.session.*` directly. Logging, infrastructure paths, and the
33
- * event bus are promoted to top-level fields so handlers and gate
34
- * adapters never reach through nested objects for leaf operations.
29
+ * `session` is a `PermissionSession` that encapsulates all mutable state
30
+ * and exposes operations instead of fields — eliminating LoD violations,
31
+ * output arguments, and scattered field resets.
32
+ *
33
+ * Remaining top-level fields are things the session does not own:
34
+ * event bus, RPC cleanup, Pi tool API, and permission request ID generation.
35
35
  */
36
36
  export interface HandlerDeps {
37
- // ── Session state ─────────────────────────────────────────────────────
38
- /** Mutable session state: permissionManager, sessionRules, cache keys. */
39
- readonly session: SessionState;
40
-
41
- // ── Logging ────────────────────────────────────────────────────────────
42
- readonly logger: SessionLogger;
43
-
44
- // ── Immutable infrastructure paths ───────────────────────────────────
45
- readonly piInfrastructureDirs: readonly string[];
46
- /** Returns config-derived infrastructure read paths (current at call time). */
47
- getPiInfrastructureReadPaths(): string[];
37
+ // ── Session ─────────────────────────────────────────────────────────
38
+ /** Encapsulates all mutable session state and permission operations. */
39
+ readonly session: PermissionSession;
48
40
 
49
41
  // ── Event bus ────────────────────────────────────────────────────────
50
42
  /** Event bus for emitting permissions:decision broadcast events. */
51
43
  readonly events: PermissionEventBus;
52
44
 
53
- // ── Factories ──────────────────────────────────────────────────────────
54
- /** Create a new PermissionManager scoped to cwd's config hierarchy. */
55
- createPermissionManagerForCwd(
56
- cwd: string | undefined | null,
57
- ): PermissionManager;
58
-
59
- // ── Config & lifecycle helpers ─────────────────────────────────────────
60
- /** Reload merged config from disk; optionally update the stored runtime context. */
61
- refreshExtensionConfig(ctx?: ExtensionContext): void;
62
- /** Write the resolved config path set to the review and debug logs. */
63
- logResolvedConfigPaths(): void;
64
-
65
45
  // ── Permission helpers ─────────────────────────────────────────────────
66
- /**
67
- * Resolve the active agent name from the session context or system prompt.
68
- * Updates session.lastKnownActiveAgentName as a side effect.
69
- */
70
- resolveAgentName(ctx: ExtensionContext, systemPrompt?: string): string | null;
71
46
  /** Whether the current context can show an interactive permission prompt. */
72
47
  canRequestPermissionConfirmation(ctx: ExtensionContext): boolean;
73
48
  /** Prompt the user for a permission decision, log the outcome, and return it. */
@@ -78,9 +53,7 @@ export interface HandlerDeps {
78
53
  /** Generate a unique ID for a permission request. */
79
54
  createPermissionRequestId(prefix: string): string;
80
55
 
81
- // ── Forwarding ─────────────────────────────────────────────────────────
82
- startForwardedPermissionPolling(ctx: ExtensionContext): void;
83
- stopForwardedPermissionPolling(): void;
56
+ // ── Lifecycle ───────────────────────────────────────────────────────────
84
57
  /** Unsubscribe the permissions:rpc:check and permissions:rpc:prompt handlers. */
85
58
  stopPermissionRpcHandlers(): void;
86
59
 
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2
2
  import { registerPermissionSystemCommand } from "./config-modal";
3
3
  import { getGlobalConfigPath } from "./config-paths";
4
4
  import type { PermissionForwardingDeps } from "./forwarded-permissions/polling";
5
+ import { ForwardingManager } from "./forwarding-manager";
5
6
  import {
6
7
  type HandlerDeps,
7
8
  handleBeforeAgentStart,
@@ -15,15 +16,12 @@ import { requestPermissionDecisionFromUi } from "./permission-dialog";
15
16
  import { registerPermissionRpcHandlers } from "./permission-event-rpc";
16
17
  import { emitReadyEvent } from "./permission-events";
17
18
  import { PermissionPrompter } from "./permission-prompter";
19
+ import { PermissionSession } from "./permission-session";
18
20
  import {
19
21
  createExtensionRuntime,
20
- createPermissionManagerForCwd,
21
22
  logResolvedConfigPaths,
22
23
  refreshExtensionConfig,
23
- resolveAgentName,
24
24
  saveExtensionConfig,
25
- startForwardedPermissionPolling,
26
- stopForwardedPermissionPolling,
27
25
  } from "./runtime";
28
26
  import { createSessionLogger } from "./session-logger";
29
27
  import { isSubagentExecutionContext } from "./subagent-context";
@@ -57,6 +55,18 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
57
55
  };
58
56
 
59
57
  refreshExtensionConfig(runtime);
58
+
59
+ const session = new PermissionSession(
60
+ runtime,
61
+ createSessionLogger(runtime),
62
+ new ForwardingManager(runtime.subagentSessionsDir, forwardingDeps),
63
+ {
64
+ refreshExtensionConfig: (ctx) => refreshExtensionConfig(runtime, ctx),
65
+ logResolvedConfigPaths: () => logResolvedConfigPaths(runtime),
66
+ getConfig: () => runtime.config,
67
+ },
68
+ );
69
+
60
70
  registerPermissionSystemCommand(pi, {
61
71
  getConfig: () => runtime.config,
62
72
  setConfig: (next, ctx) => saveExtensionConfig(runtime, next, ctx),
@@ -79,18 +89,8 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
79
89
  });
80
90
 
81
91
  const deps: HandlerDeps = {
82
- session: runtime,
83
- logger: createSessionLogger(runtime),
84
- piInfrastructureDirs: runtime.piInfrastructureDirs,
85
- getPiInfrastructureReadPaths: () =>
86
- runtime.config.piInfrastructureReadPaths ?? [],
92
+ session,
87
93
  events: pi.events,
88
- createPermissionManagerForCwd: (cwd) =>
89
- createPermissionManagerForCwd(runtime.agentDir, cwd),
90
- refreshExtensionConfig: (ctx) => refreshExtensionConfig(runtime, ctx),
91
- logResolvedConfigPaths: () => logResolvedConfigPaths(runtime),
92
- resolveAgentName: (ctx, systemPrompt) =>
93
- resolveAgentName(runtime, ctx, systemPrompt),
94
94
  canRequestPermissionConfirmation: (ctx) =>
95
95
  canResolveAskPermissionRequest({
96
96
  config: runtime.config,
@@ -102,10 +102,6 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
102
102
  }),
103
103
  promptPermission: (ctx, details) => prompter.prompt(ctx, details),
104
104
  createPermissionRequestId,
105
- startForwardedPermissionPolling: (ctx) =>
106
- startForwardedPermissionPolling(runtime, forwardingDeps, ctx),
107
- stopForwardedPermissionPolling: () =>
108
- stopForwardedPermissionPolling(runtime),
109
105
  stopPermissionRpcHandlers: () => {
110
106
  rpcHandles.unsubCheck();
111
107
  rpcHandles.unsubPrompt();