@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.
@@ -6,24 +6,9 @@ import {
6
6
  handleSessionStart,
7
7
  } from "../../src/handlers/lifecycle";
8
8
  import type { HandlerDeps } from "../../src/handlers/types";
9
- import type { PermissionManager } from "../../src/permission-manager";
10
- import type { SessionState } from "../../src/runtime";
11
- import type { SessionRules } from "../../src/session-rules";
12
- import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
9
+ import type { PermissionSession } from "../../src/permission-session";
13
10
 
14
- // ── active-agent stub ──────────────────────────────────────────────────────
15
- const { mockGetActiveAgentName } = vi.hoisted(() => ({
16
- mockGetActiveAgentName: vi.fn<(ctx: ExtensionContext) => string | null>(),
17
- }));
18
-
19
- vi.mock("../../src/active-agent", () => ({
20
- getActiveAgentName: mockGetActiveAgentName,
21
- getActiveAgentNameFromSystemPrompt: vi.fn().mockReturnValue(null),
22
- }));
23
-
24
- // ── PERMISSION_SYSTEM_STATUS_KEY stub ──────────────────────────────────────
25
- // status.ts is re-exported through the handler; the key value doesn't matter
26
- // for these tests.
11
+ // ── status stub ────────────────────────────────────────────────────────────
27
12
  vi.mock("../../src/status", () => ({
28
13
  PERMISSION_SYSTEM_STATUS_KEY: "permission-system",
29
14
  syncPermissionSystemStatus: vi.fn(),
@@ -51,55 +36,32 @@ function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
51
36
  } as unknown as ExtensionContext;
52
37
  }
53
38
 
54
- function makePermissionManager(
55
- issues: string[] = [],
56
- ): Pick<PermissionManager, "getConfigIssues"> {
57
- return {
58
- getConfigIssues: vi.fn().mockReturnValue(issues),
59
- };
60
- }
61
-
62
- function makeSessionRules(): SessionRules {
63
- return {
64
- approve: vi.fn(),
65
- getRuleset: vi.fn().mockReturnValue([]),
66
- clear: vi.fn(),
67
- } as unknown as SessionRules;
68
- }
69
-
70
- function makeSession(overrides: Partial<SessionState> = {}): SessionState {
39
+ function makeSession(
40
+ overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
41
+ ): PermissionSession {
71
42
  return {
72
- runtimeContext: null,
73
- permissionManager: makePermissionManager() as unknown as PermissionManager,
74
- activeSkillEntries: [] as SkillPromptEntry[],
75
- lastKnownActiveAgentName: null,
76
- lastActiveToolsCacheKey: null,
77
- lastPromptStateCacheKey: null,
78
- sessionRules: makeSessionRules(),
43
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
44
+ refreshConfig: vi.fn(),
45
+ resetForNewSession: vi.fn(),
46
+ logResolvedConfigPaths: vi.fn(),
47
+ resolveAgentName: vi.fn().mockReturnValue(null),
48
+ getConfigIssues: vi.fn().mockReturnValue([]),
49
+ reload: vi.fn(),
50
+ getRuntimeContext: vi.fn().mockReturnValue(null),
51
+ shutdown: vi.fn(),
79
52
  ...overrides,
80
- };
53
+ } as unknown as PermissionSession;
81
54
  }
82
55
 
83
56
  function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
84
57
  return {
85
58
  session: makeSession(),
86
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
87
- piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
88
- getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
89
- createPermissionManagerForCwd: vi
90
- .fn()
91
- .mockReturnValue(makePermissionManager()),
92
- refreshExtensionConfig: vi.fn(),
93
- logResolvedConfigPaths: vi.fn(),
94
- resolveAgentName: vi.fn().mockReturnValue(null),
59
+ events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
95
60
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
96
61
  promptPermission: vi
97
62
  .fn()
98
63
  .mockResolvedValue({ approved: true, state: "approved" }),
99
64
  createPermissionRequestId: vi.fn().mockReturnValue("test-id"),
100
- events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
101
- startForwardedPermissionPolling: vi.fn(),
102
- stopForwardedPermissionPolling: vi.fn(),
103
65
  stopPermissionRpcHandlers: vi.fn(),
104
66
  getAllTools: vi.fn().mockReturnValue([]),
105
67
  setActiveTools: vi.fn(),
@@ -110,98 +72,54 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
110
72
  // ── handleSessionStart ─────────────────────────────────────────────────────
111
73
 
112
74
  describe("handleSessionStart", () => {
113
- beforeEach(() => {
114
- mockGetActiveAgentName.mockReset();
115
- mockGetActiveAgentName.mockReturnValue(null);
116
- });
117
-
118
- it("sets the runtime context", async () => {
119
- const ctx = makeCtx();
120
- const deps = makeDeps();
121
- await handleSessionStart(deps, { reason: "startup" }, ctx);
122
- expect(deps.session.runtimeContext).toBe(ctx);
123
- });
124
-
125
- it("refreshes extension config with ctx", async () => {
126
- const ctx = makeCtx();
127
- const deps = makeDeps();
128
- await handleSessionStart(deps, { reason: "startup" }, ctx);
129
- expect(deps.refreshExtensionConfig).toHaveBeenCalledWith(ctx);
130
- });
131
-
132
- it("creates a new permission manager for ctx.cwd and stores it", async () => {
133
- const ctx = makeCtx({ cwd: "/my/project" });
134
- const newPm = makePermissionManager();
135
- const deps = makeDeps({
136
- createPermissionManagerForCwd: vi.fn().mockReturnValue(newPm),
137
- });
138
- await handleSessionStart(deps, { reason: "startup" }, ctx);
139
- expect(deps.createPermissionManagerForCwd).toHaveBeenCalledWith(
140
- "/my/project",
141
- );
142
- expect(deps.session.permissionManager).toBe(newPm);
143
- });
144
-
145
- it("clears the before_agent_start cache", async () => {
146
- const ctx = makeCtx();
147
- const deps = makeDeps();
148
- await handleSessionStart(deps, { reason: "startup" }, ctx);
149
- expect(deps.session.activeSkillEntries).toEqual([]);
150
- expect(deps.session.lastActiveToolsCacheKey).toBeNull();
151
- expect(deps.session.lastPromptStateCacheKey).toBeNull();
152
- });
153
-
154
- it("sets lastKnownActiveAgentName from getActiveAgentName", async () => {
155
- mockGetActiveAgentName.mockReturnValue("my-agent");
75
+ it("refreshes config with ctx", async () => {
156
76
  const ctx = makeCtx();
157
77
  const deps = makeDeps();
158
78
  await handleSessionStart(deps, { reason: "startup" }, ctx);
159
- expect(deps.session.lastKnownActiveAgentName).toBe("my-agent");
79
+ expect(deps.session.refreshConfig).toHaveBeenCalledWith(ctx);
160
80
  });
161
81
 
162
- it("sets lastKnownActiveAgentName to null when no agent is active", async () => {
163
- mockGetActiveAgentName.mockReturnValue(null);
82
+ it("calls resetForNewSession with ctx", async () => {
164
83
  const ctx = makeCtx();
165
84
  const deps = makeDeps();
166
85
  await handleSessionStart(deps, { reason: "startup" }, ctx);
167
- expect(deps.session.lastKnownActiveAgentName).toBeNull();
86
+ expect(deps.session.resetForNewSession).toHaveBeenCalledWith(ctx);
168
87
  });
169
88
 
170
- it("starts forwarded permission polling", async () => {
171
- const ctx = makeCtx();
89
+ it("logs resolved config paths", async () => {
172
90
  const deps = makeDeps();
173
- await handleSessionStart(deps, { reason: "startup" }, ctx);
174
- expect(deps.startForwardedPermissionPolling).toHaveBeenCalledWith(ctx);
91
+ await handleSessionStart(deps, { reason: "startup" }, makeCtx());
92
+ expect(deps.session.logResolvedConfigPaths).toHaveBeenCalledOnce();
175
93
  });
176
94
 
177
- it("logs resolved config paths", async () => {
95
+ it("resolves agent name from ctx", async () => {
178
96
  const ctx = makeCtx();
179
97
  const deps = makeDeps();
180
98
  await handleSessionStart(deps, { reason: "startup" }, ctx);
181
- expect(deps.logResolvedConfigPaths).toHaveBeenCalledOnce();
99
+ expect(deps.session.resolveAgentName).toHaveBeenCalledWith(ctx);
182
100
  });
183
101
 
184
102
  it("notifies each policy issue", async () => {
185
- const pm = makePermissionManager(["issue A", "issue B"]);
186
- const deps = makeDeps({
187
- createPermissionManagerForCwd: vi.fn().mockReturnValue(pm),
103
+ const session = makeSession({
104
+ getConfigIssues: vi.fn().mockReturnValue(["issue A", "issue B"]),
188
105
  });
106
+ const deps = makeDeps({ session });
189
107
  await handleSessionStart(deps, { reason: "startup" }, makeCtx());
190
- expect(deps.logger.warn).toHaveBeenCalledWith("issue A");
191
- expect(deps.logger.warn).toHaveBeenCalledWith("issue B");
108
+ expect(session.logger.warn).toHaveBeenCalledWith("issue A");
109
+ expect(session.logger.warn).toHaveBeenCalledWith("issue B");
192
110
  });
193
111
 
194
- it("does not call notifyWarning when there are no policy issues", async () => {
112
+ it("does not warn when there are no policy issues", async () => {
195
113
  const deps = makeDeps();
196
114
  await handleSessionStart(deps, { reason: "startup" }, makeCtx());
197
- expect(deps.logger.warn).not.toHaveBeenCalled();
115
+ expect(deps.session.logger.warn).not.toHaveBeenCalled();
198
116
  });
199
117
 
200
118
  it("writes lifecycle.reload debug log when reason is reload", async () => {
201
119
  const ctx = makeCtx({ cwd: "/proj" });
202
120
  const deps = makeDeps();
203
121
  await handleSessionStart(deps, { reason: "reload" }, ctx);
204
- expect(deps.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
122
+ expect(deps.session.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
205
123
  triggeredBy: "session_start",
206
124
  reason: "reload",
207
125
  cwd: "/proj",
@@ -211,7 +129,18 @@ describe("handleSessionStart", () => {
211
129
  it("does not write lifecycle.reload debug log for non-reload reasons", async () => {
212
130
  const deps = makeDeps();
213
131
  await handleSessionStart(deps, { reason: "startup" }, makeCtx());
214
- expect(deps.logger.debug).not.toHaveBeenCalled();
132
+ expect(deps.session.logger.debug).not.toHaveBeenCalled();
133
+ });
134
+
135
+ it("calls refreshConfig before resetForNewSession", async () => {
136
+ const callOrder: string[] = [];
137
+ const session = makeSession({
138
+ refreshConfig: vi.fn(() => callOrder.push("refreshConfig")),
139
+ resetForNewSession: vi.fn(() => callOrder.push("resetForNewSession")),
140
+ });
141
+ const deps = makeDeps({ session });
142
+ await handleSessionStart(deps, { reason: "startup" }, makeCtx());
143
+ expect(callOrder).toEqual(["refreshConfig", "resetForNewSession"]);
215
144
  });
216
145
  });
217
146
 
@@ -221,43 +150,23 @@ describe("handleResourcesDiscover", () => {
221
150
  it("does nothing when reason is not reload", async () => {
222
151
  const deps = makeDeps();
223
152
  await handleResourcesDiscover(deps, { reason: "startup" });
224
- expect(deps.createPermissionManagerForCwd).not.toHaveBeenCalled();
225
- expect(deps.logger.debug).not.toHaveBeenCalled();
226
- });
227
-
228
- it("creates and stores a new PM using runtimeContext.cwd on reload", async () => {
229
- const ctx = makeCtx({ cwd: "/runtime/cwd" });
230
- const newPm = makePermissionManager();
231
- const deps = makeDeps({
232
- session: makeSession({ runtimeContext: ctx }),
233
- createPermissionManagerForCwd: vi.fn().mockReturnValue(newPm),
234
- });
235
- await handleResourcesDiscover(deps, { reason: "reload" });
236
- expect(deps.createPermissionManagerForCwd).toHaveBeenCalledWith(
237
- "/runtime/cwd",
238
- );
239
- expect(deps.session.permissionManager).toBe(newPm);
153
+ expect(deps.session.reload).not.toHaveBeenCalled();
240
154
  });
241
155
 
242
- it("uses undefined cwd when runtimeContext is null on reload", async () => {
156
+ it("calls reload on the session on reload", async () => {
243
157
  const deps = makeDeps();
244
158
  await handleResourcesDiscover(deps, { reason: "reload" });
245
- expect(deps.createPermissionManagerForCwd).toHaveBeenCalledWith(undefined);
246
- });
247
-
248
- it("clears the before_agent_start cache on reload", async () => {
249
- const deps = makeDeps();
250
- await handleResourcesDiscover(deps, { reason: "reload" });
251
- expect(deps.session.activeSkillEntries).toEqual([]);
252
- expect(deps.session.lastActiveToolsCacheKey).toBeNull();
253
- expect(deps.session.lastPromptStateCacheKey).toBeNull();
159
+ expect(deps.session.reload).toHaveBeenCalledOnce();
254
160
  });
255
161
 
256
162
  it("writes lifecycle.reload debug log on reload", async () => {
257
163
  const ctx = makeCtx({ cwd: "/proj" });
258
- const deps = makeDeps({ session: makeSession({ runtimeContext: ctx }) });
164
+ const session = makeSession({
165
+ getRuntimeContext: vi.fn().mockReturnValue(ctx),
166
+ });
167
+ const deps = makeDeps({ session });
259
168
  await handleResourcesDiscover(deps, { reason: "reload" });
260
- expect(deps.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
169
+ expect(session.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
261
170
  triggeredBy: "resources_discover",
262
171
  reason: "reload",
263
172
  cwd: "/proj",
@@ -267,7 +176,7 @@ describe("handleResourcesDiscover", () => {
267
176
  it("logs cwd as null when runtimeContext is null on reload", async () => {
268
177
  const deps = makeDeps();
269
178
  await handleResourcesDiscover(deps, { reason: "reload" });
270
- expect(deps.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
179
+ expect(deps.session.logger.debug).toHaveBeenCalledWith("lifecycle.reload", {
271
180
  triggeredBy: "resources_discover",
272
181
  reason: "reload",
273
182
  cwd: null,
@@ -278,11 +187,12 @@ describe("handleResourcesDiscover", () => {
278
187
  // ── handleSessionShutdown ──────────────────────────────────────────────────
279
188
 
280
189
  describe("handleSessionShutdown", () => {
281
- it("clears the UI status when a runtime context is present", async () => {
190
+ it("clears UI status when runtime context is present", async () => {
282
191
  const ctx = makeCtx();
283
- const deps = makeDeps({
284
- session: makeSession({ runtimeContext: ctx }),
192
+ const session = makeSession({
193
+ getRuntimeContext: vi.fn().mockReturnValue(ctx),
285
194
  });
195
+ const deps = makeDeps({ session });
286
196
  await handleSessionShutdown(deps);
287
197
  expect(ctx.ui.setStatus).toHaveBeenCalledWith(
288
198
  "permission-system",
@@ -295,44 +205,15 @@ describe("handleSessionShutdown", () => {
295
205
  await expect(handleSessionShutdown(deps)).resolves.not.toThrow();
296
206
  });
297
207
 
298
- it("sets runtime context to null", async () => {
299
- const ctx = makeCtx();
300
- const deps = makeDeps({ session: makeSession({ runtimeContext: ctx }) });
301
- await handleSessionShutdown(deps);
302
- expect(deps.session.runtimeContext).toBeNull();
303
- });
304
-
305
- it("clears the before_agent_start cache", async () => {
208
+ it("calls shutdown on the session", async () => {
306
209
  const deps = makeDeps();
307
210
  await handleSessionShutdown(deps);
308
- expect(deps.session.activeSkillEntries).toEqual([]);
309
- expect(deps.session.lastActiveToolsCacheKey).toBeNull();
310
- expect(deps.session.lastPromptStateCacheKey).toBeNull();
211
+ expect(deps.session.shutdown).toHaveBeenCalledOnce();
311
212
  });
312
213
 
313
- it("clears the session rules", async () => {
314
- const deps = makeDeps();
315
- await handleSessionShutdown(deps);
316
- expect(deps.session.sessionRules.clear).toHaveBeenCalledOnce();
317
- });
318
-
319
- it("stops forwarded permission polling", async () => {
320
- const deps = makeDeps();
321
- await handleSessionShutdown(deps);
322
- expect(deps.stopForwardedPermissionPolling).toHaveBeenCalledOnce();
323
- });
324
-
325
- it("calls stopPermissionRpcHandlers on shutdown", async () => {
214
+ it("calls stopPermissionRpcHandlers", async () => {
326
215
  const deps = makeDeps();
327
216
  await handleSessionShutdown(deps);
328
217
  expect(deps.stopPermissionRpcHandlers).toHaveBeenCalledOnce();
329
218
  });
330
-
331
- it("does not reset lastKnownActiveAgentName", async () => {
332
- const deps = makeDeps({
333
- session: makeSession({ lastKnownActiveAgentName: "remembered" }),
334
- });
335
- await handleSessionShutdown(deps);
336
- expect(deps.session.lastKnownActiveAgentName).toBe("remembered");
337
- });
338
219
  });
@@ -9,8 +9,8 @@ import { handleToolCall } from "../../src/handlers/tool-call";
9
9
  import type { HandlerDeps } from "../../src/handlers/types";
10
10
  import type { PermissionDecisionEvent } from "../../src/permission-events";
11
11
  import { PERMISSIONS_DECISION_CHANNEL } from "../../src/permission-events";
12
- import type { SessionState } from "../../src/runtime";
13
- import type { PermissionCheckResult } from "../../src/types";
12
+ import type { PermissionSession } from "../../src/permission-session";
13
+ import type { PermissionCheckResult, PermissionState } from "../../src/types";
14
14
 
15
15
  // ── helpers ────────────────────────────────────────────────────────────────
16
16
 
@@ -67,43 +67,35 @@ function makeCheckResult(
67
67
  };
68
68
  }
69
69
 
70
- function makeSession(overrides: Partial<SessionState> = {}): SessionState {
70
+ function makeSession(
71
+ overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
72
+ ): PermissionSession {
71
73
  return {
72
- runtimeContext: null,
73
- permissionManager: {
74
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
75
- } as unknown as SessionState["permissionManager"],
76
- activeSkillEntries: [],
77
- lastKnownActiveAgentName: null,
78
- lastActiveToolsCacheKey: null,
79
- lastPromptStateCacheKey: null,
80
- sessionRules: {
81
- approve: vi.fn(),
82
- getRuleset: vi.fn().mockReturnValue([]),
83
- clear: vi.fn(),
84
- } as unknown as SessionState["sessionRules"],
74
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
75
+ activate: vi.fn(),
76
+ resolveAgentName: vi.fn().mockReturnValue(null),
77
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
78
+ getToolPermission: vi.fn().mockReturnValue("allow" as PermissionState),
79
+ getSessionRuleset: vi.fn().mockReturnValue([]),
80
+ approveSessionRule: vi.fn(),
81
+ getActiveSkillEntries: vi.fn().mockReturnValue([]),
82
+ getInfrastructureDirs: vi
83
+ .fn()
84
+ .mockReturnValue(["/test/agent", "/test/agent/git"]),
85
+ getInfrastructureReadPaths: vi.fn().mockReturnValue([]),
85
86
  ...overrides,
86
- };
87
+ } as unknown as PermissionSession;
87
88
  }
88
89
 
89
90
  function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
90
91
  return {
91
92
  session: makeSession(),
92
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
93
- piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
94
- getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
95
93
  events: makeEvents(),
96
- createPermissionManagerForCwd: vi.fn(),
97
- refreshExtensionConfig: vi.fn(),
98
- logResolvedConfigPaths: vi.fn(),
99
- resolveAgentName: vi.fn().mockReturnValue(null),
100
94
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
101
95
  promptPermission: vi
102
96
  .fn()
103
97
  .mockResolvedValue({ approved: true, state: "approved" }),
104
98
  createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
105
- startForwardedPermissionPolling: vi.fn(),
106
- stopForwardedPermissionPolling: vi.fn(),
107
99
  stopPermissionRpcHandlers: vi.fn(),
108
100
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }, { name: "bash" }]),
109
101
  setActiveTools: vi.fn(),
@@ -123,18 +115,15 @@ function getDecisionEvents(deps: HandlerDeps): PermissionDecisionEvent[] {
123
115
 
124
116
  describe("handleToolCall decision events — policy_allow", () => {
125
117
  it("emits allow with policy_allow when checkPermission returns allow", async () => {
126
- const deps = makeDeps({
127
- session: makeSession({
128
- permissionManager: {
129
- checkPermission: vi.fn().mockReturnValue(
130
- makeCheckResult("allow", {
131
- origin: "global",
132
- matchedPattern: "*",
133
- }),
134
- ),
135
- } as unknown as SessionState["permissionManager"],
136
- }),
118
+ const session = makeSession({
119
+ checkPermission: vi.fn().mockReturnValue(
120
+ makeCheckResult("allow", {
121
+ origin: "global",
122
+ matchedPattern: "*",
123
+ }),
124
+ ),
137
125
  });
126
+ const deps = makeDeps({ session });
138
127
 
139
128
  await handleToolCall(deps, makeToolCallEvent("read"), makeCtx());
140
129
 
@@ -154,18 +143,15 @@ describe("handleToolCall decision events — policy_allow", () => {
154
143
 
155
144
  describe("handleToolCall decision events — policy_deny", () => {
156
145
  it("emits deny with policy_deny when checkPermission returns deny", async () => {
157
- const deps = makeDeps({
158
- session: makeSession({
159
- permissionManager: {
160
- checkPermission: vi.fn().mockReturnValue(
161
- makeCheckResult("deny", {
162
- origin: "project",
163
- matchedPattern: "read",
164
- }),
165
- ),
166
- } as unknown as SessionState["permissionManager"],
167
- }),
146
+ const session = makeSession({
147
+ checkPermission: vi.fn().mockReturnValue(
148
+ makeCheckResult("deny", {
149
+ origin: "project",
150
+ matchedPattern: "read",
151
+ }),
152
+ ),
168
153
  });
154
+ const deps = makeDeps({ session });
169
155
 
170
156
  await handleToolCall(deps, makeToolCallEvent("read"), makeCtx());
171
157
 
@@ -183,18 +169,15 @@ describe("handleToolCall decision events — policy_deny", () => {
183
169
 
184
170
  describe("handleToolCall decision events — session_approved", () => {
185
171
  it("emits allow with session_approved when checkPermission returns source:session", async () => {
186
- const deps = makeDeps({
187
- session: makeSession({
188
- permissionManager: {
189
- checkPermission: vi.fn().mockReturnValue(
190
- makeCheckResult("allow", {
191
- source: "session",
192
- matchedPattern: "git *",
193
- }),
194
- ),
195
- } as unknown as SessionState["permissionManager"],
196
- }),
172
+ const session = makeSession({
173
+ checkPermission: vi.fn().mockReturnValue(
174
+ makeCheckResult("allow", {
175
+ source: "session",
176
+ matchedPattern: "git *",
177
+ }),
178
+ ),
197
179
  });
180
+ const deps = makeDeps({ session });
198
181
 
199
182
  await handleToolCall(
200
183
  deps,
@@ -216,12 +199,11 @@ describe("handleToolCall decision events — session_approved", () => {
216
199
 
217
200
  describe("handleToolCall decision events — user_approved", () => {
218
201
  it("emits allow with user_approved when state=ask and user approves once", async () => {
202
+ const session = makeSession({
203
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
204
+ });
219
205
  const deps = makeDeps({
220
- session: makeSession({
221
- permissionManager: {
222
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
223
- } as unknown as SessionState["permissionManager"],
224
- }),
206
+ session,
225
207
  promptPermission: vi
226
208
  .fn()
227
209
  .mockResolvedValue({ approved: true, state: "approved" }),
@@ -238,12 +220,11 @@ describe("handleToolCall decision events — user_approved", () => {
238
220
  });
239
221
 
240
222
  it("emits allow with user_approved_for_session when user approves for session", async () => {
223
+ const session = makeSession({
224
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
225
+ });
241
226
  const deps = makeDeps({
242
- session: makeSession({
243
- permissionManager: {
244
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
245
- } as unknown as SessionState["permissionManager"],
246
- }),
227
+ session,
247
228
  promptPermission: vi
248
229
  .fn()
249
230
  .mockResolvedValue({ approved: true, state: "approved_for_session" }),
@@ -264,12 +245,11 @@ describe("handleToolCall decision events — user_approved", () => {
264
245
 
265
246
  describe("handleToolCall decision events — user_denied", () => {
266
247
  it("emits deny with user_denied when state=ask and user denies", async () => {
248
+ const session = makeSession({
249
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
250
+ });
267
251
  const deps = makeDeps({
268
- session: makeSession({
269
- permissionManager: {
270
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
271
- } as unknown as SessionState["permissionManager"],
272
- }),
252
+ session,
273
253
  promptPermission: vi
274
254
  .fn()
275
255
  .mockResolvedValue({ approved: false, state: "denied" }),
@@ -290,12 +270,11 @@ describe("handleToolCall decision events — user_denied", () => {
290
270
 
291
271
  describe("handleToolCall decision events — confirmation_unavailable", () => {
292
272
  it("emits deny with confirmation_unavailable when state=ask but no UI", async () => {
273
+ const session = makeSession({
274
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
275
+ });
293
276
  const deps = makeDeps({
294
- session: makeSession({
295
- permissionManager: {
296
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
297
- } as unknown as SessionState["permissionManager"],
298
- }),
277
+ session,
299
278
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
300
279
  });
301
280
 
@@ -319,14 +298,11 @@ describe("handleToolCall decision events — confirmation_unavailable", () => {
319
298
  describe("handleToolCall decision events — infrastructure_auto_allowed", () => {
320
299
  it("emits allow with infrastructure_auto_allowed for Pi infra reads", async () => {
321
300
  const infraDir = "/test/agent";
322
- const deps = makeDeps({
323
- piInfrastructureDirs: [infraDir],
324
- session: makeSession({
325
- permissionManager: {
326
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
327
- } as unknown as SessionState["permissionManager"],
328
- }),
301
+ const session = makeSession({
302
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
303
+ getInfrastructureDirs: vi.fn().mockReturnValue([infraDir]),
329
304
  });
305
+ const deps = makeDeps({ session });
330
306
 
331
307
  const event = makeToolCallEvent("read", {
332
308
  input: { path: `${infraDir}/some-file.json` },
@@ -334,7 +310,6 @@ describe("handleToolCall decision events — infrastructure_auto_allowed", () =>
334
310
  await handleToolCall(deps, event, makeCtx());
335
311
 
336
312
  const events = getDecisionEvents(deps);
337
- // One infrastructure_auto_allowed event + one policy_allow for the normal gate
338
313
  const infraEvents = events.filter(
339
314
  (e) => e.resolution === "infrastructure_auto_allowed",
340
315
  );
@@ -350,13 +325,11 @@ describe("handleToolCall decision events — infrastructure_auto_allowed", () =>
350
325
 
351
326
  describe("handleToolCall decision events — auto_approved", () => {
352
327
  it("emits allow with auto_approved when promptPermission returns autoApproved:true", async () => {
328
+ const session = makeSession({
329
+ checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
330
+ });
353
331
  const deps = makeDeps({
354
- session: makeSession({
355
- permissionManager: {
356
- checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
357
- } as unknown as SessionState["permissionManager"],
358
- }),
359
- // Simulate what PermissionPrompter returns in yolo mode
332
+ session,
360
333
  promptPermission: vi.fn().mockResolvedValue({
361
334
  approved: true,
362
335
  state: "approved",