@gotgenes/pi-permission-system 10.4.0 → 10.5.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -1
  3. package/src/handlers/before-agent-start.ts +11 -6
  4. package/src/handlers/gates/bash-command.ts +2 -2
  5. package/src/handlers/gates/bash-external-directory.ts +2 -2
  6. package/src/handlers/gates/bash-path.ts +2 -2
  7. package/src/handlers/gates/path.ts +2 -2
  8. package/src/handlers/gates/runner.ts +2 -2
  9. package/src/handlers/gates/tool-call-gate-pipeline.ts +10 -9
  10. package/src/handlers/lifecycle.ts +7 -4
  11. package/src/handlers/permission-gate-handler.ts +3 -3
  12. package/src/index.ts +13 -4
  13. package/src/permission-resolver.ts +66 -2
  14. package/src/permission-session.ts +8 -72
  15. package/src/session-rules.ts +3 -2
  16. package/src/skill-prompt-sanitizer.ts +1 -1
  17. package/test/handlers/before-agent-start.test.ts +56 -86
  18. package/test/handlers/external-directory-session-dedup.test.ts +80 -160
  19. package/test/handlers/gates/bash-external-directory.test.ts +2 -2
  20. package/test/handlers/gates/bash-path.test.ts +2 -2
  21. package/test/handlers/gates/tool-call-gate-pipeline.test.ts +30 -21
  22. package/test/handlers/input.test.ts +5 -4
  23. package/test/handlers/lifecycle.test.ts +79 -85
  24. package/test/handlers/tool-call.test.ts +3 -2
  25. package/test/helpers/gate-fixtures.ts +5 -9
  26. package/test/helpers/handler-fixtures.ts +100 -107
  27. package/test/helpers/session-fixtures.ts +192 -0
  28. package/test/permission-resolver.test.ts +196 -0
  29. package/test/permission-session.test.ts +14 -266
  30. package/test/session-rules.test.ts +13 -5
  31. package/src/agent-prep-session.ts +0 -28
  32. package/src/gate-handler-session.ts +0 -13
  33. package/src/session-lifecycle-session.ts +0 -24
package/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ 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.5.1](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.5.0...pi-permission-system-v10.5.1) (2026-06-07)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * correct SkillPermissionChecker comment after resolver rewire ([#341](https://github.com/gotgenes/pi-packages/issues/341)) ([1528382](https://github.com/gotgenes/pi-packages/commit/15283820a920fead92b348410828332b69f0a0d9))
14
+
15
+ ## [10.5.0](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v10.4.0...pi-permission-system-v10.5.0) (2026-06-07)
16
+
17
+
18
+ ### Features
19
+
20
+ * add PermissionResolver class and route gate runner through it ([#340](https://github.com/gotgenes/pi-packages/issues/340)) ([4133601](https://github.com/gotgenes/pi-packages/commit/41336018d495f85b30b7b77fadb5912870f0dedd))
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * suppress fallow unused-class-member for pre-Step-8 resolver methods ([#340](https://github.com/gotgenes/pi-packages/issues/340)) ([fd65626](https://github.com/gotgenes/pi-packages/commit/fd65626ae867457edeb829ea28d0ab94fe51dea6))
26
+
8
27
  ## [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
28
 
10
29
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "10.4.0",
3
+ "version": "10.5.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -2,11 +2,12 @@ import type {
2
2
  BeforeAgentStartEventResult,
3
3
  ExtensionContext,
4
4
  } from "@earendil-works/pi-coding-agent";
5
- import type { AgentPrepSession } from "#src/agent-prep-session";
6
5
  import {
7
6
  createActiveToolsCacheKey,
8
7
  createBeforeAgentStartPromptStateKey,
9
8
  } from "#src/before-agent-start-cache";
9
+ import type { PermissionResolver } from "#src/permission-resolver";
10
+ import type { PermissionSession } from "#src/permission-session";
10
11
  import { resolveSkillPromptEntries } from "#src/skill-prompt-sanitizer";
11
12
  import { sanitizeAvailableToolsSection } from "#src/system-prompt-sanitizer";
12
13
  import { getToolNameFromValue, type ToolRegistry } from "#src/tool-registry";
@@ -35,12 +36,14 @@ export function shouldExposeTool(
35
36
  * Handles the `before_agent_start` event: tool filtering + prompt sanitization.
36
37
  *
37
38
  * Constructor deps:
38
- * - `session` — encapsulates all mutable session state
39
+ * - `session` — encapsulates all mutable session state and lifecycle operations
40
+ * - `resolver` — owns permission-query surface: `getToolPermission`, `getPolicyCacheStamp`, skill check
39
41
  * - `toolRegistry` — Pi tool API subset (getAll + setActive)
40
42
  */
41
43
  export class AgentPrepHandler {
42
44
  constructor(
43
- private readonly session: AgentPrepSession,
45
+ private readonly session: PermissionSession,
46
+ private readonly resolver: PermissionResolver,
44
47
  private readonly toolRegistry: ToolRegistry,
45
48
  ) {}
46
49
 
@@ -63,7 +66,7 @@ export class AgentPrepHandler {
63
66
  }
64
67
  if (
65
68
  shouldExposeTool(toolName, agentName, (t, a) =>
66
- this.session.getToolPermission(t, a),
69
+ this.resolver.getToolPermission(t, a),
67
70
  )
68
71
  ) {
69
72
  allowedTools.push(toolName);
@@ -79,7 +82,9 @@ export class AgentPrepHandler {
79
82
  const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
80
83
  agentName,
81
84
  cwd: ctx.cwd,
82
- permissionStamp: this.session.getPolicyCacheStamp(agentName ?? undefined),
85
+ permissionStamp: this.resolver.getPolicyCacheStamp(
86
+ agentName ?? undefined,
87
+ ),
83
88
  systemPrompt: event.systemPrompt,
84
89
  allowedToolNames: allowedTools,
85
90
  });
@@ -96,7 +101,7 @@ export class AgentPrepHandler {
96
101
  );
97
102
  const skillPromptResult = resolveSkillPromptEntries(
98
103
  toolPromptResult.prompt,
99
- this.session,
104
+ this.resolver,
100
105
  agentName,
101
106
  ctx.cwd,
102
107
  );
@@ -1,6 +1,6 @@
1
1
  import type { BashCommand } from "#src/handlers/gates/bash-program";
2
2
  import { pickMostRestrictive } from "#src/handlers/gates/candidate-check";
3
- import type { PermissionResolver } from "#src/permission-resolver";
3
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
4
4
  import type { PermissionCheckResult } from "#src/types";
5
5
 
6
6
  /**
@@ -30,7 +30,7 @@ export function resolveBashCommandCheck(
30
30
  command: string,
31
31
  commands: BashCommand[],
32
32
  agentName: string | undefined,
33
- resolver: PermissionResolver,
33
+ resolver: ScopedPermissionResolver,
34
34
  ): PermissionCheckResult {
35
35
  const results = commands.map((cmd) => {
36
36
  const result = resolver.resolve("bash", { command: cmd.text }, agentName);
@@ -1,5 +1,5 @@
1
1
  import { getNonEmptyString, toRecord } from "#src/common";
2
- import type { PermissionResolver } from "#src/permission-resolver";
2
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
3
3
  import { SessionApproval } from "#src/session-approval";
4
4
  import { deriveApprovalPattern } from "#src/session-rules";
5
5
  import type { PermissionCheckResult } from "#src/types";
@@ -21,7 +21,7 @@ import type { ToolCallContext } from "./types";
21
21
  export function describeBashExternalDirectoryGate(
22
22
  tcc: ToolCallContext,
23
23
  bashProgram: BashProgram | null,
24
- resolver: PermissionResolver,
24
+ resolver: ScopedPermissionResolver,
25
25
  ): GateResult {
26
26
  if (tcc.toolName !== "bash" || !tcc.cwd) return null;
27
27
 
@@ -1,5 +1,5 @@
1
1
  import { getNonEmptyString, toRecord } from "#src/common";
2
- import type { PermissionResolver } from "#src/permission-resolver";
2
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
3
3
  import { SessionApproval } from "#src/session-approval";
4
4
  import { deriveApprovalPattern } from "#src/session-rules";
5
5
  import type { PermissionCheckResult } from "#src/types";
@@ -25,7 +25,7 @@ import type { ToolCallContext } from "./types";
25
25
  export function describeBashPathGate(
26
26
  tcc: ToolCallContext,
27
27
  bashProgram: BashProgram | null,
28
- resolver: PermissionResolver,
28
+ resolver: ScopedPermissionResolver,
29
29
  ): GateResult {
30
30
  if (tcc.toolName !== "bash") return null;
31
31
 
@@ -1,5 +1,5 @@
1
1
  import { getPathBearingToolPath } from "#src/path-utils";
2
- import type { PermissionResolver } from "#src/permission-resolver";
2
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
3
3
  import { SessionApproval } from "#src/session-approval";
4
4
  import { deriveApprovalPattern } from "#src/session-rules";
5
5
  import type { GateDescriptor, GateResult } from "./descriptor";
@@ -15,7 +15,7 @@ import type { ToolCallContext } from "./types";
15
15
  */
16
16
  export function describePathGate(
17
17
  tcc: ToolCallContext,
18
- resolver: PermissionResolver,
18
+ resolver: ScopedPermissionResolver,
19
19
  ): GateResult {
20
20
  const filePath = getPathBearingToolPath(tcc.toolName, tcc.input);
21
21
  if (!filePath) return null;
@@ -7,7 +7,7 @@ import {
7
7
  import type { GatePrompter } from "#src/gate-prompter";
8
8
  import type { PermissionPromptDecision } from "#src/permission-dialog";
9
9
  import { applyPermissionGate } from "#src/permission-gate";
10
- import type { PermissionResolver } from "#src/permission-resolver";
10
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
11
11
  import type { SessionApprovalRecorder } from "#src/session-approval-recorder";
12
12
  import type { PermissionCheckResult } from "#src/types";
13
13
  import type { GateDescriptor, GateResult } from "./descriptor";
@@ -28,7 +28,7 @@ import type { GateOutcome } from "./types";
28
28
  */
29
29
  export class GateRunner {
30
30
  constructor(
31
- private readonly resolver: PermissionResolver,
31
+ private readonly resolver: ScopedPermissionResolver,
32
32
  private readonly recorder: SessionApprovalRecorder,
33
33
  private readonly prompter: GatePrompter,
34
34
  private readonly reporter: DecisionReporter,
@@ -1,5 +1,5 @@
1
1
  import { getNonEmptyString, toRecord } from "#src/common";
2
- import type { PermissionResolver } from "#src/permission-resolver";
2
+ import type { ScopedPermissionResolver } from "#src/permission-resolver";
3
3
  import type { SkillPromptEntry } from "#src/skill-prompt-sanitizer";
4
4
  import type { ToolInputFormatterLookup } from "#src/tool-input-formatter-registry";
5
5
  import {
@@ -21,14 +21,14 @@ import type { GateOutcome, ToolCallContext } from "./types";
21
21
  /**
22
22
  * Narrow interface the pipeline needs from its session-side dependency.
23
23
  *
24
- * Extends `PermissionResolver` (the `resolve` method gate factories use)
25
- * with the three query methods needed to assemble gate inputs.
24
+ * The three query methods needed to assemble gate inputs.
25
+ * The resolver is injected separately as a constructor parameter.
26
26
  *
27
27
  * `PermissionSession` satisfies this structurally at the construction call
28
28
  * site; no `implements` clause is needed and would create a layer-inversion
29
29
  * import from the domain module into the handler layer.
30
30
  */
31
- export interface ToolCallGateInputs extends PermissionResolver {
31
+ export interface ToolCallGateInputs {
32
32
  /** Active skill prompt entries for the skill-read gate. */
33
33
  getActiveSkillEntries(): SkillPromptEntry[];
34
34
  /** Combined infrastructure read directories (static + config-derived). */
@@ -50,6 +50,7 @@ export interface ToolCallGateInputs extends PermissionResolver {
50
50
  */
51
51
  export class ToolCallGatePipeline {
52
52
  constructor(
53
+ private readonly resolver: ScopedPermissionResolver,
53
54
  private readonly inputs: ToolCallGateInputs,
54
55
  private readonly customFormatters?: ToolInputFormatterLookup,
55
56
  ) {}
@@ -76,10 +77,10 @@ export class ToolCallGatePipeline {
76
77
  const gateProducers: Array<() => GateResult | Promise<GateResult>> = [
77
78
  () =>
78
79
  describeSkillReadGate(tcc, () => this.inputs.getActiveSkillEntries()),
79
- () => describePathGate(tcc, this.inputs),
80
+ () => describePathGate(tcc, this.resolver),
80
81
  () => describeExternalDirectoryGate(tcc, infraDirs),
81
- () => describeBashExternalDirectoryGate(tcc, bashProgram, this.inputs),
82
- () => describeBashPathGate(tcc, bashProgram, this.inputs),
82
+ () => describeBashExternalDirectoryGate(tcc, bashProgram, this.resolver),
83
+ () => describeBashPathGate(tcc, bashProgram, this.resolver),
83
84
  () => {
84
85
  // Bash commands may chain several sub-commands (`a && b`, `a | b`, …);
85
86
  // evaluate each unit from the shared parse on the bash surface and
@@ -91,9 +92,9 @@ export class ToolCallGatePipeline {
91
92
  command ?? "",
92
93
  bashProgram.commands(),
93
94
  tcc.agentName ?? undefined,
94
- this.inputs,
95
+ this.resolver,
95
96
  )
96
- : this.inputs.resolve(
97
+ : this.resolver.resolve(
97
98
  tcc.toolName,
98
99
  tcc.input,
99
100
  tcc.agentName ?? undefined,
@@ -1,7 +1,8 @@
1
1
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
 
3
+ import type { PermissionResolver } from "#src/permission-resolver";
4
+ import type { PermissionSession } from "#src/permission-session";
3
5
  import type { ServiceLifecycle } from "#src/service-lifecycle";
4
- import type { SessionLifecycleSession } from "#src/session-lifecycle-session";
5
6
  import { PERMISSION_SYSTEM_STATUS_KEY } from "#src/status";
6
7
 
7
8
  /** Minimal subset of SessionStartEvent used by this handler. */
@@ -18,14 +19,16 @@ interface ResourcesDiscoverPayload {
18
19
  * Handles session lifecycle events: start, reload, and shutdown.
19
20
  *
20
21
  * Constructor deps:
21
- * - `session` — encapsulates all mutable session state
22
+ * - `session` — encapsulates all mutable session state and lifecycle operations
23
+ * - `resolver` — owns permission-query surface: `getConfigIssues`
22
24
  * - `serviceLifecycle` — owns the process-global service publication;
23
25
  * `activate` publishes (skipped for registered subagent children) and emits
24
26
  * the ready event; `teardown` unsubscribes all session listeners and unpublishes
25
27
  */
26
28
  export class SessionLifecycleHandler {
27
29
  constructor(
28
- private readonly session: SessionLifecycleSession,
30
+ private readonly session: PermissionSession,
31
+ private readonly resolver: PermissionResolver,
29
32
  private readonly serviceLifecycle: ServiceLifecycle,
30
33
  ) {}
31
34
 
@@ -38,7 +41,7 @@ export class SessionLifecycleHandler {
38
41
  this.session.logResolvedConfigPaths();
39
42
 
40
43
  const agentName = this.session.resolveAgentName(ctx);
41
- const policyIssues = this.session.getConfigIssues(agentName ?? undefined);
44
+ const policyIssues = this.resolver.getConfigIssues(agentName ?? undefined);
42
45
  for (const issue of policyIssues) {
43
46
  this.session.logger.warn(issue);
44
47
  }
@@ -4,11 +4,11 @@ import type {
4
4
  } from "@earendil-works/pi-coding-agent";
5
5
 
6
6
  import { toRecord } from "#src/common";
7
- import type { GateHandlerSession } from "#src/gate-handler-session";
8
7
  import {
9
8
  formatMissingToolNameReason,
10
9
  formatUnknownToolReason,
11
10
  } from "#src/permission-prompts";
11
+ import type { PermissionSession } from "#src/permission-session";
12
12
  import {
13
13
  checkRequestedToolRegistration,
14
14
  getToolNameFromValue,
@@ -31,7 +31,7 @@ interface InputPayload {
31
31
  * Handles permission gate events: tool_call and input.
32
32
  *
33
33
  * Constructor deps:
34
- * - `session` — narrow two-method context role: bind per-event context, resolve agent name
34
+ * - `session` — state/lifecycle owner: bind per-event context, resolve agent name
35
35
  * - `toolRegistry` — Pi tool API subset (getAll + setActive)
36
36
  * - `pipeline` — owns tool-call gate-producer assembly and the run loop
37
37
  * - `skillInputPipeline` — owns skill-input gate assembly (pre-check, notify, run)
@@ -39,7 +39,7 @@ interface InputPayload {
39
39
  */
40
40
  export class PermissionGateHandler {
41
41
  constructor(
42
- private readonly session: GateHandlerSession,
42
+ private readonly session: PermissionSession,
43
43
  private readonly toolRegistry: ToolRegistry,
44
44
  private readonly pipeline: ToolCallGatePipeline,
45
45
  private readonly skillInputPipeline: SkillInputGatePipeline,
package/src/index.ts CHANGED
@@ -23,6 +23,7 @@ import { requestPermissionDecisionFromUi } from "./permission-dialog";
23
23
  import { registerPermissionRpcHandlers } from "./permission-event-rpc";
24
24
  import { PermissionManager } from "./permission-manager";
25
25
  import { PermissionPrompter } from "./permission-prompter";
26
+ import { PermissionResolver } from "./permission-resolver";
26
27
  import { PermissionSession } from "./permission-session";
27
28
  import { LocalPermissionsService } from "./permissions-service";
28
29
  import { PromptingGateway } from "./prompting-gateway";
@@ -156,15 +157,23 @@ export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
156
157
  setActive: (names: string[]) => pi.setActiveTools(names),
157
158
  };
158
159
 
159
- const lifecycle = new SessionLifecycleHandler(session, serviceLifecycle);
160
- const agentPrep = new AgentPrepHandler(session, toolRegistry);
160
+ const resolver = new PermissionResolver(permissionManager, sessionRules);
161
+
162
+ const lifecycle = new SessionLifecycleHandler(
163
+ session,
164
+ resolver,
165
+ serviceLifecycle,
166
+ );
167
+ const agentPrep = new AgentPrepHandler(session, resolver, toolRegistry);
168
+
161
169
  const reporter = new GateDecisionReporter(session.logger, pi.events);
162
- const gateRunner = new GateRunner(session, session, gateway, reporter);
170
+ const gateRunner = new GateRunner(resolver, sessionRules, gateway, reporter);
163
171
  const toolCallGatePipeline = new ToolCallGatePipeline(
172
+ resolver,
164
173
  session,
165
174
  formatterRegistry,
166
175
  );
167
- const skillInputGatePipeline = new SkillInputGatePipeline(session);
176
+ const skillInputGatePipeline = new SkillInputGatePipeline(resolver);
168
177
  const gates = new PermissionGateHandler(
169
178
  session,
170
179
  toolRegistry,
@@ -1,4 +1,7 @@
1
- import type { PermissionCheckResult } from "./types";
1
+ import type { ScopedPermissionManager } from "./permission-manager";
2
+ import type { Rule } from "./rule";
3
+ import type { SessionRules } from "./session-rules";
4
+ import type { PermissionCheckResult, PermissionState } from "./types";
2
5
 
3
6
  /**
4
7
  * Resolves the effective permission for a surface/input, applying the current
@@ -8,10 +11,71 @@ import type { PermissionCheckResult } from "./types";
8
11
  * previously threaded by hand: the ruleset was only ever fetched to be passed
9
12
  * straight back into `checkPermission`, so the two are one operation.
10
13
  */
11
- export interface PermissionResolver {
14
+ export interface ScopedPermissionResolver {
12
15
  resolve(
13
16
  surface: string,
14
17
  input: unknown,
15
18
  agentName?: string,
16
19
  ): PermissionCheckResult;
17
20
  }
21
+
22
+ /**
23
+ * Concrete collaborator that owns the resolution surface.
24
+ *
25
+ * Holds a `ScopedPermissionManager` and a `SessionRules` store, composing
26
+ * them so callers never thread the session ruleset by hand.
27
+ *
28
+ * Constructor deps:
29
+ * - `permissionManager` — the narrow session-scoped permission-checking interface
30
+ * - `sessionRules` — narrowed to `getRuleset` (ISP: the resolver only reads, never records)
31
+ */
32
+ export class PermissionResolver implements ScopedPermissionResolver {
33
+ constructor(
34
+ private readonly permissionManager: ScopedPermissionManager,
35
+ private readonly sessionRules: Pick<SessionRules, "getRuleset">,
36
+ ) {}
37
+
38
+ /**
39
+ * Resolve the effective permission for a surface/input, applying the current
40
+ * session rules. Composes `checkPermission` with `getRuleset()` so callers
41
+ * never thread the ruleset by hand.
42
+ */
43
+ resolve(
44
+ surface: string,
45
+ input: unknown,
46
+ agentName?: string,
47
+ ): PermissionCheckResult {
48
+ return this.checkPermission(
49
+ surface,
50
+ input,
51
+ agentName,
52
+ this.sessionRules.getRuleset(),
53
+ );
54
+ }
55
+
56
+ checkPermission(
57
+ surface: string,
58
+ input: unknown,
59
+ agentName?: string,
60
+ sessionRules?: Rule[],
61
+ ): PermissionCheckResult {
62
+ return this.permissionManager.checkPermission(
63
+ surface,
64
+ input,
65
+ agentName,
66
+ sessionRules,
67
+ );
68
+ }
69
+
70
+ getToolPermission(toolName: string, agentName?: string): PermissionState {
71
+ return this.permissionManager.getToolPermission(toolName, agentName);
72
+ }
73
+
74
+ getConfigIssues(agentName?: string): string[] {
75
+ return this.permissionManager.getConfigIssues(agentName);
76
+ }
77
+
78
+ getPolicyCacheStamp(agentName?: string): string {
79
+ return this.permissionManager.getPolicyCacheStamp(agentName);
80
+ }
81
+ }
@@ -4,19 +4,15 @@ import {
4
4
  getActiveAgentName,
5
5
  getActiveAgentNameFromSystemPrompt,
6
6
  } from "./active-agent";
7
- import type { AgentPrepSession } from "./agent-prep-session";
7
+
8
8
  import type { SessionConfigStore } from "./config-store";
9
9
  import type { PermissionSystemExtensionConfig } from "./extension-config";
10
10
  import type { ExtensionPaths } from "./extension-paths";
11
11
  import type { ForwardingController } from "./forwarding-manager";
12
- import type { GateHandlerSession } from "./gate-handler-session";
12
+ import type { ToolCallGateInputs } from "./handlers/gates/tool-call-gate-pipeline";
13
13
  import type { ScopedPermissionManager } from "./permission-manager";
14
- import type { PermissionResolver } from "./permission-resolver";
15
14
  import type { PromptingGatewayLifecycle } from "./prompting-gateway";
16
- import type { Rule } from "./rule";
17
- import type { SessionApproval } from "./session-approval";
18
- import type { SessionApprovalRecorder } from "./session-approval-recorder";
19
- import type { SessionLifecycleSession } from "./session-lifecycle-session";
15
+
20
16
  import type { SessionLogger } from "./session-logger";
21
17
  import type { SessionRules } from "./session-rules";
22
18
  import type { SkillPromptEntry } from "./skill-prompt-sanitizer";
@@ -24,7 +20,6 @@ import {
24
20
  resolveToolPreviewLimits,
25
21
  type ToolPreviewFormatterOptions,
26
22
  } from "./tool-preview-formatter";
27
- import type { PermissionCheckResult, PermissionState } from "./types";
28
23
 
29
24
  /**
30
25
  * Encapsulates all mutable session state and exposes operations instead of
@@ -41,14 +36,7 @@ import type { PermissionCheckResult, PermissionState } from "./types";
41
36
  * - `SessionConfigStore` — owns extension config; provides refresh, log, read
42
37
  * - `PromptingGatewayLifecycle` — prompting lifecycle forwarded via activate/deactivate
43
38
  */
44
- export class PermissionSession
45
- implements
46
- PermissionResolver,
47
- SessionApprovalRecorder,
48
- GateHandlerSession,
49
- AgentPrepSession,
50
- SessionLifecycleSession
51
- {
39
+ export class PermissionSession implements ToolCallGateInputs {
52
40
  private context: ExtensionContext | null = null;
53
41
  private skillEntries: SkillPromptEntry[] = [];
54
42
  private knownAgentName: string | null = null;
@@ -86,62 +74,6 @@ export class PermissionSession
86
74
  return this.context;
87
75
  }
88
76
 
89
- // ── Permission checking (delegates to PermissionManager) ───────────────
90
-
91
- checkPermission(
92
- surface: string,
93
- input: unknown,
94
- agentName?: string,
95
- sessionRules?: Rule[],
96
- ): PermissionCheckResult {
97
- return this.permissionManager.checkPermission(
98
- surface,
99
- input,
100
- agentName,
101
- sessionRules,
102
- );
103
- }
104
-
105
- /**
106
- * Resolve the effective permission for a surface/input, applying the current
107
- * session rules. Composes `checkPermission` with `getSessionRuleset` so
108
- * callers never thread the ruleset by hand.
109
- */
110
- resolve(
111
- surface: string,
112
- input: unknown,
113
- agentName?: string,
114
- ): PermissionCheckResult {
115
- return this.checkPermission(
116
- surface,
117
- input,
118
- agentName,
119
- this.getSessionRuleset(),
120
- );
121
- }
122
-
123
- getToolPermission(toolName: string, agentName?: string): PermissionState {
124
- return this.permissionManager.getToolPermission(toolName, agentName);
125
- }
126
-
127
- getConfigIssues(agentName?: string): string[] {
128
- return this.permissionManager.getConfigIssues(agentName);
129
- }
130
-
131
- getPolicyCacheStamp(agentName?: string): string {
132
- return this.permissionManager.getPolicyCacheStamp(agentName);
133
- }
134
-
135
- // ── Session rules (delegates to SessionRules) ──────────────────────────
136
-
137
- getSessionRuleset(): Rule[] {
138
- return this.sessionRules.getRuleset();
139
- }
140
-
141
- recordSessionApproval(approval: SessionApproval): void {
142
- this.sessionRules.record(approval);
143
- }
144
-
145
77
  // ── Session lifecycle ────────────────────────────────────────────────────
146
78
 
147
79
  /**
@@ -232,6 +164,10 @@ export class PermissionSession
232
164
  return this.knownAgentName;
233
165
  }
234
166
 
167
+ // Read by config-modal (`controller.session.lastKnownActiveAgentName`).
168
+ // fallow cannot trace the getter through the command's object-literal
169
+ // wiring, so it reports a false positive here.
170
+ // fallow-ignore-next-line unused-class-member
235
171
  get lastKnownActiveAgentName(): string | null {
236
172
  return this.knownAgentName;
237
173
  }
@@ -2,6 +2,7 @@ import { dirname, sep } from "node:path";
2
2
 
3
3
  import type { Ruleset } from "./rule";
4
4
  import type { SessionApproval } from "./session-approval";
5
+ import type { SessionApprovalRecorder } from "./session-approval-recorder";
5
6
 
6
7
  /**
7
8
  * Ephemeral in-memory store of session-scoped permission approvals.
@@ -11,7 +12,7 @@ import type { SessionApproval } from "./session-approval";
11
12
  *
12
13
  * Cleared on session_shutdown — never persisted to disk.
13
14
  */
14
- export class SessionRules {
15
+ export class SessionRules implements SessionApprovalRecorder {
15
16
  private rules: Ruleset = [];
16
17
 
17
18
  /** Record a wildcard pattern as approved for the given surface. */
@@ -36,7 +37,7 @@ export class SessionRules {
36
37
  * The loop lives here so callers never need to know whether an approval
37
38
  * carries one pattern or many — they just tell the store to record it.
38
39
  */
39
- record(approval: SessionApproval): void {
40
+ recordSessionApproval(approval: SessionApproval): void {
40
41
  for (const pattern of approval.patterns) {
41
42
  this.approve(approval.surface, pattern);
42
43
  }
@@ -8,7 +8,7 @@ import type { PermissionCheckResult, PermissionState } from "./types";
8
8
 
9
9
  /**
10
10
  * Narrow interface for the permission checker used by skill prompt resolution.
11
- * Both `PermissionManager` and `PermissionSession` satisfy this structurally.
11
+ * Both `PermissionManager` and `PermissionResolver` satisfy this structurally.
12
12
  */
13
13
  export interface SkillPermissionChecker {
14
14
  checkPermission(