@gotgenes/pi-permission-system 5.5.0 → 5.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ 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.5.1](https://github.com/gotgenes/pi-permission-system/compare/v5.5.0...v5.5.1) (2026-05-07)
9
+
10
+
11
+ ### Documentation
12
+
13
+ * plan narrow handler dependencies by splitting ExtensionRuntime ([#111](https://github.com/gotgenes/pi-permission-system/issues/111)) ([cb44dee](https://github.com/gotgenes/pi-permission-system/commit/cb44deea383fbcca8c8ba25bd4098f7dd8c35bfb))
14
+ * **retro:** add retro notes for issue [#108](https://github.com/gotgenes/pi-permission-system/issues/108) ([e967fd9](https://github.com/gotgenes/pi-permission-system/commit/e967fd96c6ed0a9ac0a4f0037889f1d9c88ebd97))
15
+ * update target architecture for gate interfaces and SessionState ([#111](https://github.com/gotgenes/pi-permission-system/issues/111)) ([85620c4](https://github.com/gotgenes/pi-permission-system/commit/85620c4486fdcf756c7039c15bdd8a295621ad37))
16
+
8
17
  ## [5.5.0](https://github.com/gotgenes/pi-permission-system/compare/v5.4.0...v5.5.0) (2026-05-07)
9
18
 
10
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -41,12 +41,12 @@ export async function handleBeforeAgentStart(
41
41
  event: BeforeAgentStartPayload,
42
42
  ctx: ExtensionContext,
43
43
  ): Promise<BeforeAgentStartEventResult> {
44
- deps.runtime.runtimeContext = ctx;
44
+ deps.session.runtimeContext = ctx;
45
45
  deps.refreshExtensionConfig(ctx);
46
46
  deps.startForwardedPermissionPolling(ctx);
47
47
 
48
48
  const agentName = deps.resolveAgentName(ctx, event.systemPrompt);
49
- const { permissionManager } = deps.runtime;
49
+ const { permissionManager } = deps.session;
50
50
  const allTools = deps.getAllTools();
51
51
  const allowedTools: string[] = [];
52
52
 
@@ -63,12 +63,12 @@ export async function handleBeforeAgentStart(
63
63
  const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
64
64
  if (
65
65
  shouldApplyCachedAgentStartState(
66
- deps.runtime.lastActiveToolsCacheKey,
66
+ deps.session.lastActiveToolsCacheKey,
67
67
  activeToolsCacheKey,
68
68
  )
69
69
  ) {
70
70
  deps.setActiveTools(allowedTools);
71
- deps.runtime.lastActiveToolsCacheKey = activeToolsCacheKey;
71
+ deps.session.lastActiveToolsCacheKey = activeToolsCacheKey;
72
72
  }
73
73
 
74
74
  const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
@@ -83,14 +83,14 @@ export async function handleBeforeAgentStart(
83
83
 
84
84
  if (
85
85
  !shouldApplyCachedAgentStartState(
86
- deps.runtime.lastPromptStateCacheKey,
86
+ deps.session.lastPromptStateCacheKey,
87
87
  promptStateCacheKey,
88
88
  )
89
89
  ) {
90
90
  return {};
91
91
  }
92
92
 
93
- deps.runtime.lastPromptStateCacheKey = promptStateCacheKey;
93
+ deps.session.lastPromptStateCacheKey = promptStateCacheKey;
94
94
 
95
95
  const toolPromptResult = sanitizeAvailableToolsSection(
96
96
  event.systemPrompt,
@@ -102,7 +102,7 @@ export async function handleBeforeAgentStart(
102
102
  agentName,
103
103
  ctx.cwd,
104
104
  );
105
- deps.runtime.activeSkillEntries = skillPromptResult.entries;
105
+ deps.session.activeSkillEntries = skillPromptResult.entries;
106
106
 
107
107
  if (skillPromptResult.prompt !== event.systemPrompt) {
108
108
  return { systemPrompt: skillPromptResult.prompt };
@@ -8,8 +8,11 @@ import {
8
8
  import type { PermissionPromptDecision } from "../../permission-dialog";
9
9
  import { applyPermissionGate } from "../../permission-gate";
10
10
  import { deriveApprovalPattern } from "../../session-rules";
11
- import type { HandlerDeps } from "../types";
12
- import type { GateOutcome, ToolCallContext } from "./types";
11
+ import type {
12
+ BashExternalDirectoryGateDeps,
13
+ GateOutcome,
14
+ ToolCallContext,
15
+ } from "./types";
13
16
 
14
17
  /**
15
18
  * Evaluate the bash external-directory permission gate.
@@ -20,7 +23,7 @@ import type { GateOutcome, ToolCallContext } from "./types";
20
23
  */
21
24
  export async function evaluateBashExternalDirectoryGate(
22
25
  tcc: ToolCallContext,
23
- deps: HandlerDeps,
26
+ deps: BashExternalDirectoryGateDeps,
24
27
  ): Promise<GateOutcome | null> {
25
28
  if (tcc.toolName !== "bash" || !tcc.cwd) return null;
26
29
 
@@ -33,10 +36,10 @@ export async function evaluateBashExternalDirectoryGate(
33
36
  );
34
37
  if (externalPaths.length === 0) return null;
35
38
 
36
- const bashSessionRules = deps.runtime.sessionRules.getRuleset();
39
+ const bashSessionRules = deps.getSessionRuleset();
37
40
  const uncoveredPaths = externalPaths.filter(
38
41
  (p) =>
39
- deps.runtime.permissionManager.checkPermission(
42
+ deps.checkPermission(
40
43
  "external_directory",
41
44
  { path: p },
42
45
  tcc.agentName ?? undefined,
@@ -45,7 +48,7 @@ export async function evaluateBashExternalDirectoryGate(
45
48
  );
46
49
 
47
50
  if (uncoveredPaths.length === 0) {
48
- deps.runtime.writeReviewLog("permission_request.session_approved", {
51
+ deps.writeReviewLog("permission_request.session_approved", {
49
52
  source: "tool_call",
50
53
  toolCallId: tcc.toolCallId,
51
54
  toolName: tcc.toolName,
@@ -58,7 +61,7 @@ export async function evaluateBashExternalDirectoryGate(
58
61
  }
59
62
 
60
63
  // Get the config-level policy (no path → no session check).
61
- const extCheck = deps.runtime.permissionManager.checkPermission(
64
+ const extCheck = deps.checkPermission(
62
65
  "external_directory",
63
66
  {},
64
67
  tcc.agentName ?? undefined,
@@ -73,26 +76,21 @@ export async function evaluateBashExternalDirectoryGate(
73
76
  );
74
77
  const bashExtGate = await applyPermissionGate({
75
78
  state: extCheck.state,
76
- canConfirm: deps.canRequestPermissionConfirmation(
77
- deps.runtime.runtimeContext!,
78
- ),
79
+ canConfirm: deps.canConfirm(),
79
80
  promptForApproval: async () => {
80
- const decision = await deps.promptPermission(
81
- deps.runtime.runtimeContext!,
82
- {
83
- requestId: tcc.toolCallId,
84
- source: "tool_call",
85
- agentName: tcc.agentName,
86
- message: bashExtMessage,
87
- toolCallId: tcc.toolCallId,
88
- toolName: tcc.toolName,
89
- command,
90
- },
91
- );
81
+ const decision = await deps.promptPermission({
82
+ requestId: tcc.toolCallId,
83
+ source: "tool_call",
84
+ agentName: tcc.agentName,
85
+ message: bashExtMessage,
86
+ toolCallId: tcc.toolCallId,
87
+ toolName: tcc.toolName,
88
+ command,
89
+ });
92
90
  bashExtDecision = decision;
93
91
  return decision;
94
92
  },
95
- writeLog: deps.runtime.writeReviewLog,
93
+ writeLog: deps.writeReviewLog,
96
94
  logContext: {
97
95
  source: "tool_call",
98
96
  toolCallId: tcc.toolCallId,
@@ -126,7 +124,7 @@ export async function evaluateBashExternalDirectoryGate(
126
124
  if (bashExtDecision?.state === "approved_for_session") {
127
125
  for (const extPath of uncoveredPaths) {
128
126
  const pattern = deriveApprovalPattern(extPath);
129
- deps.runtime.sessionRules.approve("external_directory", pattern);
127
+ deps.approveSessionRule("external_directory", pattern);
130
128
  }
131
129
  }
132
130
 
@@ -8,12 +8,14 @@ import {
8
8
  normalizePathForComparison,
9
9
  } from "../../external-directory";
10
10
  import type { PermissionPromptDecision } from "../../permission-dialog";
11
- import { emitDecisionEvent } from "../../permission-events";
12
11
  import { applyPermissionGate } from "../../permission-gate";
13
12
  import { deriveApprovalPattern } from "../../session-rules";
14
- import type { HandlerDeps } from "../types";
15
13
  import { deriveResolution } from "./helpers";
16
- import type { GateOutcome, ToolCallContext } from "./types";
14
+ import type {
15
+ ExternalDirectoryGateDeps,
16
+ GateOutcome,
17
+ ToolCallContext,
18
+ } from "./types";
17
19
 
18
20
  /**
19
21
  * Evaluate the external-directory permission gate for file tools.
@@ -23,7 +25,7 @@ import type { GateOutcome, ToolCallContext } from "./types";
23
25
  */
24
26
  export async function evaluateExternalDirectoryGate(
25
27
  tcc: ToolCallContext,
26
- deps: HandlerDeps,
28
+ deps: ExternalDirectoryGateDeps,
27
29
  ): Promise<GateOutcome | null> {
28
30
  if (!tcc.cwd) return null;
29
31
 
@@ -40,10 +42,7 @@ export async function evaluateExternalDirectoryGate(
40
42
  );
41
43
 
42
44
  // ── Pi infrastructure read bypass ──────────────────────────────────────
43
- const allInfraDirs = [
44
- ...deps.runtime.piInfrastructureDirs,
45
- ...(deps.runtime.config.piInfrastructureReadPaths ?? []),
46
- ];
45
+ const allInfraDirs = deps.getInfrastructureDirs();
47
46
  if (
48
47
  isPiInfrastructureRead(
49
48
  tcc.toolName,
@@ -52,17 +51,14 @@ export async function evaluateExternalDirectoryGate(
52
51
  tcc.cwd,
53
52
  )
54
53
  ) {
55
- deps.runtime.writeReviewLog(
56
- "permission_request.infrastructure_auto_allowed",
57
- {
58
- source: "tool_call",
59
- toolCallId: tcc.toolCallId,
60
- toolName: tcc.toolName,
61
- agentName: tcc.agentName,
62
- path: externalDirectoryPath,
63
- },
64
- );
65
- emitDecisionEvent(deps.events, {
54
+ deps.writeReviewLog("permission_request.infrastructure_auto_allowed", {
55
+ source: "tool_call",
56
+ toolCallId: tcc.toolCallId,
57
+ toolName: tcc.toolName,
58
+ agentName: tcc.agentName,
59
+ path: externalDirectoryPath,
60
+ });
61
+ deps.emitDecision({
66
62
  surface: tcc.toolName,
67
63
  value: externalDirectoryPath,
68
64
  result: "allow",
@@ -75,16 +71,16 @@ export async function evaluateExternalDirectoryGate(
75
71
  }
76
72
 
77
73
  // ── Policy check ───────────────────────────────────────────────────────
78
- const extCheck = deps.runtime.permissionManager.checkPermission(
74
+ const extCheck = deps.checkPermission(
79
75
  "external_directory",
80
76
  { path: normalizedExtPath },
81
77
  tcc.agentName ?? undefined,
82
- deps.runtime.sessionRules.getRuleset(),
78
+ deps.getSessionRuleset(),
83
79
  );
84
80
 
85
81
  // Session-rule hit
86
82
  if (extCheck.source === "session") {
87
- deps.runtime.writeReviewLog("permission_request.session_approved", {
83
+ deps.writeReviewLog("permission_request.session_approved", {
88
84
  source: "tool_call",
89
85
  toolCallId: tcc.toolCallId,
90
86
  toolName: tcc.toolName,
@@ -93,7 +89,7 @@ export async function evaluateExternalDirectoryGate(
93
89
  resolution: "session_approved",
94
90
  sessionApprovalPattern: extCheck.matchedPattern,
95
91
  });
96
- emitDecisionEvent(deps.events, {
92
+ deps.emitDecision({
97
93
  surface: "external_directory",
98
94
  value: externalDirectoryPath,
99
95
  result: "allow",
@@ -113,29 +109,24 @@ export async function evaluateExternalDirectoryGate(
113
109
  tcc.cwd,
114
110
  tcc.agentName ?? undefined,
115
111
  );
116
- const extDirCanConfirm = deps.canRequestPermissionConfirmation(
117
- deps.runtime.runtimeContext!,
118
- );
112
+ const extDirCanConfirm = deps.canConfirm();
119
113
  const extDirGateResult = await applyPermissionGate({
120
114
  state: extCheck.state,
121
115
  canConfirm: extDirCanConfirm,
122
116
  promptForApproval: async () => {
123
- const decision = await deps.promptPermission(
124
- deps.runtime.runtimeContext!,
125
- {
126
- requestId: tcc.toolCallId,
127
- source: "tool_call",
128
- agentName: tcc.agentName,
129
- message: extDirMessage,
130
- toolCallId: tcc.toolCallId,
131
- toolName: tcc.toolName,
132
- path: externalDirectoryPath,
133
- },
134
- );
117
+ const decision = await deps.promptPermission({
118
+ requestId: tcc.toolCallId,
119
+ source: "tool_call",
120
+ agentName: tcc.agentName,
121
+ message: extDirMessage,
122
+ toolCallId: tcc.toolCallId,
123
+ toolName: tcc.toolName,
124
+ path: externalDirectoryPath,
125
+ });
135
126
  extDirDecision = decision;
136
127
  return decision;
137
128
  },
138
- writeLog: deps.runtime.writeReviewLog,
129
+ writeLog: deps.writeReviewLog,
139
130
  logContext: {
140
131
  source: "tool_call",
141
132
  toolCallId: tcc.toolCallId,
@@ -161,7 +152,7 @@ export async function evaluateExternalDirectoryGate(
161
152
  },
162
153
  });
163
154
 
164
- emitDecisionEvent(deps.events, {
155
+ deps.emitDecision({
165
156
  surface: "external_directory",
166
157
  value: externalDirectoryPath,
167
158
  result: extDirGateResult.action === "allow" ? "allow" : "deny",
@@ -182,7 +173,7 @@ export async function evaluateExternalDirectoryGate(
182
173
 
183
174
  if (extDirDecision?.state === "approved_for_session") {
184
175
  const pattern = deriveApprovalPattern(normalizedExtPath);
185
- deps.runtime.sessionRules.approve("external_directory", pattern);
176
+ deps.approveSessionRule("external_directory", pattern);
186
177
  }
187
178
 
188
179
  return { action: "allow" };
@@ -1,15 +1,13 @@
1
1
  import { toRecord } from "../../common";
2
2
  import { normalizePathForComparison } from "../../external-directory";
3
- import { emitDecisionEvent } from "../../permission-events";
4
3
  import { applyPermissionGate } from "../../permission-gate";
5
4
  import {
6
5
  formatSkillPathAskPrompt,
7
6
  formatSkillPathDenyReason,
8
7
  } from "../../permission-prompts";
9
8
  import { findSkillPathMatch } from "../../skill-prompt-sanitizer";
10
- import type { HandlerDeps } from "../types";
11
9
  import { deriveResolution } from "./helpers";
12
- import type { GateOutcome, ToolCallContext } from "./types";
10
+ import type { GateOutcome, SkillReadGateDeps, ToolCallContext } from "./types";
13
11
 
14
12
  /**
15
13
  * Evaluate the skill-read permission gate.
@@ -19,10 +17,12 @@ import type { GateOutcome, ToolCallContext } from "./types";
19
17
  */
20
18
  export async function evaluateSkillReadGate(
21
19
  tcc: ToolCallContext,
22
- deps: HandlerDeps,
20
+ deps: SkillReadGateDeps,
23
21
  ): Promise<GateOutcome | null> {
22
+ const activeSkillEntries = deps.getActiveSkillEntries();
23
+
24
24
  // Only applies to read tool calls with active skill entries
25
- if (tcc.toolName !== "read" || deps.runtime.activeSkillEntries.length === 0) {
25
+ if (tcc.toolName !== "read" || activeSkillEntries.length === 0) {
26
26
  return null;
27
27
  }
28
28
 
@@ -35,7 +35,7 @@ export async function evaluateSkillReadGate(
35
35
  const normalizedReadPath = normalizePathForComparison(path, tcc.cwd);
36
36
  const matchedSkill = findSkillPathMatch(
37
37
  normalizedReadPath,
38
- deps.runtime.activeSkillEntries,
38
+ activeSkillEntries,
39
39
  );
40
40
 
41
41
  if (!matchedSkill) {
@@ -47,14 +47,12 @@ export async function evaluateSkillReadGate(
47
47
  path,
48
48
  tcc.agentName ?? undefined,
49
49
  );
50
- const skillReadCanConfirm = deps.canRequestPermissionConfirmation(
51
- deps.runtime.runtimeContext!,
52
- );
50
+ const skillReadCanConfirm = deps.canConfirm();
53
51
  const skillReadGate = await applyPermissionGate({
54
52
  state: matchedSkill.state,
55
53
  canConfirm: skillReadCanConfirm,
56
54
  promptForApproval: () =>
57
- deps.promptPermission(deps.runtime.runtimeContext!, {
55
+ deps.promptPermission({
58
56
  requestId: tcc.toolCallId,
59
57
  source: "skill_read",
60
58
  agentName: tcc.agentName,
@@ -64,7 +62,7 @@ export async function evaluateSkillReadGate(
64
62
  skillName: matchedSkill.name,
65
63
  path,
66
64
  }),
67
- writeLog: deps.runtime.writeReviewLog,
65
+ writeLog: deps.writeReviewLog,
68
66
  logContext: {
69
67
  source: "skill_read",
70
68
  skillName: matchedSkill.name,
@@ -88,7 +86,7 @@ export async function evaluateSkillReadGate(
88
86
  },
89
87
  });
90
88
 
91
- emitDecisionEvent(deps.events, {
89
+ deps.emitDecision({
92
90
  surface: "skill",
93
91
  value: matchedSkill.name,
94
92
  result: skillReadGate.action === "allow" ? "allow" : "deny",
@@ -1,6 +1,5 @@
1
1
  import { PATH_BEARING_TOOLS } from "../../external-directory";
2
2
  import { suggestSessionPattern } from "../../pattern-suggest";
3
- import { emitDecisionEvent } from "../../permission-events";
4
3
  import { applyPermissionGate } from "../../permission-gate";
5
4
  import {
6
5
  formatAskPrompt,
@@ -8,9 +7,8 @@ import {
8
7
  formatUserDeniedReason,
9
8
  } from "../../permission-prompts";
10
9
  import { getPermissionLogContext } from "../../tool-input-preview";
11
- import type { HandlerDeps } from "../types";
12
10
  import { deriveDecisionValue, deriveResolution } from "./helpers";
13
- import type { GateOutcome, ToolCallContext } from "./types";
11
+ import type { GateOutcome, ToolCallContext, ToolGateDeps } from "./types";
14
12
 
15
13
  /**
16
14
  * Evaluate the normal tool permission gate.
@@ -19,18 +17,18 @@ import type { GateOutcome, ToolCallContext } from "./types";
19
17
  */
20
18
  export async function evaluateToolGate(
21
19
  tcc: ToolCallContext,
22
- deps: HandlerDeps,
20
+ deps: ToolGateDeps,
23
21
  ): Promise<GateOutcome> {
24
- const check = deps.runtime.permissionManager.checkPermission(
22
+ const check = deps.checkPermission(
25
23
  tcc.toolName,
26
24
  tcc.input,
27
25
  tcc.agentName ?? undefined,
28
- deps.runtime.sessionRules.getRuleset(),
26
+ deps.getSessionRuleset(),
29
27
  );
30
28
 
31
29
  // Session-hit: already approved by a session rule — skip the gate entirely.
32
30
  if (check.source === "session") {
33
- deps.runtime.writeReviewLog("permission_request.session_approved", {
31
+ deps.writeReviewLog("permission_request.session_approved", {
34
32
  source: "tool_call",
35
33
  toolCallId: tcc.toolCallId,
36
34
  toolName: tcc.toolName,
@@ -38,7 +36,7 @@ export async function evaluateToolGate(
38
36
  resolution: "session_approved",
39
37
  sessionApprovalPattern: check.matchedPattern,
40
38
  });
41
- emitDecisionEvent(deps.events, {
39
+ deps.emitDecision({
42
40
  surface: tcc.toolName,
43
41
  value: deriveDecisionValue(tcc.toolName, check),
44
42
  result: "allow",
@@ -82,9 +80,7 @@ export async function evaluateToolGate(
82
80
  tcc.agentName ?? undefined,
83
81
  tcc.input,
84
82
  );
85
- const toolCanConfirm = deps.canRequestPermissionConfirmation(
86
- deps.runtime.runtimeContext!,
87
- );
83
+ const toolCanConfirm = deps.canConfirm();
88
84
  let toolDecisionAutoApproved = false;
89
85
  const toolGate = await applyPermissionGate({
90
86
  state: check.state,
@@ -94,23 +90,20 @@ export async function evaluateToolGate(
94
90
  pattern: suggestion.pattern,
95
91
  },
96
92
  promptForApproval: async () => {
97
- const decision = await deps.promptPermission(
98
- deps.runtime.runtimeContext!,
99
- {
100
- requestId: tcc.toolCallId,
101
- source: "tool_call",
102
- agentName: tcc.agentName,
103
- message: toolAskMessage,
104
- toolCallId: tcc.toolCallId,
105
- toolName: tcc.toolName,
106
- sessionLabel: suggestion.label,
107
- ...permissionLogContext,
108
- },
109
- );
93
+ const decision = await deps.promptPermission({
94
+ requestId: tcc.toolCallId,
95
+ source: "tool_call",
96
+ agentName: tcc.agentName,
97
+ message: toolAskMessage,
98
+ toolCallId: tcc.toolCallId,
99
+ toolName: tcc.toolName,
100
+ sessionLabel: suggestion.label,
101
+ ...permissionLogContext,
102
+ });
110
103
  toolDecisionAutoApproved = decision.autoApproved === true;
111
104
  return decision;
112
105
  },
113
- writeLog: deps.runtime.writeReviewLog,
106
+ writeLog: deps.writeReviewLog,
114
107
  logContext: {
115
108
  source: "tool_call",
116
109
  toolCallId: tcc.toolCallId,
@@ -129,7 +122,7 @@ export async function evaluateToolGate(
129
122
 
130
123
  const toolGateHasSession =
131
124
  toolGate.action === "allow" && toolGate.sessionApproval !== undefined;
132
- emitDecisionEvent(deps.events, {
125
+ deps.emitDecision({
133
126
  surface: tcc.toolName,
134
127
  value: deriveDecisionValue(tcc.toolName, check),
135
128
  result: toolGate.action === "allow" ? "allow" : "deny",
@@ -150,7 +143,7 @@ export async function evaluateToolGate(
150
143
  }
151
144
 
152
145
  if (toolGate.sessionApproval) {
153
- deps.runtime.sessionRules.approve(
146
+ deps.approveSessionRule(
154
147
  toolGate.sessionApproval.surface,
155
148
  toolGate.sessionApproval.pattern,
156
149
  );
@@ -1,5 +1,12 @@
1
1
  import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
2
2
 
3
+ import type { PermissionPromptDecision } from "../../permission-dialog";
4
+ import type { PermissionDecisionEvent } from "../../permission-events";
5
+ import type { Rule } from "../../rule";
6
+ import type { SkillPromptEntry } from "../../skill-prompt-sanitizer";
7
+ import type { PermissionCheckResult } from "../../types";
8
+ import type { PromptPermissionDetails } from "../types";
9
+
3
10
  /** Outcome of a single permission gate evaluation. */
4
11
  export type GateOutcome =
5
12
  | { action: "allow" }
@@ -13,3 +20,71 @@ export interface ToolCallContext {
13
20
  toolCallId: string;
14
21
  cwd: string | undefined;
15
22
  }
23
+
24
+ // ── Per-gate narrow dependency interfaces ──────────────────────────────────
25
+
26
+ /** Narrow deps for evaluateToolGate — every field is a leaf method. */
27
+ export interface ToolGateDeps {
28
+ checkPermission(
29
+ surface: string,
30
+ input: unknown,
31
+ agentName?: string,
32
+ sessionRules?: Rule[],
33
+ ): PermissionCheckResult;
34
+ getSessionRuleset(): Rule[];
35
+ approveSessionRule(surface: string, pattern: string): void;
36
+ writeReviewLog(event: string, details: Record<string, unknown>): void;
37
+ emitDecision(event: PermissionDecisionEvent): void;
38
+ canConfirm(): boolean;
39
+ promptPermission(
40
+ details: PromptPermissionDetails,
41
+ ): Promise<PermissionPromptDecision>;
42
+ }
43
+
44
+ /** Narrow deps for evaluateExternalDirectoryGate. */
45
+ export interface ExternalDirectoryGateDeps {
46
+ checkPermission(
47
+ surface: string,
48
+ input: unknown,
49
+ agentName?: string,
50
+ sessionRules?: Rule[],
51
+ ): PermissionCheckResult;
52
+ getSessionRuleset(): Rule[];
53
+ approveSessionRule(surface: string, pattern: string): void;
54
+ writeReviewLog(event: string, details: Record<string, unknown>): void;
55
+ emitDecision(event: PermissionDecisionEvent): void;
56
+ canConfirm(): boolean;
57
+ promptPermission(
58
+ details: PromptPermissionDetails,
59
+ ): Promise<PermissionPromptDecision>;
60
+ /** Resolved infrastructure dirs (static + config-based). */
61
+ getInfrastructureDirs(): string[];
62
+ }
63
+
64
+ /** Narrow deps for evaluateBashExternalDirectoryGate. */
65
+ export interface BashExternalDirectoryGateDeps {
66
+ checkPermission(
67
+ surface: string,
68
+ input: unknown,
69
+ agentName?: string,
70
+ sessionRules?: Rule[],
71
+ ): PermissionCheckResult;
72
+ getSessionRuleset(): Rule[];
73
+ approveSessionRule(surface: string, pattern: string): void;
74
+ writeReviewLog(event: string, details: Record<string, unknown>): void;
75
+ canConfirm(): boolean;
76
+ promptPermission(
77
+ details: PromptPermissionDetails,
78
+ ): Promise<PermissionPromptDecision>;
79
+ }
80
+
81
+ /** Narrow deps for evaluateSkillReadGate. */
82
+ export interface SkillReadGateDeps {
83
+ getActiveSkillEntries(): SkillPromptEntry[];
84
+ writeReviewLog(event: string, details: Record<string, unknown>): void;
85
+ emitDecision(event: PermissionDecisionEvent): void;
86
+ canConfirm(): boolean;
87
+ promptPermission(
88
+ details: PromptPermissionDetails,
89
+ ): Promise<PermissionPromptDecision>;
90
+ }
@@ -40,7 +40,7 @@ export async function handleInput(
40
40
  event: InputPayload,
41
41
  ctx: ExtensionContext,
42
42
  ): Promise<InputEventResult> {
43
- deps.runtime.runtimeContext = ctx;
43
+ deps.session.runtimeContext = ctx;
44
44
  deps.startForwardedPermissionPolling(ctx);
45
45
 
46
46
  const skillName = extractSkillNameFromInput(event.text);
@@ -49,7 +49,7 @@ export async function handleInput(
49
49
  }
50
50
 
51
51
  const agentName = deps.resolveAgentName(ctx);
52
- const check = deps.runtime.permissionManager.checkPermission(
52
+ const check = deps.session.permissionManager.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.runtime.writeReviewLog,
85
+ writeLog: deps.writeReviewLog,
86
86
  logContext: {
87
87
  source: "skill_input",
88
88
  skillName,