@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,9 +6,8 @@ import {
6
6
  shouldExposeTool,
7
7
  } from "../../src/handlers/before-agent-start";
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 { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
9
+ import type { PermissionSession } from "../../src/permission-session";
10
+ import type { PermissionState } from "../../src/types";
12
11
 
13
12
  // ── SDK stubs ──────────────────────────────────────────────────────────────
14
13
  vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
@@ -45,53 +44,36 @@ function makeEvent(systemPrompt = "You are an assistant.") {
45
44
  return { systemPrompt };
46
45
  }
47
46
 
48
- /** Minimal PermissionManager stub for shouldExposeTool / policy-cache tests. */
49
- function makePm(
50
- toolPermission: "allow" | "deny" | "ask" = "allow",
51
- ): PermissionManager {
47
+ function makeSession(
48
+ overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
49
+ ): PermissionSession {
52
50
  return {
53
- getToolPermission: vi.fn().mockReturnValue(toolPermission),
54
- getPolicyCacheStamp: vi.fn().mockReturnValue("stamp-1"),
55
- getConfigIssues: vi.fn().mockReturnValue([]),
51
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
52
+ activate: vi.fn(),
53
+ refreshConfig: vi.fn(),
54
+ resolveAgentName: vi.fn().mockReturnValue(null),
55
+ getToolPermission: vi.fn().mockReturnValue("allow" as PermissionState),
56
56
  checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
57
- } as unknown as PermissionManager;
58
- }
59
-
60
- function makeSession(overrides: Partial<SessionState> = {}): SessionState {
61
- return {
62
- runtimeContext: null,
63
- permissionManager: makePm() as unknown as PermissionManager,
64
- activeSkillEntries: [] as SkillPromptEntry[],
65
- lastKnownActiveAgentName: null,
66
- lastActiveToolsCacheKey: null,
67
- lastPromptStateCacheKey: null,
68
- sessionRules: {
69
- approve: vi.fn(),
70
- getRuleset: vi.fn().mockReturnValue([]),
71
- clear: vi.fn(),
72
- } as unknown as SessionState["sessionRules"],
57
+ shouldUpdateActiveTools: vi.fn().mockReturnValue(true),
58
+ commitActiveToolsCacheKey: vi.fn(),
59
+ getPolicyCacheStamp: vi.fn().mockReturnValue("stamp-1"),
60
+ shouldUpdatePromptState: vi.fn().mockReturnValue(true),
61
+ commitPromptStateCacheKey: vi.fn(),
62
+ setActiveSkillEntries: vi.fn(),
63
+ getActiveSkillEntries: vi.fn().mockReturnValue([]),
73
64
  ...overrides,
74
- };
65
+ } as unknown as PermissionSession;
75
66
  }
76
67
 
77
68
  function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
78
69
  return {
79
70
  session: makeSession(),
80
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
81
- piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
82
- getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
83
- createPermissionManagerForCwd: vi.fn().mockReturnValue(makePm()),
84
- refreshExtensionConfig: vi.fn(),
85
- logResolvedConfigPaths: vi.fn(),
86
- resolveAgentName: vi.fn().mockReturnValue(null),
71
+ events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
87
72
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
88
73
  promptPermission: vi
89
74
  .fn()
90
75
  .mockResolvedValue({ approved: true, state: "approved" }),
91
76
  createPermissionRequestId: vi.fn().mockReturnValue("test-id"),
92
- events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
93
- startForwardedPermissionPolling: vi.fn(),
94
- stopForwardedPermissionPolling: vi.fn(),
95
77
  stopPermissionRpcHandlers: vi.fn(),
96
78
  getAllTools: vi.fn().mockReturnValue([]),
97
79
  setActiveTools: vi.fn(),
@@ -103,48 +85,48 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
103
85
 
104
86
  describe("shouldExposeTool", () => {
105
87
  it("returns true when tool permission is allow", () => {
106
- const pm = makePm("allow");
107
- expect(shouldExposeTool("read", null, pm)).toBe(true);
88
+ const getter = vi.fn().mockReturnValue("allow");
89
+ expect(shouldExposeTool("read", null, getter)).toBe(true);
108
90
  });
109
91
 
110
92
  it("returns true when tool permission is ask", () => {
111
- const pm = makePm("ask");
112
- expect(shouldExposeTool("bash", "agent-x", pm)).toBe(true);
93
+ const getter = vi.fn().mockReturnValue("ask");
94
+ expect(shouldExposeTool("bash", "agent-x", getter)).toBe(true);
113
95
  });
114
96
 
115
97
  it("returns false when tool permission is deny", () => {
116
- const pm = makePm("deny");
117
- expect(shouldExposeTool("write", null, pm)).toBe(false);
98
+ const getter = vi.fn().mockReturnValue("deny");
99
+ expect(shouldExposeTool("write", null, getter)).toBe(false);
118
100
  });
119
101
 
120
102
  it("passes agentName through to getToolPermission", () => {
121
- const pm = makePm("allow");
122
- shouldExposeTool("read", "my-agent", pm);
123
- expect(pm.getToolPermission).toHaveBeenCalledWith("read", "my-agent");
103
+ const getter = vi.fn().mockReturnValue("allow");
104
+ shouldExposeTool("read", "my-agent", getter);
105
+ expect(getter).toHaveBeenCalledWith("read", "my-agent");
124
106
  });
125
107
 
126
108
  it("converts null agentName to undefined for getToolPermission", () => {
127
- const pm = makePm("allow");
128
- shouldExposeTool("read", null, pm);
129
- expect(pm.getToolPermission).toHaveBeenCalledWith("read", undefined);
109
+ const getter = vi.fn().mockReturnValue("allow");
110
+ shouldExposeTool("read", null, getter);
111
+ expect(getter).toHaveBeenCalledWith("read", undefined);
130
112
  });
131
113
  });
132
114
 
133
115
  // ── handleBeforeAgentStart ─────────────────────────────────────────────────
134
116
 
135
117
  describe("handleBeforeAgentStart", () => {
136
- it("refreshes extension config with ctx", async () => {
118
+ it("activates the session with ctx", async () => {
137
119
  const ctx = makeCtx();
138
120
  const deps = makeDeps();
139
121
  await handleBeforeAgentStart(deps, makeEvent(), ctx);
140
- expect(deps.refreshExtensionConfig).toHaveBeenCalledWith(ctx);
122
+ expect(deps.session.activate).toHaveBeenCalledWith(ctx);
141
123
  });
142
124
 
143
- it("starts forwarded permission polling", async () => {
125
+ it("refreshes config with ctx", async () => {
144
126
  const ctx = makeCtx();
145
127
  const deps = makeDeps();
146
128
  await handleBeforeAgentStart(deps, makeEvent(), ctx);
147
- expect(deps.startForwardedPermissionPolling).toHaveBeenCalledWith(ctx);
129
+ expect(deps.session.refreshConfig).toHaveBeenCalledWith(ctx);
148
130
  });
149
131
 
150
132
  it("resolves agent name using systemPrompt", async () => {
@@ -155,33 +137,28 @@ describe("handleBeforeAgentStart", () => {
155
137
  makeEvent("<active_agent name='x'>"),
156
138
  ctx,
157
139
  );
158
- expect(deps.resolveAgentName).toHaveBeenCalledWith(
140
+ expect(deps.session.resolveAgentName).toHaveBeenCalledWith(
159
141
  ctx,
160
142
  "<active_agent name='x'>",
161
143
  );
162
144
  });
163
145
 
164
146
  it("filters out denied tools from allowed list", async () => {
165
- const pm = makePm("deny");
147
+ const session = makeSession({
148
+ getToolPermission: vi.fn().mockReturnValue("deny"),
149
+ });
166
150
  const deps = makeDeps({
167
- session: makeSession({
168
- permissionManager: pm as unknown as PermissionManager,
169
- }),
151
+ session,
170
152
  getAllTools: vi
171
153
  .fn()
172
154
  .mockReturnValue([{ name: "write" }, { name: "read" }]),
173
155
  });
174
- // write is deny, read is deny (same pm stub — both denied)
175
156
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
176
157
  expect(deps.setActiveTools).toHaveBeenCalledWith([]);
177
158
  });
178
159
 
179
160
  it("includes allowed and ask tools in the active list", async () => {
180
- const pm = makePm("allow");
181
161
  const deps = makeDeps({
182
- session: makeSession({
183
- permissionManager: pm as unknown as PermissionManager,
184
- }),
185
162
  getAllTools: vi
186
163
  .fn()
187
164
  .mockReturnValue([{ name: "read" }, { name: "write" }]),
@@ -190,31 +167,59 @@ describe("handleBeforeAgentStart", () => {
190
167
  expect(deps.setActiveTools).toHaveBeenCalledWith(["read", "write"]);
191
168
  });
192
169
 
193
- it("updates the active-tools cache key after applying", async () => {
170
+ it("commits active-tools cache key after applying", async () => {
194
171
  const deps = makeDeps({
195
172
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
196
173
  });
197
174
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
198
- expect(deps.session.lastActiveToolsCacheKey).not.toBeNull();
175
+ expect(deps.session.commitActiveToolsCacheKey).toHaveBeenCalled();
199
176
  });
200
177
 
201
178
  it("skips setActiveTools when cache key is unchanged", async () => {
202
- // Pre-populate the cache key to match what would be computed for ["read"]
203
- const { createActiveToolsCacheKey } = await import(
204
- "../../src/before-agent-start-cache"
205
- );
206
- const key = createActiveToolsCacheKey(["read"]);
179
+ const session = makeSession({
180
+ shouldUpdateActiveTools: vi.fn().mockReturnValue(false),
181
+ });
207
182
  const deps = makeDeps({
208
- session: makeSession({ lastActiveToolsCacheKey: key }),
183
+ session,
209
184
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
210
185
  });
211
186
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
212
187
  expect(deps.setActiveTools).not.toHaveBeenCalled();
188
+ expect(session.commitActiveToolsCacheKey).not.toHaveBeenCalled();
189
+ });
190
+
191
+ it("returns empty object when prompt cache is unchanged", async () => {
192
+ const session = makeSession({
193
+ shouldUpdatePromptState: vi.fn().mockReturnValue(false),
194
+ });
195
+ const deps = makeDeps({
196
+ session,
197
+ getAllTools: vi.fn().mockReturnValue([]),
198
+ });
199
+ const result = await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
200
+ expect(result).toEqual({});
201
+ expect(session.commitPromptStateCacheKey).not.toHaveBeenCalled();
213
202
  });
214
203
 
215
- it("updates the prompt-state cache key and returns modified systemPrompt", async () => {
216
- // Provide a systemPrompt that sanitizeAvailableToolsSection will modify:
217
- // it strips denied tools from the "Available tools:" section.
204
+ it("commits prompt-state cache key and processes prompt when cache is new", async () => {
205
+ const deps = makeDeps({
206
+ getAllTools: vi.fn().mockReturnValue([]),
207
+ });
208
+ await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
209
+ expect(deps.session.commitPromptStateCacheKey).toHaveBeenCalled();
210
+ });
211
+
212
+ it("stores resolved skill entries on the session", async () => {
213
+ const deps = makeDeps({
214
+ getAllTools: vi.fn().mockReturnValue([]),
215
+ });
216
+ await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
217
+ expect(deps.session.setActiveSkillEntries).toHaveBeenCalledWith(
218
+ expect.any(Array),
219
+ );
220
+ });
221
+
222
+ it("returns modified systemPrompt when prompt changes", async () => {
218
223
  const systemPrompt = `You are an assistant.\n\nAvailable tools:\n- read\n- write\n`;
219
224
  const deps = makeDeps({
220
225
  getAllTools: vi.fn().mockReturnValue([]),
@@ -224,9 +229,7 @@ describe("handleBeforeAgentStart", () => {
224
229
  makeEvent(systemPrompt),
225
230
  makeCtx(),
226
231
  );
227
- // The prompt was modified, so systemPrompt should be returned
228
232
  expect(result).toHaveProperty("systemPrompt");
229
- expect(deps.session.lastPromptStateCacheKey).not.toBeNull();
230
233
  });
231
234
 
232
235
  it("returns empty object when systemPrompt is unchanged", async () => {
@@ -241,39 +244,4 @@ describe("handleBeforeAgentStart", () => {
241
244
  );
242
245
  expect(result).toEqual({});
243
246
  });
244
-
245
- it("stores resolved skill entries on deps", async () => {
246
- const deps = makeDeps({
247
- getAllTools: vi.fn().mockReturnValue([]),
248
- });
249
- await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
250
- expect(deps.session.activeSkillEntries).toEqual(expect.any(Array));
251
- });
252
-
253
- it("returns empty object and skips prompt work when prompt cache key is unchanged", async () => {
254
- const { createBeforeAgentStartPromptStateKey } = await import(
255
- "../../src/before-agent-start-cache"
256
- );
257
- const pm = makePm("allow");
258
- const ctx = makeCtx({ cwd: "/proj" });
259
- const allowedTools: string[] = ["read"];
260
- const key = createBeforeAgentStartPromptStateKey({
261
- agentName: null,
262
- cwd: "/proj",
263
- permissionStamp: "stamp-1",
264
- systemPrompt: "hello",
265
- allowedToolNames: allowedTools,
266
- });
267
- const deps = makeDeps({
268
- session: makeSession({
269
- permissionManager: pm as unknown as PermissionManager,
270
- lastPromptStateCacheKey: key,
271
- }),
272
- getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
273
- });
274
- const result = await handleBeforeAgentStart(deps, makeEvent("hello"), ctx);
275
- expect(result).toEqual({});
276
- // activeSkillEntries was not assigned by the handler (early return)
277
- expect(deps.session.activeSkillEntries).toEqual([]);
278
- });
279
247
  });
@@ -8,7 +8,8 @@ import { handleInput } from "../../src/handlers/input";
8
8
  import type { HandlerDeps } from "../../src/handlers/types";
9
9
  import type { PermissionDecisionEvent } from "../../src/permission-events";
10
10
  import { PERMISSIONS_DECISION_CHANNEL } from "../../src/permission-events";
11
- import type { SessionState } from "../../src/runtime";
11
+ import type { PermissionSession } from "../../src/permission-session";
12
+ import type { PermissionState } from "../../src/types";
12
13
 
13
14
  // ── helpers ────────────────────────────────────────────────────────────────
14
15
 
@@ -38,28 +39,24 @@ function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
38
39
  } as unknown as ExtensionContext;
39
40
  }
40
41
 
41
- function makeSession(state: "allow" | "deny" | "ask" = "allow"): SessionState {
42
+ function makeSession(
43
+ state: "allow" | "deny" | "ask" = "allow",
44
+ ): PermissionSession {
42
45
  return {
43
- runtimeContext: null,
44
- permissionManager: {
45
- checkPermission: vi.fn().mockReturnValue({
46
- state,
47
- toolName: "skill",
48
- source: "skill",
49
- origin: "global",
50
- matchedPattern: "*",
51
- }),
52
- } as unknown as SessionState["permissionManager"],
53
- activeSkillEntries: [],
54
- lastKnownActiveAgentName: null,
55
- lastActiveToolsCacheKey: null,
56
- lastPromptStateCacheKey: null,
57
- sessionRules: {
58
- approve: vi.fn(),
59
- getRuleset: vi.fn().mockReturnValue([]),
60
- clear: vi.fn(),
61
- } as unknown as SessionState["sessionRules"],
62
- };
46
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
47
+ activate: vi.fn(),
48
+ resolveAgentName: vi.fn().mockReturnValue(null),
49
+ checkPermission: vi.fn().mockReturnValue({
50
+ state,
51
+ toolName: "skill",
52
+ source: "skill",
53
+ origin: "global",
54
+ matchedPattern: "*",
55
+ }),
56
+ getToolPermission: vi.fn().mockReturnValue("allow" as PermissionState),
57
+ getSessionRuleset: vi.fn().mockReturnValue([]),
58
+ approveSessionRule: vi.fn(),
59
+ } as unknown as PermissionSession;
63
60
  }
64
61
 
65
62
  function makeDeps(
@@ -68,21 +65,12 @@ function makeDeps(
68
65
  ): HandlerDeps {
69
66
  return {
70
67
  session: makeSession(state),
71
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
72
- piInfrastructureDirs: ["/test/agent"],
73
- getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
74
68
  events: makeEvents(),
75
- createPermissionManagerForCwd: vi.fn(),
76
- refreshExtensionConfig: vi.fn(),
77
- logResolvedConfigPaths: vi.fn(),
78
- resolveAgentName: vi.fn().mockReturnValue(null),
79
69
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
80
70
  promptPermission: vi
81
71
  .fn()
82
72
  .mockResolvedValue({ approved: true, state: "approved" }),
83
73
  createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
84
- startForwardedPermissionPolling: vi.fn(),
85
- stopForwardedPermissionPolling: vi.fn(),
86
74
  stopPermissionRpcHandlers: vi.fn(),
87
75
  getAllTools: vi.fn().mockReturnValue([]),
88
76
  setActiveTools: vi.fn(),
@@ -192,7 +180,6 @@ describe("handleInput decision events — skill gate", () => {
192
180
 
193
181
  it("emits allow with auto_approved when promptPermission returns autoApproved:true", async () => {
194
182
  const deps = makeDeps("ask", {
195
- // Simulate what PermissionPrompter returns in yolo mode
196
183
  promptPermission: vi.fn().mockResolvedValue({
197
184
  approved: true,
198
185
  state: "approved",
@@ -6,8 +6,8 @@ import {
6
6
  handleInput,
7
7
  } from "../../src/handlers/input";
8
8
  import type { HandlerDeps } from "../../src/handlers/types";
9
- import type { SessionState } from "../../src/runtime";
10
- import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
9
+ import type { PermissionSession } from "../../src/permission-session";
10
+ import type { PermissionState } from "../../src/types";
11
11
 
12
12
  // ── helpers ────────────────────────────────────────────────────────────────
13
13
 
@@ -34,43 +34,30 @@ function makeInputEvent(text: string) {
34
34
  return { text };
35
35
  }
36
36
 
37
- function makeSession(overrides: Partial<SessionState> = {}): SessionState {
37
+ function makeSession(
38
+ overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
39
+ ): PermissionSession {
38
40
  return {
39
- runtimeContext: null,
40
- permissionManager: {
41
- checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
42
- } as unknown as SessionState["permissionManager"],
43
- activeSkillEntries: [] as SkillPromptEntry[],
44
- lastKnownActiveAgentName: null,
45
- lastActiveToolsCacheKey: null,
46
- lastPromptStateCacheKey: null,
47
- sessionRules: {
48
- approve: vi.fn(),
49
- getRuleset: vi.fn().mockReturnValue([]),
50
- clear: vi.fn(),
51
- } as unknown as SessionState["sessionRules"],
41
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
42
+ activate: vi.fn(),
43
+ resolveAgentName: vi.fn().mockReturnValue(null),
44
+ checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
45
+ getToolPermission: vi.fn().mockReturnValue("allow" as PermissionState),
46
+ getSessionRuleset: vi.fn().mockReturnValue([]),
47
+ approveSessionRule: vi.fn(),
52
48
  ...overrides,
53
- };
49
+ } as unknown as PermissionSession;
54
50
  }
55
51
 
56
52
  function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
57
53
  return {
58
54
  session: makeSession(),
59
- logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
60
- piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
61
- getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
62
- createPermissionManagerForCwd: vi.fn(),
63
- refreshExtensionConfig: vi.fn(),
64
- logResolvedConfigPaths: vi.fn(),
65
- resolveAgentName: vi.fn().mockReturnValue(null),
55
+ events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
66
56
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
67
57
  promptPermission: vi
68
58
  .fn()
69
59
  .mockResolvedValue({ approved: true, state: "approved" }),
70
60
  createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
71
- events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
72
- startForwardedPermissionPolling: vi.fn(),
73
- stopForwardedPermissionPolling: vi.fn(),
74
61
  stopPermissionRpcHandlers: vi.fn(),
75
62
  getAllTools: vi.fn().mockReturnValue([]),
76
63
  setActiveTools: vi.fn(),
@@ -115,18 +102,11 @@ describe("extractSkillNameFromInput", () => {
115
102
  // ── handleInput ───────────────────────────────────────────────────────────
116
103
 
117
104
  describe("handleInput", () => {
118
- it("sets runtime context", async () => {
105
+ it("activates session with ctx", async () => {
119
106
  const ctx = makeCtx();
120
107
  const deps = makeDeps();
121
108
  await handleInput(deps, makeInputEvent("hello"), ctx);
122
- expect(deps.session.runtimeContext).toBe(ctx);
123
- });
124
-
125
- it("starts forwarded permission polling", async () => {
126
- const ctx = makeCtx();
127
- const deps = makeDeps();
128
- await handleInput(deps, makeInputEvent("hello"), ctx);
129
- expect(deps.startForwardedPermissionPolling).toHaveBeenCalledWith(ctx);
109
+ expect(deps.session.activate).toHaveBeenCalledWith(ctx);
130
110
  });
131
111
 
132
112
  it("returns continue for non-skill input", async () => {
@@ -142,14 +122,11 @@ describe("handleInput", () => {
142
122
  it("does not check permissions for non-skill input", async () => {
143
123
  const deps = makeDeps();
144
124
  await handleInput(deps, makeInputEvent("just a message"), makeCtx());
145
- expect(
146
- deps.session.permissionManager.checkPermission,
147
- ).not.toHaveBeenCalled();
125
+ expect(deps.session.checkPermission).not.toHaveBeenCalled();
148
126
  });
149
127
 
150
128
  it("returns continue when skill is allowed", async () => {
151
129
  const deps = makeDeps();
152
- // default makeRuntime() has checkPermission → { state: "allow" }
153
130
  const result = await handleInput(
154
131
  deps,
155
132
  makeInputEvent("/skill:librarian"),
@@ -159,14 +136,10 @@ describe("handleInput", () => {
159
136
  });
160
137
 
161
138
  it("returns handled when skill is denied", async () => {
162
- const pm = {
139
+ const session = makeSession({
163
140
  checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
164
- };
165
- const deps = makeDeps({
166
- session: makeSession({
167
- permissionManager: pm as unknown as SessionState["permissionManager"],
168
- }),
169
141
  });
142
+ const deps = makeDeps({ session });
170
143
  const result = await handleInput(
171
144
  deps,
172
145
  makeInputEvent("/skill:librarian"),
@@ -177,14 +150,10 @@ describe("handleInput", () => {
177
150
 
178
151
  it("shows a warning notification when skill is denied and UI is available", async () => {
179
152
  const ctx = makeCtx({ hasUI: true });
180
- const pm = {
153
+ const session = makeSession({
181
154
  checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
182
- };
183
- const deps = makeDeps({
184
- session: makeSession({
185
- permissionManager: pm as unknown as SessionState["permissionManager"],
186
- }),
187
155
  });
156
+ const deps = makeDeps({ session });
188
157
  await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
189
158
  expect(ctx.ui.notify).toHaveBeenCalledWith(
190
159
  expect.stringContaining("librarian"),
@@ -194,22 +163,20 @@ describe("handleInput", () => {
194
163
 
195
164
  it("does not show a warning notification when skill is denied and UI is absent", async () => {
196
165
  const ctx = makeCtx({ hasUI: false });
197
- const pm = { checkPermission: vi.fn().mockReturnValue({ state: "deny" }) };
198
- const deps = makeDeps({
199
- session: makeSession({
200
- permissionManager: pm as unknown as SessionState["permissionManager"],
201
- }),
166
+ const session = makeSession({
167
+ checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
202
168
  });
169
+ const deps = makeDeps({ session });
203
170
  await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
204
171
  expect(ctx.ui.notify).not.toHaveBeenCalled();
205
172
  });
206
173
 
207
174
  it("returns handled when skill requires approval but no UI is available", async () => {
208
- const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
175
+ const session = makeSession({
176
+ checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
177
+ });
209
178
  const deps = makeDeps({
210
- session: makeSession({
211
- permissionManager: pm as unknown as SessionState["permissionManager"],
212
- }),
179
+ session,
213
180
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
214
181
  });
215
182
  const result = await handleInput(
@@ -221,11 +188,11 @@ describe("handleInput", () => {
221
188
  });
222
189
 
223
190
  it("prompts and returns continue when skill ask is approved", async () => {
224
- const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
191
+ const session = makeSession({
192
+ checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
193
+ });
225
194
  const deps = makeDeps({
226
- session: makeSession({
227
- permissionManager: pm as unknown as SessionState["permissionManager"],
228
- }),
195
+ session,
229
196
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
230
197
  promptPermission: vi
231
198
  .fn()
@@ -241,11 +208,11 @@ describe("handleInput", () => {
241
208
  });
242
209
 
243
210
  it("returns handled when skill ask is denied by user", async () => {
244
- const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
211
+ const session = makeSession({
212
+ checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
213
+ });
245
214
  const deps = makeDeps({
246
- session: makeSession({
247
- permissionManager: pm as unknown as SessionState["permissionManager"],
248
- }),
215
+ session,
249
216
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
250
217
  promptPermission: vi
251
218
  .fn()
@@ -260,12 +227,12 @@ describe("handleInput", () => {
260
227
  });
261
228
 
262
229
  it("passes agentName in the prompt permission request", async () => {
263
- const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
264
- const deps = makeDeps({
265
- session: makeSession({
266
- permissionManager: pm as unknown as SessionState["permissionManager"],
267
- }),
230
+ const session = makeSession({
231
+ checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
268
232
  resolveAgentName: vi.fn().mockReturnValue("code-agent"),
233
+ });
234
+ const deps = makeDeps({
235
+ session,
269
236
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
270
237
  promptPermission: vi
271
238
  .fn()