@gotgenes/pi-permission-system 10.0.0 → 10.2.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.
Files changed (68) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/src/agent-prep-session.ts +28 -0
  5. package/src/decision-reporter.ts +41 -0
  6. package/src/denial-messages.ts +11 -0
  7. package/src/forwarded-permissions/permission-forwarder.ts +549 -0
  8. package/src/forwarding-manager.ts +3 -7
  9. package/src/gate-handler-session.ts +13 -0
  10. package/src/gate-prompter.ts +14 -0
  11. package/src/handlers/before-agent-start.ts +2 -3
  12. package/src/handlers/gates/bash-command.ts +4 -18
  13. package/src/handlers/gates/bash-external-directory.ts +3 -15
  14. package/src/handlers/gates/bash-path.ts +3 -16
  15. package/src/handlers/gates/descriptor.ts +0 -28
  16. package/src/handlers/gates/path.ts +3 -15
  17. package/src/handlers/gates/runner.ts +142 -105
  18. package/src/handlers/gates/skill-input-gate-pipeline.ts +104 -0
  19. package/src/handlers/gates/skill-input.ts +44 -0
  20. package/src/handlers/gates/tool-call-gate-pipeline.ts +120 -0
  21. package/src/handlers/lifecycle.ts +9 -9
  22. package/src/handlers/permission-gate-handler.ts +34 -238
  23. package/src/index.ts +53 -69
  24. package/src/mcp-targets.ts +56 -46
  25. package/src/permission-manager.ts +69 -3
  26. package/src/permission-prompter.ts +7 -58
  27. package/src/permission-resolver.ts +17 -0
  28. package/src/permission-session.ts +83 -27
  29. package/src/permissions-service.ts +53 -0
  30. package/src/runtime.ts +1 -37
  31. package/src/service-lifecycle.ts +49 -0
  32. package/src/session-approval-recorder.ts +6 -0
  33. package/src/session-lifecycle-session.ts +24 -0
  34. package/src/tool-input-preview.ts +0 -62
  35. package/src/tool-input-prompt-formatters.ts +63 -0
  36. package/src/tool-preview-formatter.ts +6 -4
  37. package/test/decision-reporter.test.ts +112 -0
  38. package/test/denial-messages.test.ts +62 -0
  39. package/test/forwarding-manager.test.ts +26 -44
  40. package/test/handlers/before-agent-start.test.ts +45 -21
  41. package/test/handlers/external-directory-integration.test.ts +83 -114
  42. package/test/handlers/external-directory-session-dedup.test.ts +102 -55
  43. package/test/handlers/gates/bash-command.test.ts +49 -90
  44. package/test/handlers/gates/bash-external-directory.test.ts +54 -95
  45. package/test/handlers/gates/bash-path.test.ts +54 -157
  46. package/test/handlers/gates/path.test.ts +38 -105
  47. package/test/handlers/gates/runner.test.ts +151 -186
  48. package/test/handlers/gates/skill-input-gate-pipeline.test.ts +176 -0
  49. package/test/handlers/gates/skill-input.test.ts +128 -0
  50. package/test/handlers/gates/tool-call-gate-pipeline.test.ts +180 -0
  51. package/test/handlers/input.test.ts +1 -2
  52. package/test/handlers/lifecycle.test.ts +49 -33
  53. package/test/handlers/tool-call-events.test.ts +1 -1
  54. package/test/handlers/tool-call.test.ts +44 -153
  55. package/test/helpers/gate-fixtures.ts +212 -17
  56. package/test/helpers/handler-fixtures.ts +226 -29
  57. package/test/mcp-targets.test.ts +55 -0
  58. package/test/permission-forwarder.test.ts +295 -0
  59. package/test/permission-forwarding.test.ts +0 -282
  60. package/test/permission-manager-unified.test.ts +159 -1
  61. package/test/permission-prompter.test.ts +33 -44
  62. package/test/permission-session.test.ts +211 -105
  63. package/test/permissions-service.test.ts +151 -0
  64. package/test/runtime.test.ts +2 -86
  65. package/test/service-lifecycle.test.ts +162 -0
  66. package/test/tool-input-preview.test.ts +0 -111
  67. package/test/tool-input-prompt-formatters.test.ts +115 -0
  68. package/src/forwarded-permissions/polling.ts +0 -411
@@ -1,17 +1,10 @@
1
1
  import { beforeEach, describe, expect, it, vi } from "vitest";
2
2
 
3
- // ── Module mocks ────────────────────────────────────────────────────────────
3
+ // ── Injected mock ───────────────────────────────────────────────────────────
4
4
 
5
- const { mockConfirmPermission } = vi.hoisted(() => ({
6
- mockConfirmPermission: vi.fn(),
7
- }));
5
+ const mockRequestApproval = vi.fn();
8
6
 
9
- vi.mock("../src/forwarded-permissions/polling", () => ({
10
- confirmPermission: mockConfirmPermission,
11
- processForwardedPermissionRequests: vi.fn().mockResolvedValue(undefined),
12
- }));
13
-
14
- // ── Imports (after mocks) ───────────────────────────────────────────────────
7
+ // ── Imports ─────────────────────────────────────────────────────────────────
15
8
 
16
9
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
17
10
  import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
@@ -51,12 +44,8 @@ function makeDeps(
51
44
  return {
52
45
  getConfig: () => ({ ...DEFAULT_EXTENSION_CONFIG, yoloMode: false }),
53
46
  writeReviewLog: vi.fn(),
54
- subagentSessionsDir: "/sessions/subagents",
55
- forwardingDir: "/sessions/permission-forwarding",
56
47
  events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
57
- requestPermissionDecisionFromUi: vi
58
- .fn()
59
- .mockResolvedValue({ approved: true, state: "approved" }),
48
+ forwarder: { requestApproval: mockRequestApproval },
60
49
  ...overrides,
61
50
  };
62
51
  }
@@ -65,7 +54,11 @@ function makeDeps(
65
54
 
66
55
  describe("PermissionPrompter", () => {
67
56
  beforeEach(() => {
68
- mockConfirmPermission.mockReset();
57
+ mockRequestApproval.mockReset();
58
+ mockRequestApproval.mockResolvedValue({
59
+ approved: true,
60
+ state: "approved",
61
+ });
69
62
  });
70
63
 
71
64
  // ── Yolo-mode auto-approve ───────────────────────────────────────────────
@@ -89,7 +82,7 @@ describe("PermissionPrompter", () => {
89
82
  state: "approved",
90
83
  autoApproved: true,
91
84
  });
92
- expect(mockConfirmPermission).not.toHaveBeenCalled();
85
+ expect(mockRequestApproval).not.toHaveBeenCalled();
93
86
  expect(events.emit).not.toHaveBeenCalledWith(
94
87
  "permissions:ui_prompt",
95
88
  expect.anything(),
@@ -136,7 +129,7 @@ describe("PermissionPrompter", () => {
136
129
 
137
130
  await prompter.prompt(makeCtx(true), makeDetails());
138
131
 
139
- expect(mockConfirmPermission).not.toHaveBeenCalled();
132
+ expect(mockRequestApproval).not.toHaveBeenCalled();
140
133
  });
141
134
  });
142
135
 
@@ -149,7 +142,7 @@ describe("PermissionPrompter", () => {
149
142
  approved: true,
150
143
  state: "approved",
151
144
  };
152
- mockConfirmPermission.mockResolvedValue(approved);
145
+ mockRequestApproval.mockResolvedValue(approved);
153
146
  const deps = makeDeps({ writeReviewLog });
154
147
  const prompter = new PermissionPrompter(deps);
155
148
 
@@ -169,7 +162,7 @@ describe("PermissionPrompter", () => {
169
162
  emit: vi.fn(),
170
163
  on: vi.fn().mockReturnValue(() => undefined),
171
164
  };
172
- mockConfirmPermission.mockResolvedValue({
165
+ mockRequestApproval.mockResolvedValue({
173
166
  approved: true,
174
167
  state: "approved",
175
168
  });
@@ -201,7 +194,7 @@ describe("PermissionPrompter", () => {
201
194
  emit: vi.fn(),
202
195
  on: vi.fn().mockReturnValue(() => undefined),
203
196
  };
204
- mockConfirmPermission.mockResolvedValue({
197
+ mockRequestApproval.mockResolvedValue({
205
198
  approved: true,
206
199
  state: "approved",
207
200
  });
@@ -233,7 +226,7 @@ describe("PermissionPrompter", () => {
233
226
  emit: vi.fn(),
234
227
  on: vi.fn().mockReturnValue(() => undefined),
235
228
  };
236
- mockConfirmPermission.mockResolvedValue({
229
+ mockRequestApproval.mockResolvedValue({
237
230
  approved: true,
238
231
  state: "approved",
239
232
  });
@@ -250,7 +243,7 @@ describe("PermissionPrompter", () => {
250
243
 
251
244
  it("logs permission_request.approved when confirmPermission returns approved", async () => {
252
245
  const writeReviewLog = vi.fn();
253
- mockConfirmPermission.mockResolvedValue({
246
+ mockRequestApproval.mockResolvedValue({
254
247
  approved: true,
255
248
  state: "approved",
256
249
  });
@@ -270,7 +263,7 @@ describe("PermissionPrompter", () => {
270
263
 
271
264
  it("logs permission_request.denied when confirmPermission returns denied", async () => {
272
265
  const writeReviewLog = vi.fn();
273
- mockConfirmPermission.mockResolvedValue({
266
+ mockRequestApproval.mockResolvedValue({
274
267
  approved: false,
275
268
  state: "denied",
276
269
  });
@@ -290,7 +283,7 @@ describe("PermissionPrompter", () => {
290
283
 
291
284
  it("logs permission_request.denied with denialReason when present", async () => {
292
285
  const writeReviewLog = vi.fn();
293
- mockConfirmPermission.mockResolvedValue({
286
+ mockRequestApproval.mockResolvedValue({
294
287
  approved: false,
295
288
  state: "denied_with_reason",
296
289
  denialReason: "too sensitive",
@@ -314,7 +307,7 @@ describe("PermissionPrompter", () => {
314
307
  state: "denied_with_reason",
315
308
  denialReason: "sensitive",
316
309
  };
317
- mockConfirmPermission.mockResolvedValue(decision);
310
+ mockRequestApproval.mockResolvedValue(decision);
318
311
  const deps = makeDeps();
319
312
  const prompter = new PermissionPrompter(deps);
320
313
 
@@ -324,7 +317,7 @@ describe("PermissionPrompter", () => {
324
317
  });
325
318
 
326
319
  it("passes sessionLabel option to confirmPermission when present", async () => {
327
- mockConfirmPermission.mockResolvedValue({
320
+ mockRequestApproval.mockResolvedValue({
328
321
  approved: true,
329
322
  state: "approved",
330
323
  });
@@ -334,17 +327,16 @@ describe("PermissionPrompter", () => {
334
327
 
335
328
  await prompter.prompt(makeCtx(true), details);
336
329
 
337
- expect(mockConfirmPermission).toHaveBeenCalledWith(
330
+ expect(mockRequestApproval).toHaveBeenCalledWith(
338
331
  expect.anything(),
339
332
  expect.any(String),
340
- expect.anything(),
341
333
  { sessionLabel: "Yes, for 'read' tool" },
342
334
  { source: "tool_call", surface: "read", value: "read" },
343
335
  );
344
336
  });
345
337
 
346
338
  it("passes the display fields (source/surface/value) to confirmPermission", async () => {
347
- mockConfirmPermission.mockResolvedValue({
339
+ mockRequestApproval.mockResolvedValue({
348
340
  approved: true,
349
341
  state: "approved",
350
342
  });
@@ -357,17 +349,16 @@ describe("PermissionPrompter", () => {
357
349
 
358
350
  await prompter.prompt(makeCtx(false), details);
359
351
 
360
- expect(mockConfirmPermission).toHaveBeenCalledWith(
352
+ expect(mockRequestApproval).toHaveBeenCalledWith(
361
353
  expect.anything(),
362
354
  expect.any(String),
363
- expect.anything(),
364
355
  undefined,
365
356
  { source: "tool_call", surface: "bash", value: "git push" },
366
357
  );
367
358
  });
368
359
 
369
360
  it("passes undefined options to confirmPermission when sessionLabel is absent", async () => {
370
- mockConfirmPermission.mockResolvedValue({
361
+ mockRequestApproval.mockResolvedValue({
371
362
  approved: true,
372
363
  state: "approved",
373
364
  });
@@ -376,17 +367,16 @@ describe("PermissionPrompter", () => {
376
367
 
377
368
  await prompter.prompt(makeCtx(true), makeDetails());
378
369
 
379
- expect(mockConfirmPermission).toHaveBeenCalledWith(
370
+ expect(mockRequestApproval).toHaveBeenCalledWith(
380
371
  expect.anything(),
381
372
  expect.any(String),
382
- expect.anything(),
383
373
  undefined,
384
374
  { source: "tool_call", surface: "read", value: "read" },
385
375
  );
386
376
  });
387
377
 
388
378
  it("passes the message from details to confirmPermission", async () => {
389
- mockConfirmPermission.mockResolvedValue({
379
+ mockRequestApproval.mockResolvedValue({
390
380
  approved: true,
391
381
  state: "approved",
392
382
  });
@@ -396,10 +386,9 @@ describe("PermissionPrompter", () => {
396
386
 
397
387
  await prompter.prompt(makeCtx(true), details);
398
388
 
399
- expect(mockConfirmPermission).toHaveBeenCalledWith(
389
+ expect(mockRequestApproval).toHaveBeenCalledWith(
400
390
  expect.anything(),
401
391
  "Allow bash: git status?",
402
- expect.anything(),
403
392
  undefined,
404
393
  { source: "tool_call", surface: "read", value: "read" },
405
394
  );
@@ -411,7 +400,7 @@ describe("PermissionPrompter", () => {
411
400
  describe("review log fields", () => {
412
401
  it("includes all standard fields in the waiting log entry", async () => {
413
402
  const writeReviewLog = vi.fn();
414
- mockConfirmPermission.mockResolvedValue({
403
+ mockRequestApproval.mockResolvedValue({
415
404
  approved: true,
416
405
  state: "approved",
417
406
  });
@@ -448,7 +437,7 @@ describe("PermissionPrompter", () => {
448
437
 
449
438
  it("uses null for optional fields not present in details", async () => {
450
439
  const writeReviewLog = vi.fn();
451
- mockConfirmPermission.mockResolvedValue({
440
+ mockRequestApproval.mockResolvedValue({
452
441
  approved: true,
453
442
  state: "approved",
454
443
  });
@@ -479,13 +468,13 @@ describe("PermissionPrompter", () => {
479
468
  approved: true,
480
469
  state: "approved",
481
470
  };
482
- mockConfirmPermission.mockResolvedValue(forwarded);
471
+ mockRequestApproval.mockResolvedValue(forwarded);
483
472
  const deps = makeDeps();
484
473
  const prompter = new PermissionPrompter(deps);
485
474
 
486
475
  await prompter.prompt(makeCtx(false), makeDetails());
487
476
 
488
- expect(mockConfirmPermission).toHaveBeenCalled();
477
+ expect(mockRequestApproval).toHaveBeenCalled();
489
478
  });
490
479
 
491
480
  it("returns the decision from confirmPermission in the subagent path", async () => {
@@ -493,7 +482,7 @@ describe("PermissionPrompter", () => {
493
482
  approved: false,
494
483
  state: "denied",
495
484
  };
496
- mockConfirmPermission.mockResolvedValue(forwarded);
485
+ mockRequestApproval.mockResolvedValue(forwarded);
497
486
  const deps = makeDeps();
498
487
  const prompter = new PermissionPrompter(deps);
499
488
 
@@ -504,7 +493,7 @@ describe("PermissionPrompter", () => {
504
493
 
505
494
  it("logs the outcome when confirmPermission resolves via forwarding", async () => {
506
495
  const writeReviewLog = vi.fn();
507
- mockConfirmPermission.mockResolvedValue({
496
+ mockRequestApproval.mockResolvedValue({
508
497
  approved: true,
509
498
  state: "approved",
510
499
  });