@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.
- package/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/agent-prep-session.ts +28 -0
- package/src/decision-reporter.ts +41 -0
- package/src/denial-messages.ts +11 -0
- package/src/forwarded-permissions/permission-forwarder.ts +549 -0
- package/src/forwarding-manager.ts +3 -7
- package/src/gate-handler-session.ts +13 -0
- package/src/gate-prompter.ts +14 -0
- package/src/handlers/before-agent-start.ts +2 -3
- package/src/handlers/gates/bash-command.ts +4 -18
- package/src/handlers/gates/bash-external-directory.ts +3 -15
- package/src/handlers/gates/bash-path.ts +3 -16
- package/src/handlers/gates/descriptor.ts +0 -28
- package/src/handlers/gates/path.ts +3 -15
- package/src/handlers/gates/runner.ts +142 -105
- package/src/handlers/gates/skill-input-gate-pipeline.ts +104 -0
- package/src/handlers/gates/skill-input.ts +44 -0
- package/src/handlers/gates/tool-call-gate-pipeline.ts +120 -0
- package/src/handlers/lifecycle.ts +9 -9
- package/src/handlers/permission-gate-handler.ts +34 -238
- package/src/index.ts +53 -69
- package/src/mcp-targets.ts +56 -46
- package/src/permission-manager.ts +69 -3
- package/src/permission-prompter.ts +7 -58
- package/src/permission-resolver.ts +17 -0
- package/src/permission-session.ts +83 -27
- package/src/permissions-service.ts +53 -0
- package/src/runtime.ts +1 -37
- package/src/service-lifecycle.ts +49 -0
- package/src/session-approval-recorder.ts +6 -0
- package/src/session-lifecycle-session.ts +24 -0
- package/src/tool-input-preview.ts +0 -62
- package/src/tool-input-prompt-formatters.ts +63 -0
- package/src/tool-preview-formatter.ts +6 -4
- package/test/decision-reporter.test.ts +112 -0
- package/test/denial-messages.test.ts +62 -0
- package/test/forwarding-manager.test.ts +26 -44
- package/test/handlers/before-agent-start.test.ts +45 -21
- package/test/handlers/external-directory-integration.test.ts +83 -114
- package/test/handlers/external-directory-session-dedup.test.ts +102 -55
- package/test/handlers/gates/bash-command.test.ts +49 -90
- package/test/handlers/gates/bash-external-directory.test.ts +54 -95
- package/test/handlers/gates/bash-path.test.ts +54 -157
- package/test/handlers/gates/path.test.ts +38 -105
- package/test/handlers/gates/runner.test.ts +151 -186
- package/test/handlers/gates/skill-input-gate-pipeline.test.ts +176 -0
- package/test/handlers/gates/skill-input.test.ts +128 -0
- package/test/handlers/gates/tool-call-gate-pipeline.test.ts +180 -0
- package/test/handlers/input.test.ts +1 -2
- package/test/handlers/lifecycle.test.ts +49 -33
- package/test/handlers/tool-call-events.test.ts +1 -1
- package/test/handlers/tool-call.test.ts +44 -153
- package/test/helpers/gate-fixtures.ts +212 -17
- package/test/helpers/handler-fixtures.ts +226 -29
- package/test/mcp-targets.test.ts +55 -0
- package/test/permission-forwarder.test.ts +295 -0
- package/test/permission-forwarding.test.ts +0 -282
- package/test/permission-manager-unified.test.ts +159 -1
- package/test/permission-prompter.test.ts +33 -44
- package/test/permission-session.test.ts +211 -105
- package/test/permissions-service.test.ts +151 -0
- package/test/runtime.test.ts +2 -86
- package/test/service-lifecycle.test.ts +162 -0
- package/test/tool-input-preview.test.ts +0 -111
- package/test/tool-input-prompt-formatters.test.ts +115 -0
- 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
|
-
// ──
|
|
3
|
+
// ── Injected mock ───────────────────────────────────────────────────────────
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
mockConfirmPermission: vi.fn(),
|
|
7
|
-
}));
|
|
5
|
+
const mockRequestApproval = vi.fn();
|
|
8
6
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
496
|
+
mockRequestApproval.mockResolvedValue({
|
|
508
497
|
approved: true,
|
|
509
498
|
state: "approved",
|
|
510
499
|
});
|