@gotgenes/pi-permission-system 10.3.1 → 10.4.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,18 @@ 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
+ ## [10.4.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.3.1...pi-permission-system-v10.4.0) (2026-06-07)
9
+
10
+
11
+ ### Features
12
+
13
+ * add context-owning PromptingGateway ([1885be2](https://github.com/gotgenes/pi-packages/commit/1885be28fb797eb5ed67a7a30d51e58fa73e3ff0))
14
+
15
+
16
+ ### Documentation
17
+
18
+ * mark Phase 4 Step 6 complete; drop unused beforeEach import ([217057a](https://github.com/gotgenes/pi-packages/commit/217057ab5f8a1d3290322b442e267287b31635cf))
19
+
8
20
  ## [10.3.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.3.0...pi-permission-system-v10.3.1) (2026-06-06)
9
21
 
10
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "10.3.1",
3
+ "version": "10.4.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -14,9 +14,12 @@ import type { Ruleset } from "./rule";
14
14
 
15
15
  interface PermissionSystemConfigController {
16
16
  config: CommandConfigStore;
17
- getConfigPath(): string;
18
- /** Optional: returns the composed config-layer ruleset for origin display. */
19
- getComposedRules?(): Ruleset;
17
+ /** Precomputed global config file path. */
18
+ configPath: string;
19
+ /** Returns the composed config-layer ruleset for origin display. */
20
+ permissionManager: { getComposedConfigRules(agentName?: string): Ruleset };
21
+ /** Provides the active agent name for scoped rule lookup. */
22
+ session: { readonly lastKnownActiveAgentName: string | null };
20
23
  }
21
24
 
22
25
  const ON_OFF = ["on", "off"];
@@ -203,7 +206,9 @@ function handleArgs(
203
206
  }
204
207
 
205
208
  if (normalized === "show") {
206
- const rules = controller.getComposedRules?.();
209
+ const rules = controller.permissionManager.getComposedConfigRules(
210
+ controller.session.lastKnownActiveAgentName ?? undefined,
211
+ );
207
212
  ctx.ui.notify(
208
213
  `permission-system: ${summarizeConfig(controller.config.current(), rules)}`,
209
214
  "info",
@@ -212,10 +217,7 @@ function handleArgs(
212
217
  }
213
218
 
214
219
  if (normalized === "path") {
215
- ctx.ui.notify(
216
- `permission-system config: ${controller.getConfigPath()}`,
217
- "info",
218
- );
220
+ ctx.ui.notify(`permission-system config: ${controller.configPath}`, "info");
219
221
  return true;
220
222
  }
221
223
 
@@ -26,6 +26,7 @@ import {
26
26
  type PermissionSystemExtensionConfig,
27
27
  } from "./extension-config";
28
28
  import type { ResolvedPolicyPaths } from "./policy-loader";
29
+ import type { DebugReviewLogger } from "./session-logger";
29
30
  import { syncPermissionSystemStatus } from "./status";
30
31
 
31
32
  /** Read-only view of the current config — for consumers that only read. */
@@ -57,12 +58,6 @@ export interface CommandConfigStore extends ConfigReader {
57
58
  ): void;
58
59
  }
59
60
 
60
- /** Narrow logging sink — replaced by an injected logger in Step 3 (#336). */
61
- export interface ConfigStoreLogger {
62
- writeDebugLog(event: string, details?: Record<string, unknown>): void;
63
- writeReviewLog(event: string, details?: Record<string, unknown>): void;
64
- }
65
-
66
61
  /** Narrow view of the manager's resolved policy paths (for `logResolvedPaths`). */
67
62
  export interface ResolvedPolicyPathProvider {
68
63
  getResolvedPolicyPaths(): ResolvedPolicyPaths;
@@ -71,7 +66,7 @@ export interface ResolvedPolicyPathProvider {
71
66
  export interface ConfigStoreDeps {
72
67
  agentDir: string;
73
68
  policyPaths: ResolvedPolicyPathProvider;
74
- logger: ConfigStoreLogger;
69
+ logger: DebugReviewLogger;
75
70
  }
76
71
 
77
72
  /**
@@ -127,7 +122,7 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
127
122
  this.lastConfigWarning = null;
128
123
  }
129
124
 
130
- this.deps.logger.writeDebugLog("config.loaded", {
125
+ this.deps.logger.debug("config.loaded", {
131
126
  warning: warning ?? null,
132
127
  debugLog: runtimeConfig.debugLog,
133
128
  permissionReviewLog: runtimeConfig.permissionReviewLog,
@@ -183,7 +178,7 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
183
178
  syncPermissionSystemStatus(ctx, normalized);
184
179
  this.lastConfigWarning = null;
185
180
 
186
- this.deps.logger.writeDebugLog("config.saved", {
181
+ this.deps.logger.debug("config.saved", {
187
182
  debugLog: normalized.debugLog,
188
183
  permissionReviewLog: normalized.permissionReviewLog,
189
184
  yoloMode: normalized.yoloMode,
@@ -215,11 +210,11 @@ export class ConfigStore implements SessionConfigStore, CommandConfigStore {
215
210
  legacyProjectPolicyDetected,
216
211
  legacyExtensionConfigDetected,
217
212
  });
218
- this.deps.logger.writeReviewLog(
213
+ this.deps.logger.review(
219
214
  "config.resolved",
220
215
  entry as unknown as Record<string, unknown>,
221
216
  );
222
- this.deps.logger.writeDebugLog(
217
+ this.deps.logger.debug(
223
218
  "config.resolved",
224
219
  entry as unknown as Record<string, unknown>,
225
220
  );
@@ -17,6 +17,7 @@ import {
17
17
  type ForwardedPermissionResponse,
18
18
  type PermissionForwardingLocation,
19
19
  } from "#src/permission-forwarding";
20
+ import type { DebugReviewLogger } from "#src/session-logger";
20
21
 
21
22
  /** Valid `permissions:ui_prompt` source values, for tolerant request reads. */
22
23
  const UI_PROMPT_SOURCES = [
@@ -41,13 +42,6 @@ function asNullableDisplayString(value: unknown): string | null | undefined {
41
42
  return undefined;
42
43
  }
43
44
 
44
- type LogFn = (event: string, details: Record<string, unknown>) => void;
45
-
46
- export interface ForwardedPermissionLogger {
47
- writeReviewLog: LogFn;
48
- writeDebugLog: LogFn;
49
- }
50
-
51
45
  export function formatUnknownErrorMessage(error: unknown): string {
52
46
  if (error instanceof Error && error.message) {
53
47
  return error.message;
@@ -69,7 +63,7 @@ export function isErrnoCode(error: unknown, code: string): boolean {
69
63
  * Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
70
64
  */
71
65
  export function logPermissionForwardingWarning(
72
- logger: ForwardedPermissionLogger | null,
66
+ logger: DebugReviewLogger | null,
73
67
  message: string,
74
68
  error?: unknown,
75
69
  ): void {
@@ -78,8 +72,8 @@ export function logPermissionForwardingWarning(
78
72
  ? { message }
79
73
  : { message, error: formatUnknownErrorMessage(error) };
80
74
 
81
- logger?.writeReviewLog("permission_forwarding.warning", details);
82
- logger?.writeDebugLog("permission_forwarding.warning", details);
75
+ logger?.review("permission_forwarding.warning", details);
76
+ logger?.debug("permission_forwarding.warning", details);
83
77
  }
84
78
 
85
79
  /**
@@ -87,7 +81,7 @@ export function logPermissionForwardingWarning(
87
81
  * Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
88
82
  */
89
83
  export function logPermissionForwardingError(
90
- logger: ForwardedPermissionLogger | null,
84
+ logger: DebugReviewLogger | null,
91
85
  message: string,
92
86
  error?: unknown,
93
87
  ): void {
@@ -96,12 +90,12 @@ export function logPermissionForwardingError(
96
90
  ? { message }
97
91
  : { message, error: formatUnknownErrorMessage(error) };
98
92
 
99
- logger?.writeReviewLog("permission_forwarding.error", details);
100
- logger?.writeDebugLog("permission_forwarding.error", details);
93
+ logger?.review("permission_forwarding.error", details);
94
+ logger?.debug("permission_forwarding.error", details);
101
95
  }
102
96
 
103
97
  export function ensureDirectoryExists(
104
- logger: ForwardedPermissionLogger | null,
98
+ logger: DebugReviewLogger | null,
105
99
  path: string,
106
100
  description: string,
107
101
  ): boolean {
@@ -126,7 +120,7 @@ export function getPermissionForwardingLocationForSession(
126
120
  }
127
121
 
128
122
  export function ensurePermissionForwardingLocation(
129
- logger: ForwardedPermissionLogger | null,
123
+ logger: DebugReviewLogger | null,
130
124
  forwardingDir: string,
131
125
  sessionId: string,
132
126
  ): PermissionForwardingLocation | null {
@@ -182,7 +176,7 @@ export function getExistingPermissionForwardingLocation(
182
176
  }
183
177
 
184
178
  export function tryRemoveDirectoryIfEmpty(
185
- logger: ForwardedPermissionLogger | null,
179
+ logger: DebugReviewLogger | null,
186
180
  path: string,
187
181
  description: string,
188
182
  ): void {
@@ -222,7 +216,7 @@ export function tryRemoveDirectoryIfEmpty(
222
216
  }
223
217
 
224
218
  export function cleanupPermissionForwardingLocationIfEmpty(
225
- logger: ForwardedPermissionLogger | null,
219
+ logger: DebugReviewLogger | null,
226
220
  location: PermissionForwardingLocation,
227
221
  ): void {
228
222
  tryRemoveDirectoryIfEmpty(
@@ -243,7 +237,7 @@ export function cleanupPermissionForwardingLocationIfEmpty(
243
237
  }
244
238
 
245
239
  export function safeDeleteFile(
246
- logger: ForwardedPermissionLogger | null,
240
+ logger: DebugReviewLogger | null,
247
241
  filePath: string,
248
242
  description: string,
249
243
  ): void {
@@ -263,7 +257,7 @@ export function safeDeleteFile(
263
257
  }
264
258
 
265
259
  export function writeJsonFileAtomic(
266
- logger: ForwardedPermissionLogger | null,
260
+ logger: DebugReviewLogger | null,
267
261
  filePath: string,
268
262
  value: unknown,
269
263
  ): void {
@@ -279,7 +273,7 @@ export function writeJsonFileAtomic(
279
273
  }
280
274
 
281
275
  export function readForwardedPermissionRequest(
282
- logger: ForwardedPermissionLogger | null,
276
+ logger: DebugReviewLogger | null,
283
277
  filePath: string,
284
278
  ): ForwardedPermissionRequest | null {
285
279
  try {
@@ -326,7 +320,7 @@ export function readForwardedPermissionRequest(
326
320
  }
327
321
 
328
322
  export function readForwardedPermissionResponse(
329
- logger: ForwardedPermissionLogger | null,
323
+ logger: DebugReviewLogger | null,
330
324
  filePath: string,
331
325
  ): ForwardedPermissionResponse | null {
332
326
  try {
@@ -370,7 +364,7 @@ export function readForwardedPermissionResponse(
370
364
  }
371
365
 
372
366
  export function listRequestFiles(
373
- logger: ForwardedPermissionLogger | null,
367
+ logger: DebugReviewLogger | null,
374
368
  requestsDir: string,
375
369
  ): string[] {
376
370
  try {
@@ -7,6 +7,7 @@ import {
7
7
  getActiveAgentNameFromSystemPrompt,
8
8
  } from "#src/active-agent";
9
9
  import { toRecord } from "#src/common";
10
+ import type { ConfigReader } from "#src/config-store";
10
11
  import type {
11
12
  PermissionPromptDecision,
12
13
  RequestPermissionOptions,
@@ -27,13 +28,14 @@ import {
27
28
  SUBAGENT_PARENT_SESSION_ENV_CANDIDATES,
28
29
  } from "#src/permission-forwarding";
29
30
  import { buildForwardedUiPrompt } from "#src/permission-ui-prompt";
31
+ import type { DebugReviewLogger } from "#src/session-logger";
30
32
  import { isSubagentExecutionContext } from "#src/subagent-context";
31
33
  import type { SubagentSessionRegistry } from "#src/subagent-registry";
34
+ import { shouldAutoApprovePermissionState } from "#src/yolo-mode";
32
35
 
33
36
  import {
34
37
  cleanupPermissionForwardingLocationIfEmpty,
35
38
  ensurePermissionForwardingLocation,
36
- type ForwardedPermissionLogger,
37
39
  getExistingPermissionForwardingLocation,
38
40
  listRequestFiles,
39
41
  logPermissionForwardingError,
@@ -59,15 +61,15 @@ export interface PermissionForwarderDeps {
59
61
  registry?: SubagentSessionRegistry;
60
62
  /** Event bus used for UI prompt broadcasts. */
61
63
  events?: PermissionEventBus;
62
- logger: ForwardedPermissionLogger;
63
- writeReviewLog: (event: string, details: Record<string, unknown>) => void;
64
+ logger: DebugReviewLogger;
64
65
  requestPermissionDecisionFromUi: (
65
66
  ui: ExtensionContext["ui"],
66
67
  title: string,
67
68
  message: string,
68
69
  options?: RequestPermissionOptions,
69
70
  ) => Promise<PermissionPromptDecision>;
70
- shouldAutoApprove: () => boolean;
71
+ /** Read current config for yolo-mode auto-approve check (called at prompt time). */
72
+ config: ConfigReader;
71
73
  }
72
74
 
73
75
  // ── Module-private helpers ────────────────────────────────────────────────
@@ -165,18 +167,14 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
165
167
  private readonly subagentSessionsDir: string;
166
168
  private readonly registry: SubagentSessionRegistry | undefined;
167
169
  private readonly events: PermissionEventBus | undefined;
168
- private readonly logger: ForwardedPermissionLogger;
169
- private readonly writeReviewLog: (
170
- event: string,
171
- details: Record<string, unknown>,
172
- ) => void;
170
+ private readonly logger: DebugReviewLogger;
173
171
  private readonly requestPermissionDecisionFromUi: (
174
172
  ui: ExtensionContext["ui"],
175
173
  title: string,
176
174
  message: string,
177
175
  options?: RequestPermissionOptions,
178
176
  ) => Promise<PermissionPromptDecision>;
179
- private readonly shouldAutoApprove: () => boolean;
177
+ private readonly config: ConfigReader;
180
178
 
181
179
  constructor(deps: PermissionForwarderDeps) {
182
180
  this.forwardingDir = deps.forwardingDir;
@@ -184,9 +182,8 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
184
182
  this.registry = deps.registry;
185
183
  this.events = deps.events;
186
184
  this.logger = deps.logger;
187
- this.writeReviewLog = deps.writeReviewLog;
188
185
  this.requestPermissionDecisionFromUi = deps.requestPermissionDecisionFromUi;
189
- this.shouldAutoApprove = deps.shouldAutoApprove;
186
+ this.config = deps.config;
190
187
  }
191
188
 
192
189
  // ── Public seam methods ────────────────────────────────────────────────
@@ -319,7 +316,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
319
316
  const requestPath = join(location.requestsDir, `${request.id}.json`);
320
317
  const responsePath = join(location.responsesDir, `${request.id}.json`);
321
318
 
322
- this.writeReviewLog("forwarded_permission.request_created", {
319
+ this.logger.review("forwarded_permission.request_created", {
323
320
  requestId: request.id,
324
321
  requesterAgentName: request.requesterAgentName,
325
322
  requesterSessionId: request.requesterSessionId,
@@ -391,7 +388,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
391
388
  this.logger,
392
389
  responsePath,
393
390
  );
394
- this.writeReviewLog("forwarded_permission.response_received", {
391
+ this.logger.review("forwarded_permission.response_received", {
395
392
  requestId,
396
393
  approved: response?.approved ?? null,
397
394
  state: response?.state ?? null,
@@ -421,7 +418,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
421
418
  this.logger,
422
419
  `Timed out waiting for forwarded permission response '${responsePath}'`,
423
420
  );
424
- this.writeReviewLog("forwarded_permission.response_timed_out", {
421
+ this.logger.review("forwarded_permission.response_timed_out", {
425
422
  requestId,
426
423
  requesterAgentName,
427
424
  targetSessionId,
@@ -465,14 +462,14 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
465
462
  approved: false,
466
463
  state: "denied",
467
464
  };
468
- if (this.shouldAutoApprove()) {
469
- this.writeReviewLog(
465
+ if (shouldAutoApprovePermissionState("ask", this.config.current())) {
466
+ this.logger.review(
470
467
  "forwarded_permission.auto_approved",
471
468
  forwardedPermissionLogDetails,
472
469
  );
473
470
  decision = { approved: true, state: "approved" };
474
471
  } else {
475
- this.writeReviewLog(
472
+ this.logger.review(
476
473
  "forwarded_permission.prompted",
477
474
  forwardedPermissionLogDetails,
478
475
  );
@@ -508,7 +505,7 @@ export class PermissionForwarder implements ApprovalRequester, InboxProcessor {
508
505
  }
509
506
 
510
507
  const responsePath = join(location.responsesDir, `${request.id}.json`);
511
- this.writeReviewLog(
508
+ this.logger.review(
512
509
  decision.approved
513
510
  ? "forwarded_permission.approved"
514
511
  : "forwarded_permission.denied",
@@ -8,7 +8,5 @@ import type { PromptPermissionDetails } from "./permission-prompter";
8
8
  */
9
9
  export interface GatePrompter {
10
10
  canConfirm(): boolean;
11
- promptPermission(
12
- details: PromptPermissionDetails,
13
- ): Promise<PermissionPromptDecision>;
11
+ prompt(details: PromptPermissionDetails): Promise<PermissionPromptDecision>;
14
12
  }
@@ -121,7 +121,7 @@ export class GateRunner {
121
121
  canConfirm,
122
122
  sessionApproval: descriptor.sessionApproval?.toGateApproval(),
123
123
  promptForApproval: async () => {
124
- const decision = await this.prompter.promptPermission({
124
+ const decision = await this.prompter.prompt({
125
125
  requestId: toolCallId,
126
126
  ...descriptor.promptDetails,
127
127
  });
package/src/index.ts CHANGED
@@ -25,17 +25,13 @@ import { PermissionManager } from "./permission-manager";
25
25
  import { PermissionPrompter } from "./permission-prompter";
26
26
  import { PermissionSession } from "./permission-session";
27
27
  import { LocalPermissionsService } from "./permissions-service";
28
+ import { PromptingGateway } from "./prompting-gateway";
28
29
  import { PermissionServiceLifecycle } from "./service-lifecycle";
29
30
  import { createSessionLogger } from "./session-logger";
30
31
  import { SessionRules } from "./session-rules";
31
- import { isSubagentExecutionContext } from "./subagent-context";
32
32
  import { subscribeSubagentLifecycle } from "./subagent-lifecycle-events";
33
33
  import { getSubagentSessionRegistry } from "./subagent-registry";
34
34
  import { ToolInputFormatterRegistry } from "./tool-input-formatter-registry";
35
- import {
36
- canResolveAskPermissionRequest,
37
- shouldAutoApprovePermissionState,
38
- } from "./yolo-mode";
39
35
 
40
36
  export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
41
37
  const agentDir = getAgentDir();
@@ -67,10 +63,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
67
63
  configStore = new ConfigStore({
68
64
  agentDir,
69
65
  policyPaths: permissionManager,
70
- logger: {
71
- writeDebugLog: (e, d) => logger.debug(e, d),
72
- writeReviewLog: (e, d) => logger.review(e, d),
73
- },
66
+ logger,
74
67
  });
75
68
 
76
69
  const forwardingDeps: PermissionForwarderDeps = {
@@ -78,26 +71,28 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
78
71
  subagentSessionsDir: paths.subagentSessionsDir,
79
72
  registry: subagentRegistry,
80
73
  events: pi.events,
81
- logger: {
82
- writeReviewLog: (event, details) => logger.review(event, details),
83
- writeDebugLog: (event, details) => logger.debug(event, details),
84
- },
85
- writeReviewLog: (event, details) => logger.review(event, details),
74
+ logger,
86
75
  requestPermissionDecisionFromUi,
87
- shouldAutoApprove: () =>
88
- shouldAutoApprovePermissionState("ask", configStore.current()),
76
+ config: configStore,
89
77
  };
90
78
  const forwarder = new PermissionForwarder(forwardingDeps);
91
79
 
92
80
  const prompter = new PermissionPrompter({
93
81
  config: configStore,
94
- writeReviewLog: (event, details) => logger.review(event, details),
82
+ logger,
95
83
  events: pi.events,
96
84
  forwarder,
97
85
  });
98
86
 
99
87
  configStore.refresh();
100
88
 
89
+ const gateway = new PromptingGateway({
90
+ config: configStore,
91
+ subagentSessionsDir: paths.subagentSessionsDir,
92
+ registry: subagentRegistry,
93
+ prompter,
94
+ });
95
+
101
96
  const session = new PermissionSession(
102
97
  paths,
103
98
  logger,
@@ -109,39 +104,26 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
109
104
  permissionManager,
110
105
  sessionRules,
111
106
  configStore,
112
- {
113
- canRequestPermissionConfirmation: (ctx) =>
114
- canResolveAskPermissionRequest({
115
- config: configStore.current(),
116
- hasUI: ctx.hasUI,
117
- isSubagent: isSubagentExecutionContext(
118
- ctx,
119
- paths.subagentSessionsDir,
120
- subagentRegistry,
121
- ),
122
- }),
123
- promptPermission: (ctx, details) => prompter.prompt(ctx, details),
124
- },
107
+ gateway,
125
108
  );
126
109
 
127
110
  // Connect the notify sink now that session is available.
128
111
  sessionNotify = session;
129
112
 
113
+ const configPath = getGlobalConfigPath(agentDir);
130
114
  registerPermissionSystemCommand(pi, {
131
115
  config: configStore,
132
- getConfigPath: () => getGlobalConfigPath(agentDir),
133
- getComposedRules: () =>
134
- permissionManager.getComposedConfigRules(
135
- session.lastKnownActiveAgentName ?? undefined,
136
- ),
116
+ configPath,
117
+ permissionManager,
118
+ session,
137
119
  });
138
120
 
139
121
  const rpcHandles = registerPermissionRpcHandlers(pi.events, {
140
- getPermissionManager: () => permissionManager,
141
- getSessionRules: () => sessionRules.getRuleset(),
142
- getRuntimeContext: () => session.getRuntimeContext(),
122
+ permissionManager,
123
+ sessionRules,
124
+ session,
143
125
  requestPermissionDecisionFromUi,
144
- writeReviewLog: (event, details) => logger.review(event, details),
126
+ logger,
145
127
  });
146
128
 
147
129
  const permissionsService = new LocalPermissionsService(
@@ -177,7 +159,7 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
177
159
  const lifecycle = new SessionLifecycleHandler(session, serviceLifecycle);
178
160
  const agentPrep = new AgentPrepHandler(session, toolRegistry);
179
161
  const reporter = new GateDecisionReporter(session.logger, pi.events);
180
- const gateRunner = new GateRunner(session, session, session, reporter);
162
+ const gateRunner = new GateRunner(session, session, gateway, reporter);
181
163
  const toolCallGatePipeline = new ToolCallGatePipeline(
182
164
  session,
183
165
  formatterRegistry,
@@ -28,19 +28,20 @@ import {
28
28
  } from "./permission-events";
29
29
  import type { PermissionManager } from "./permission-manager";
30
30
  import { buildRpcUiPrompt } from "./permission-ui-prompt";
31
- import type { Rule } from "./rule";
31
+ import type { ReviewLogger } from "./session-logger";
32
+ import type { SessionRules } from "./session-rules";
32
33
 
33
34
  /** Dependencies injected into the RPC handler registry. */
34
35
  export interface PermissionRpcDeps {
35
- /** Returns the current PermissionManager (refreshed on session start). */
36
- getPermissionManager(): Pick<PermissionManager, "checkPermission">;
37
- /** Returns the current session rules (highest-priority approvals). */
38
- getSessionRules(): Rule[];
36
+ /** The shared PermissionManager instance. */
37
+ permissionManager: Pick<PermissionManager, "checkPermission">;
38
+ /** The shared SessionRules instance. */
39
+ sessionRules: Pick<SessionRules, "getRuleset">;
39
40
  /**
40
- * Returns the current ExtensionContext, or null if no session is active.
41
+ * Narrow session view: provides runtime context.
41
42
  * Used by the prompt handler to check hasUI and access the UI dialog.
42
43
  */
43
- getRuntimeContext(): ExtensionContext | null;
44
+ session: { getRuntimeContext(): ExtensionContext | null };
44
45
  /** Show the interactive permission dialog in the parent session UI. */
45
46
  requestPermissionDecisionFromUi(
46
47
  ui: ExtensionContext["ui"],
@@ -48,8 +49,8 @@ export interface PermissionRpcDeps {
48
49
  message: string,
49
50
  options?: RequestPermissionOptions,
50
51
  ): Promise<PermissionPromptDecision>;
51
- /** Write structured entries to the permission review log. */
52
- writeReviewLog(event: string, details: Record<string, unknown>): void;
52
+ /** Write review-log entries for prompted decisions. */
53
+ logger: ReviewLogger;
53
54
  }
54
55
 
55
56
  /** Unsubscribe handles returned from registerPermissionRpcHandlers. */
@@ -107,10 +108,13 @@ function handleCheckRpc(
107
108
  }
108
109
 
109
110
  const input = buildInputForSurface(surface, value);
110
- const sessionRules = deps.getSessionRules();
111
- const result = deps
112
- .getPermissionManager()
113
- .checkPermission(surface, input, agentName ?? undefined, sessionRules);
111
+ const sessionRules = deps.sessionRules.getRuleset();
112
+ const result = deps.permissionManager.checkPermission(
113
+ surface,
114
+ input,
115
+ agentName ?? undefined,
116
+ sessionRules,
117
+ );
114
118
 
115
119
  const data: PermissionsCheckReplyData = {
116
120
  result: result.state,
@@ -141,7 +145,7 @@ async function handlePromptRpc(
141
145
 
142
146
  const replyChannel = `${PERMISSIONS_RPC_PROMPT_CHANNEL}:reply:${requestId}`;
143
147
 
144
- const ctx = deps.getRuntimeContext();
148
+ const ctx = deps.session.getRuntimeContext();
145
149
  if (!ctx?.hasUI) {
146
150
  events.emit(replyChannel, errorReply("no_ui"));
147
151
  return;
@@ -169,7 +173,7 @@ async function handlePromptRpc(
169
173
  sessionLabel ? { sessionLabel } : undefined,
170
174
  );
171
175
 
172
- deps.writeReviewLog("permission_request.rpc_prompt", {
176
+ deps.logger.review("permission_request.rpc_prompt", {
173
177
  requestId,
174
178
  surface: surface ?? null,
175
179
  value: value ?? null,
@@ -7,6 +7,7 @@ import {
7
7
  type PermissionEventBus,
8
8
  } from "./permission-events";
9
9
  import { buildDirectUiPrompt } from "./permission-ui-prompt";
10
+ import type { ReviewLogger } from "./session-logger";
10
11
  import { shouldAutoApprovePermissionState } from "./yolo-mode";
11
12
 
12
13
  export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
@@ -40,14 +41,14 @@ export interface PermissionPrompterApi {
40
41
  * Dependencies required by PermissionPrompter.
41
42
  *
42
43
  * Keeps the prompter's external surface narrow: callers provide config
43
- * access, review-log writing, the UI-prompt event bus, and the forwarder
44
+ * access, a review logger, the UI-prompt event bus, and the forwarder
44
45
  * that owns the UI/subagent-forwarding branching logic.
45
46
  */
46
47
  export interface PermissionPrompterDeps {
47
48
  /** Read current config for yolo-mode check (called at prompt time). */
48
49
  config: ConfigReader;
49
50
  /** Write structured entries to the permission review log. */
50
- writeReviewLog(event: string, details: Record<string, unknown>): void;
51
+ logger: ReviewLogger;
51
52
  /** Event bus used for UI prompt broadcasts. */
52
53
  events: PermissionEventBus;
53
54
  /** Resolves the permission decision: direct UI dialog or forwarded to parent. */
@@ -122,7 +123,7 @@ export class PermissionPrompter implements PermissionPrompterApi {
122
123
  denialReason?: string;
123
124
  },
124
125
  ): void {
125
- this.deps.writeReviewLog(event, {
126
+ this.deps.logger.review(event, {
126
127
  requestId: details.requestId,
127
128
  source: details.source,
128
129
  agentName: details.agentName,