@gotgenes/pi-permission-system 3.7.0 → 3.8.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.
@@ -7,6 +7,7 @@ import {
7
7
  } from "../../src/handlers/before-agent-start";
8
8
  import type { HandlerDeps } from "../../src/handlers/types";
9
9
  import type { PermissionManager } from "../../src/permission-manager";
10
+ import type { ExtensionRuntime } from "../../src/runtime";
10
11
  import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
11
12
 
12
13
  // ── SDK stubs ──────────────────────────────────────────────────────────────
@@ -56,27 +57,41 @@ function makePm(
56
57
  } as unknown as PermissionManager;
57
58
  }
58
59
 
59
- function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
60
- const pm = makePm();
60
+ function makeRuntime(
61
+ overrides: Partial<ExtensionRuntime> = {},
62
+ ): ExtensionRuntime {
61
63
  return {
62
- getPermissionManager: vi.fn().mockReturnValue(pm),
63
- setPermissionManager: vi.fn(),
64
- getRuntimeContext: vi.fn().mockReturnValue(null),
65
- setRuntimeContext: vi.fn(),
66
- getActiveSkillEntries: vi.fn().mockReturnValue([] as SkillPromptEntry[]),
67
- setActiveSkillEntries: vi.fn(),
68
- getLastKnownActiveAgentName: vi.fn().mockReturnValue(null),
69
- setLastKnownActiveAgentName: vi.fn(),
70
- getLastActiveToolsCacheKey: vi.fn().mockReturnValue(null),
71
- setLastActiveToolsCacheKey: vi.fn(),
72
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
73
- setLastPromptStateCacheKey: vi.fn(),
64
+ agentDir: "/test/agent",
65
+ sessionsDir: "/test/agent/sessions",
66
+ subagentSessionsDir: "/test/agent/subagent-sessions",
67
+ forwardingDir: "/test/agent/sessions/permission-forwarding",
68
+ globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
69
+ config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
70
+ runtimeContext: null,
71
+ permissionManager: makePm() as unknown as PermissionManager,
72
+ activeSkillEntries: [] as SkillPromptEntry[],
73
+ lastKnownActiveAgentName: null,
74
+ lastActiveToolsCacheKey: null,
75
+ lastPromptStateCacheKey: null,
76
+ lastConfigWarning: null,
74
77
  sessionApprovalCache: {
75
78
  approve: vi.fn(),
76
79
  has: vi.fn(),
77
80
  findMatchingPrefix: vi.fn(),
78
81
  clear: vi.fn(),
79
- } as unknown as HandlerDeps["sessionApprovalCache"],
82
+ } as unknown as ExtensionRuntime["sessionApprovalCache"],
83
+ permissionForwardingContext: null,
84
+ permissionForwardingTimer: null,
85
+ isProcessingForwardedRequests: false,
86
+ writeDebugLog: vi.fn(),
87
+ writeReviewLog: vi.fn(),
88
+ ...overrides,
89
+ } as ExtensionRuntime;
90
+ }
91
+
92
+ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
93
+ return {
94
+ runtime: makeRuntime(),
80
95
  createPermissionManagerForCwd: vi.fn().mockReturnValue(makePm()),
81
96
  refreshExtensionConfig: vi.fn(),
82
97
  notifyWarning: vi.fn(),
@@ -89,8 +104,6 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
89
104
  createPermissionRequestId: vi.fn().mockReturnValue("test-id"),
90
105
  startForwardedPermissionPolling: vi.fn(),
91
106
  stopForwardedPermissionPolling: vi.fn(),
92
- writeReviewLog: vi.fn(),
93
- writeDebugLog: vi.fn(),
94
107
  getAllTools: vi.fn().mockReturnValue([]),
95
108
  setActiveTools: vi.fn(),
96
109
  ...overrides,
@@ -162,7 +175,9 @@ describe("handleBeforeAgentStart", () => {
162
175
  it("filters out denied tools from allowed list", async () => {
163
176
  const pm = makePm("deny");
164
177
  const deps = makeDeps({
165
- getPermissionManager: vi.fn().mockReturnValue(pm),
178
+ runtime: makeRuntime({
179
+ permissionManager: pm as unknown as PermissionManager,
180
+ }),
166
181
  getAllTools: vi
167
182
  .fn()
168
183
  .mockReturnValue([{ name: "write" }, { name: "read" }]),
@@ -175,7 +190,9 @@ describe("handleBeforeAgentStart", () => {
175
190
  it("includes allowed and ask tools in the active list", async () => {
176
191
  const pm = makePm("allow");
177
192
  const deps = makeDeps({
178
- getPermissionManager: vi.fn().mockReturnValue(pm),
193
+ runtime: makeRuntime({
194
+ permissionManager: pm as unknown as PermissionManager,
195
+ }),
179
196
  getAllTools: vi
180
197
  .fn()
181
198
  .mockReturnValue([{ name: "read" }, { name: "write" }]),
@@ -187,10 +204,9 @@ describe("handleBeforeAgentStart", () => {
187
204
  it("updates the active-tools cache key after applying", async () => {
188
205
  const deps = makeDeps({
189
206
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
190
- getLastActiveToolsCacheKey: vi.fn().mockReturnValue(null),
191
207
  });
192
208
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
193
- expect(deps.setLastActiveToolsCacheKey).toHaveBeenCalledOnce();
209
+ expect(deps.runtime.lastActiveToolsCacheKey).not.toBeNull();
194
210
  });
195
211
 
196
212
  it("skips setActiveTools when cache key is unchanged", async () => {
@@ -200,8 +216,8 @@ describe("handleBeforeAgentStart", () => {
200
216
  );
201
217
  const key = createActiveToolsCacheKey(["read"]);
202
218
  const deps = makeDeps({
219
+ runtime: makeRuntime({ lastActiveToolsCacheKey: key }),
203
220
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
204
- getLastActiveToolsCacheKey: vi.fn().mockReturnValue(key),
205
221
  });
206
222
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
207
223
  expect(deps.setActiveTools).not.toHaveBeenCalled();
@@ -213,7 +229,6 @@ describe("handleBeforeAgentStart", () => {
213
229
  const systemPrompt = `You are an assistant.\n\nAvailable tools:\n- read\n- write\n`;
214
230
  const deps = makeDeps({
215
231
  getAllTools: vi.fn().mockReturnValue([]),
216
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
217
232
  });
218
233
  const result = await handleBeforeAgentStart(
219
234
  deps,
@@ -222,14 +237,13 @@ describe("handleBeforeAgentStart", () => {
222
237
  );
223
238
  // The prompt was modified, so systemPrompt should be returned
224
239
  expect(result).toHaveProperty("systemPrompt");
225
- expect(deps.setLastPromptStateCacheKey).toHaveBeenCalledOnce();
240
+ expect(deps.runtime.lastPromptStateCacheKey).not.toBeNull();
226
241
  });
227
242
 
228
243
  it("returns empty object when systemPrompt is unchanged", async () => {
229
244
  const prompt = "No tools section here.";
230
245
  const deps = makeDeps({
231
246
  getAllTools: vi.fn().mockReturnValue([]),
232
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
233
247
  });
234
248
  const result = await handleBeforeAgentStart(
235
249
  deps,
@@ -242,10 +256,9 @@ describe("handleBeforeAgentStart", () => {
242
256
  it("stores resolved skill entries on deps", async () => {
243
257
  const deps = makeDeps({
244
258
  getAllTools: vi.fn().mockReturnValue([]),
245
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
246
259
  });
247
260
  await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
248
- expect(deps.setActiveSkillEntries).toHaveBeenCalledOnce();
261
+ expect(deps.runtime.activeSkillEntries).toEqual(expect.any(Array));
249
262
  });
250
263
 
251
264
  it("returns empty object and skips prompt work when prompt cache key is unchanged", async () => {
@@ -263,12 +276,15 @@ describe("handleBeforeAgentStart", () => {
263
276
  allowedToolNames: allowedTools,
264
277
  });
265
278
  const deps = makeDeps({
266
- getPermissionManager: vi.fn().mockReturnValue(pm),
279
+ runtime: makeRuntime({
280
+ permissionManager: pm as unknown as PermissionManager,
281
+ lastPromptStateCacheKey: key,
282
+ }),
267
283
  getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
268
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(key),
269
284
  });
270
285
  const result = await handleBeforeAgentStart(deps, makeEvent("hello"), ctx);
271
286
  expect(result).toEqual({});
272
- expect(deps.setActiveSkillEntries).not.toHaveBeenCalled();
287
+ // activeSkillEntries was not assigned by the handler (early return)
288
+ expect(deps.runtime.activeSkillEntries).toEqual([]);
273
289
  });
274
290
  });
@@ -6,6 +6,7 @@ import {
6
6
  handleInput,
7
7
  } from "../../src/handlers/input";
8
8
  import type { HandlerDeps } from "../../src/handlers/types";
9
+ import type { ExtensionRuntime } from "../../src/runtime";
9
10
  import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
10
11
 
11
12
  // ── helpers ────────────────────────────────────────────────────────────────
@@ -33,29 +34,43 @@ function makeInputEvent(text: string) {
33
34
  return { text };
34
35
  }
35
36
 
36
- function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
37
+ function makeRuntime(
38
+ overrides: Partial<ExtensionRuntime> = {},
39
+ ): ExtensionRuntime {
37
40
  return {
38
- getPermissionManager: vi.fn().mockReturnValue({
41
+ agentDir: "/test/agent",
42
+ sessionsDir: "/test/agent/sessions",
43
+ subagentSessionsDir: "/test/agent/subagent-sessions",
44
+ forwardingDir: "/test/agent/sessions/permission-forwarding",
45
+ globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
46
+ config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
47
+ runtimeContext: null,
48
+ permissionManager: {
39
49
  checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
40
- getConfigIssues: vi.fn().mockReturnValue([]),
41
- }),
42
- setPermissionManager: vi.fn(),
43
- getRuntimeContext: vi.fn().mockReturnValue(null),
44
- setRuntimeContext: vi.fn(),
45
- getActiveSkillEntries: vi.fn().mockReturnValue([] as SkillPromptEntry[]),
46
- setActiveSkillEntries: vi.fn(),
47
- getLastKnownActiveAgentName: vi.fn().mockReturnValue(null),
48
- setLastKnownActiveAgentName: vi.fn(),
49
- getLastActiveToolsCacheKey: vi.fn().mockReturnValue(null),
50
- setLastActiveToolsCacheKey: vi.fn(),
51
- getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
52
- setLastPromptStateCacheKey: vi.fn(),
50
+ } as unknown as ExtensionRuntime["permissionManager"],
51
+ activeSkillEntries: [] as SkillPromptEntry[],
52
+ lastKnownActiveAgentName: null,
53
+ lastActiveToolsCacheKey: null,
54
+ lastPromptStateCacheKey: null,
55
+ lastConfigWarning: null,
53
56
  sessionApprovalCache: {
54
57
  approve: vi.fn(),
55
58
  has: vi.fn(),
56
59
  findMatchingPrefix: vi.fn(),
57
60
  clear: vi.fn(),
58
- } as unknown as HandlerDeps["sessionApprovalCache"],
61
+ } as unknown as ExtensionRuntime["sessionApprovalCache"],
62
+ permissionForwardingContext: null,
63
+ permissionForwardingTimer: null,
64
+ isProcessingForwardedRequests: false,
65
+ writeDebugLog: vi.fn(),
66
+ writeReviewLog: vi.fn(),
67
+ ...overrides,
68
+ } as ExtensionRuntime;
69
+ }
70
+
71
+ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
72
+ return {
73
+ runtime: makeRuntime(),
59
74
  createPermissionManagerForCwd: vi.fn(),
60
75
  refreshExtensionConfig: vi.fn(),
61
76
  notifyWarning: vi.fn(),
@@ -68,8 +83,6 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
68
83
  createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
69
84
  startForwardedPermissionPolling: vi.fn(),
70
85
  stopForwardedPermissionPolling: vi.fn(),
71
- writeReviewLog: vi.fn(),
72
- writeDebugLog: vi.fn(),
73
86
  getAllTools: vi.fn().mockReturnValue([]),
74
87
  setActiveTools: vi.fn(),
75
88
  ...overrides,
@@ -117,7 +130,7 @@ describe("handleInput", () => {
117
130
  const ctx = makeCtx();
118
131
  const deps = makeDeps();
119
132
  await handleInput(deps, makeInputEvent("hello"), ctx);
120
- expect(deps.setRuntimeContext).toHaveBeenCalledWith(ctx);
133
+ expect(deps.runtime.runtimeContext).toBe(ctx);
121
134
  });
122
135
 
123
136
  it("starts forwarded permission polling", async () => {
@@ -140,15 +153,14 @@ describe("handleInput", () => {
140
153
  it("does not check permissions for non-skill input", async () => {
141
154
  const deps = makeDeps();
142
155
  await handleInput(deps, makeInputEvent("just a message"), makeCtx());
143
- expect(deps.getPermissionManager().checkPermission).not.toHaveBeenCalled();
156
+ expect(
157
+ deps.runtime.permissionManager.checkPermission,
158
+ ).not.toHaveBeenCalled();
144
159
  });
145
160
 
146
161
  it("returns continue when skill is allowed", async () => {
147
- const deps = makeDeps({
148
- getPermissionManager: vi.fn().mockReturnValue({
149
- checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
150
- }),
151
- });
162
+ const deps = makeDeps();
163
+ // default makeRuntime() has checkPermission → { state: "allow" }
152
164
  const result = await handleInput(
153
165
  deps,
154
166
  makeInputEvent("/skill:librarian"),
@@ -158,9 +170,13 @@ describe("handleInput", () => {
158
170
  });
159
171
 
160
172
  it("returns handled when skill is denied", async () => {
173
+ const pm = {
174
+ checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
175
+ };
161
176
  const deps = makeDeps({
162
- getPermissionManager: vi.fn().mockReturnValue({
163
- checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
177
+ runtime: makeRuntime({
178
+ permissionManager:
179
+ pm as unknown as ExtensionRuntime["permissionManager"],
164
180
  }),
165
181
  });
166
182
  const result = await handleInput(
@@ -173,9 +189,13 @@ describe("handleInput", () => {
173
189
 
174
190
  it("shows a warning notification when skill is denied and UI is available", async () => {
175
191
  const ctx = makeCtx({ hasUI: true });
192
+ const pm = {
193
+ checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
194
+ };
176
195
  const deps = makeDeps({
177
- getPermissionManager: vi.fn().mockReturnValue({
178
- checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
196
+ runtime: makeRuntime({
197
+ permissionManager:
198
+ pm as unknown as ExtensionRuntime["permissionManager"],
179
199
  }),
180
200
  });
181
201
  await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
@@ -187,9 +207,11 @@ describe("handleInput", () => {
187
207
 
188
208
  it("does not show a warning notification when skill is denied and UI is absent", async () => {
189
209
  const ctx = makeCtx({ hasUI: false });
210
+ const pm = { checkPermission: vi.fn().mockReturnValue({ state: "deny" }) };
190
211
  const deps = makeDeps({
191
- getPermissionManager: vi.fn().mockReturnValue({
192
- checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
212
+ runtime: makeRuntime({
213
+ permissionManager:
214
+ pm as unknown as ExtensionRuntime["permissionManager"],
193
215
  }),
194
216
  });
195
217
  await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
@@ -197,9 +219,11 @@ describe("handleInput", () => {
197
219
  });
198
220
 
199
221
  it("returns handled when skill requires approval but no UI is available", async () => {
222
+ const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
200
223
  const deps = makeDeps({
201
- getPermissionManager: vi.fn().mockReturnValue({
202
- checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
224
+ runtime: makeRuntime({
225
+ permissionManager:
226
+ pm as unknown as ExtensionRuntime["permissionManager"],
203
227
  }),
204
228
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
205
229
  });
@@ -212,9 +236,11 @@ describe("handleInput", () => {
212
236
  });
213
237
 
214
238
  it("prompts and returns continue when skill ask is approved", async () => {
239
+ const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
215
240
  const deps = makeDeps({
216
- getPermissionManager: vi.fn().mockReturnValue({
217
- checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
241
+ runtime: makeRuntime({
242
+ permissionManager:
243
+ pm as unknown as ExtensionRuntime["permissionManager"],
218
244
  }),
219
245
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
220
246
  promptPermission: vi
@@ -231,9 +257,11 @@ describe("handleInput", () => {
231
257
  });
232
258
 
233
259
  it("returns handled when skill ask is denied by user", async () => {
260
+ const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
234
261
  const deps = makeDeps({
235
- getPermissionManager: vi.fn().mockReturnValue({
236
- checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
262
+ runtime: makeRuntime({
263
+ permissionManager:
264
+ pm as unknown as ExtensionRuntime["permissionManager"],
237
265
  }),
238
266
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
239
267
  promptPermission: vi
@@ -249,9 +277,11 @@ describe("handleInput", () => {
249
277
  });
250
278
 
251
279
  it("passes agentName in the prompt permission request", async () => {
280
+ const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
252
281
  const deps = makeDeps({
253
- getPermissionManager: vi.fn().mockReturnValue({
254
- checkPermission: vi.fn().mockReturnValue({ state: "ask" }),
282
+ runtime: makeRuntime({
283
+ permissionManager:
284
+ pm as unknown as ExtensionRuntime["permissionManager"],
255
285
  }),
256
286
  resolveAgentName: vi.fn().mockReturnValue("code-agent"),
257
287
  canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),