@gotgenes/pi-permission-system 8.2.0 → 8.3.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 +23 -0
- package/package.json +1 -1
- package/src/builtin-tool-input-formatters.ts +82 -0
- package/src/config-loader.ts +53 -46
- package/src/handlers/gates/bash-path-extractor.ts +135 -169
- package/src/handlers/gates/bash-token-classification.ts +105 -0
- package/src/handlers/permission-gate-handler.ts +3 -0
- package/src/index.ts +13 -1
- package/src/permission-prompts.ts +5 -1
- package/src/service.ts +21 -1
- package/src/tool-input-formatter-registry.ts +57 -0
- package/src/tool-preview-formatter.ts +18 -1
- package/test/builtin-tool-input-formatters.test.ts +109 -0
- package/test/config-loader.test.ts +82 -0
- package/test/handlers/before-agent-start.test.ts +2 -20
- package/test/handlers/external-directory-integration.test.ts +43 -81
- package/test/handlers/external-directory-session-dedup.test.ts +2 -29
- package/test/handlers/gates/bash-path.test.ts +5 -26
- package/test/handlers/gates/bash-token-classification.test.ts +241 -0
- package/test/handlers/gates/path.test.ts +3 -12
- package/test/handlers/gates/runner.test.ts +78 -91
- package/test/handlers/input-events.test.ts +42 -95
- package/test/handlers/input.test.ts +3 -71
- package/test/handlers/lifecycle.test.ts +3 -20
- package/test/handlers/tool-call-events.test.ts +30 -127
- package/test/handlers/tool-call.test.ts +21 -110
- package/test/helpers/gate-fixtures.ts +105 -0
- package/test/helpers/handler-fixtures.ts +141 -0
- package/test/helpers/manager-harness.ts +51 -0
- package/test/permission-prompts.test.ts +53 -7
- package/test/permission-session.test.ts +1 -19
- package/test/permission-system.test.ts +4 -40
- package/test/service.test.ts +52 -0
- package/test/tool-input-formatter-registry.test.ts +75 -0
- package/test/tool-preview-formatter.test.ts +73 -0
|
@@ -8,21 +8,23 @@
|
|
|
8
8
|
* Regression guard: importing the four external-directory message helpers
|
|
9
9
|
* ensures the test file fails to load if any helper is removed.
|
|
10
10
|
*/
|
|
11
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
11
|
import { describe, expect, it, vi } from "vitest";
|
|
13
12
|
|
|
14
13
|
import { EXTENSION_TAG } from "#src/denial-messages";
|
|
15
14
|
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
16
15
|
import { formatExternalDirectoryAskPrompt } from "#src/handlers/gates/external-directory-messages";
|
|
17
16
|
import { PermissionGateHandler } from "#src/handlers/permission-gate-handler";
|
|
18
|
-
import {
|
|
19
|
-
PERMISSIONS_DECISION_CHANNEL,
|
|
20
|
-
type PermissionDecisionEvent,
|
|
21
|
-
} from "#src/permission-events";
|
|
22
17
|
import type { PermissionSession } from "#src/permission-session";
|
|
23
18
|
import type { ToolRegistry } from "#src/tool-registry";
|
|
24
19
|
import type { PermissionCheckResult, PermissionState } from "#src/types";
|
|
25
20
|
|
|
21
|
+
import {
|
|
22
|
+
getDecisionEvents,
|
|
23
|
+
makeCtx,
|
|
24
|
+
makeEvents,
|
|
25
|
+
makeToolCallEvent,
|
|
26
|
+
} from "#test/helpers/handler-fixtures";
|
|
27
|
+
|
|
26
28
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
27
29
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
28
30
|
const original =
|
|
@@ -70,39 +72,6 @@ function makeCheckPermission(
|
|
|
70
72
|
});
|
|
71
73
|
}
|
|
72
74
|
|
|
73
|
-
function makeCtx(
|
|
74
|
-
overrides: Partial<ExtensionContext> & { cwd?: string } = {},
|
|
75
|
-
): ExtensionContext {
|
|
76
|
-
return {
|
|
77
|
-
cwd: CWD,
|
|
78
|
-
hasUI: true,
|
|
79
|
-
ui: {
|
|
80
|
-
setStatus: vi.fn(),
|
|
81
|
-
notify: vi.fn(),
|
|
82
|
-
select: vi.fn(),
|
|
83
|
-
input: vi.fn(),
|
|
84
|
-
},
|
|
85
|
-
sessionManager: {
|
|
86
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
87
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
88
|
-
addEntry: vi.fn(),
|
|
89
|
-
},
|
|
90
|
-
...overrides,
|
|
91
|
-
} as unknown as ExtensionContext;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function makeToolCallEvent(
|
|
95
|
-
toolName: string,
|
|
96
|
-
input: Record<string, unknown> = {},
|
|
97
|
-
) {
|
|
98
|
-
return {
|
|
99
|
-
type: "tool_call",
|
|
100
|
-
toolCallId: "tc-ext-1",
|
|
101
|
-
name: toolName,
|
|
102
|
-
input,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
75
|
function makeSession(
|
|
107
76
|
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
108
77
|
): PermissionSession {
|
|
@@ -124,13 +93,6 @@ function makeSession(
|
|
|
124
93
|
} as unknown as PermissionSession;
|
|
125
94
|
}
|
|
126
95
|
|
|
127
|
-
function makeEvents() {
|
|
128
|
-
return {
|
|
129
|
-
emit: vi.fn(),
|
|
130
|
-
on: vi.fn().mockReturnValue(() => undefined),
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
96
|
/** All PATH_BEARING_TOOLS members. */
|
|
135
97
|
const ALL_PATH_BEARING_TOOLS = ["read", "write", "edit", "find", "grep", "ls"];
|
|
136
98
|
|
|
@@ -164,14 +126,6 @@ function makeHandler(overrides?: {
|
|
|
164
126
|
return { handler, events, session };
|
|
165
127
|
}
|
|
166
128
|
|
|
167
|
-
function getDecisionEvents(
|
|
168
|
-
events: ReturnType<typeof makeEvents>,
|
|
169
|
-
): PermissionDecisionEvent[] {
|
|
170
|
-
return events.emit.mock.calls
|
|
171
|
-
.filter(([channel]) => channel === PERMISSIONS_DECISION_CHANNEL)
|
|
172
|
-
.map(([, payload]) => payload as PermissionDecisionEvent);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
129
|
// ── Regression guard: helper presence ──────────────────────────────────────
|
|
176
130
|
|
|
177
131
|
describe("external_directory helper regression guard", () => {
|
|
@@ -199,7 +153,7 @@ describe("external_directory path scope", () => {
|
|
|
199
153
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
200
154
|
});
|
|
201
155
|
const event = makeToolCallEvent("read", {
|
|
202
|
-
path: `${CWD}/src/index.ts
|
|
156
|
+
input: { path: `${CWD}/src/index.ts` },
|
|
203
157
|
});
|
|
204
158
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
205
159
|
// Should not be blocked — the external_directory gate is skipped,
|
|
@@ -211,7 +165,7 @@ describe("external_directory path scope", () => {
|
|
|
211
165
|
const { handler } = makeHandler({
|
|
212
166
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
213
167
|
});
|
|
214
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
168
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
215
169
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
216
170
|
expect(result).toMatchObject({ block: true });
|
|
217
171
|
});
|
|
@@ -221,7 +175,7 @@ describe("external_directory path scope", () => {
|
|
|
221
175
|
session: { checkPermission: makeCheckPermission("deny", "allow") },
|
|
222
176
|
});
|
|
223
177
|
const event = makeToolCallEvent("bash", {
|
|
224
|
-
command: `cat ${EXTERNAL_PATH}
|
|
178
|
+
input: { command: `cat ${EXTERNAL_PATH}` },
|
|
225
179
|
});
|
|
226
180
|
// bash is not in PATH_BEARING_TOOLS, so the external_directory gate
|
|
227
181
|
// for tool path does not fire (bash-external-directory gate is separate)
|
|
@@ -239,7 +193,9 @@ describe("external_directory path scope", () => {
|
|
|
239
193
|
const { handler } = makeHandler({
|
|
240
194
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
241
195
|
});
|
|
242
|
-
const event = makeToolCallEvent(toolName, {
|
|
196
|
+
const event = makeToolCallEvent(toolName, {
|
|
197
|
+
input: { path: EXTERNAL_PATH },
|
|
198
|
+
});
|
|
243
199
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
244
200
|
expect(result).toMatchObject({ block: true });
|
|
245
201
|
});
|
|
@@ -251,7 +207,7 @@ describe("external_directory path scope", () => {
|
|
|
251
207
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
252
208
|
});
|
|
253
209
|
// No path in input — external_directory gate should not fire
|
|
254
|
-
const event = makeToolCallEvent(toolName
|
|
210
|
+
const event = makeToolCallEvent(toolName);
|
|
255
211
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
256
212
|
expect(result).toEqual({});
|
|
257
213
|
});
|
|
@@ -264,7 +220,7 @@ describe("external_directory policy state — allow", () => {
|
|
|
264
220
|
const { handler } = makeHandler({
|
|
265
221
|
session: { checkPermission: makeCheckPermission("allow") },
|
|
266
222
|
});
|
|
267
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
223
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
268
224
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
269
225
|
expect(result).toEqual({});
|
|
270
226
|
});
|
|
@@ -273,7 +229,7 @@ describe("external_directory policy state — allow", () => {
|
|
|
273
229
|
const { handler, events } = makeHandler({
|
|
274
230
|
session: { checkPermission: makeCheckPermission("allow") },
|
|
275
231
|
});
|
|
276
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
232
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
277
233
|
await handler.handleToolCall(event, makeCtx());
|
|
278
234
|
const decisions = getDecisionEvents(events);
|
|
279
235
|
const extDirDecision = decisions.find(
|
|
@@ -290,7 +246,7 @@ describe("external_directory policy state — allow", () => {
|
|
|
290
246
|
const { handler, session } = makeHandler({
|
|
291
247
|
session: { checkPermission: makeCheckPermission("allow") },
|
|
292
248
|
});
|
|
293
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
249
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
294
250
|
await handler.handleToolCall(event, makeCtx());
|
|
295
251
|
const reviewCalls = (session.logger.review as ReturnType<typeof vi.fn>).mock
|
|
296
252
|
.calls;
|
|
@@ -307,7 +263,7 @@ describe("external_directory — allow external reads, gate external writes (#14
|
|
|
307
263
|
const { handler } = makeHandler({
|
|
308
264
|
session: { checkPermission: makeCheckPermission("allow", "allow") },
|
|
309
265
|
});
|
|
310
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
266
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
311
267
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
312
268
|
expect(result).toEqual({});
|
|
313
269
|
});
|
|
@@ -322,7 +278,9 @@ describe("external_directory — allow external reads, gate external writes (#14
|
|
|
322
278
|
prompt,
|
|
323
279
|
},
|
|
324
280
|
});
|
|
325
|
-
const event = makeToolCallEvent("write", {
|
|
281
|
+
const event = makeToolCallEvent("write", {
|
|
282
|
+
input: { path: EXTERNAL_PATH },
|
|
283
|
+
});
|
|
326
284
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
327
285
|
// external_directory passes; write gate prompts and user approves
|
|
328
286
|
expect(result).toEqual({});
|
|
@@ -333,7 +291,9 @@ describe("external_directory — allow external reads, gate external writes (#14
|
|
|
333
291
|
const { handler } = makeHandler({
|
|
334
292
|
session: { checkPermission: makeCheckPermission("allow", "deny") },
|
|
335
293
|
});
|
|
336
|
-
const event = makeToolCallEvent("write", {
|
|
294
|
+
const event = makeToolCallEvent("write", {
|
|
295
|
+
input: { path: EXTERNAL_PATH },
|
|
296
|
+
});
|
|
337
297
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
338
298
|
expect(result.block).toBe(true);
|
|
339
299
|
});
|
|
@@ -342,7 +302,9 @@ describe("external_directory — allow external reads, gate external writes (#14
|
|
|
342
302
|
const { handler, events } = makeHandler({
|
|
343
303
|
session: { checkPermission: makeCheckPermission("allow", "deny") },
|
|
344
304
|
});
|
|
345
|
-
const event = makeToolCallEvent("write", {
|
|
305
|
+
const event = makeToolCallEvent("write", {
|
|
306
|
+
input: { path: EXTERNAL_PATH },
|
|
307
|
+
});
|
|
346
308
|
await handler.handleToolCall(event, makeCtx());
|
|
347
309
|
const decisions = getDecisionEvents(events);
|
|
348
310
|
const extDirDecision = decisions.find(
|
|
@@ -367,7 +329,7 @@ describe("external_directory policy state — deny", () => {
|
|
|
367
329
|
const { handler } = makeHandler({
|
|
368
330
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
369
331
|
});
|
|
370
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
332
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
371
333
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
372
334
|
expect(result.block).toBe(true);
|
|
373
335
|
expect(result.reason).toContain(EXTERNAL_PATH);
|
|
@@ -377,7 +339,7 @@ describe("external_directory policy state — deny", () => {
|
|
|
377
339
|
const { handler } = makeHandler({
|
|
378
340
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
379
341
|
});
|
|
380
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
342
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
381
343
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
382
344
|
expect(result.reason).toContain("[pi-permission-system]");
|
|
383
345
|
expect(result.reason).not.toContain("Hard stop");
|
|
@@ -387,7 +349,7 @@ describe("external_directory policy state — deny", () => {
|
|
|
387
349
|
const { handler, session } = makeHandler({
|
|
388
350
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
389
351
|
});
|
|
390
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
352
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
391
353
|
await handler.handleToolCall(event, makeCtx());
|
|
392
354
|
const reviewCalls = (session.logger.review as ReturnType<typeof vi.fn>).mock
|
|
393
355
|
.calls;
|
|
@@ -404,7 +366,7 @@ describe("external_directory policy state — deny", () => {
|
|
|
404
366
|
const { handler, events } = makeHandler({
|
|
405
367
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
406
368
|
});
|
|
407
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
369
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
408
370
|
await handler.handleToolCall(event, makeCtx());
|
|
409
371
|
const decisions = getDecisionEvents(events);
|
|
410
372
|
const extDirDecision = decisions.find(
|
|
@@ -430,7 +392,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
430
392
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
431
393
|
},
|
|
432
394
|
});
|
|
433
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
395
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
434
396
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
435
397
|
expect(result).toEqual({});
|
|
436
398
|
});
|
|
@@ -444,7 +406,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
444
406
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
445
407
|
},
|
|
446
408
|
});
|
|
447
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
409
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
448
410
|
await handler.handleToolCall(event, makeCtx());
|
|
449
411
|
const decisions = getDecisionEvents(events);
|
|
450
412
|
const extDirDecision = decisions.find(
|
|
@@ -464,7 +426,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
464
426
|
prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
|
|
465
427
|
},
|
|
466
428
|
});
|
|
467
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
429
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
468
430
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
469
431
|
expect(result.block).toBe(true);
|
|
470
432
|
});
|
|
@@ -476,7 +438,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
476
438
|
prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
|
|
477
439
|
},
|
|
478
440
|
});
|
|
479
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
441
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
480
442
|
await handler.handleToolCall(event, makeCtx());
|
|
481
443
|
const decisions = getDecisionEvents(events);
|
|
482
444
|
const extDirDecision = decisions.find(
|
|
@@ -500,7 +462,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
500
462
|
}),
|
|
501
463
|
},
|
|
502
464
|
});
|
|
503
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
465
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
504
466
|
const result = await handler.handleToolCall(event, makeCtx());
|
|
505
467
|
expect(result.block).toBe(true);
|
|
506
468
|
expect(result.reason).toContain("not needed");
|
|
@@ -513,7 +475,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
513
475
|
canPrompt: vi.fn().mockReturnValue(false),
|
|
514
476
|
},
|
|
515
477
|
});
|
|
516
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
478
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
517
479
|
const result = await handler.handleToolCall(
|
|
518
480
|
event,
|
|
519
481
|
makeCtx({ hasUI: false }),
|
|
@@ -529,7 +491,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
529
491
|
canPrompt: vi.fn().mockReturnValue(false),
|
|
530
492
|
},
|
|
531
493
|
});
|
|
532
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
494
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
533
495
|
await handler.handleToolCall(event, makeCtx({ hasUI: false }));
|
|
534
496
|
const reviewCalls = (session.logger.review as ReturnType<typeof vi.fn>).mock
|
|
535
497
|
.calls;
|
|
@@ -549,7 +511,7 @@ describe("external_directory policy state — ask", () => {
|
|
|
549
511
|
canPrompt: vi.fn().mockReturnValue(false),
|
|
550
512
|
},
|
|
551
513
|
});
|
|
552
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
514
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
553
515
|
await handler.handleToolCall(event, makeCtx({ hasUI: false }));
|
|
554
516
|
const decisions = getDecisionEvents(events);
|
|
555
517
|
const extDirDecision = decisions.find(
|
|
@@ -602,7 +564,7 @@ describe("external_directory per-agent override", () => {
|
|
|
602
564
|
resolveAgentName: vi.fn().mockReturnValue("special-agent"),
|
|
603
565
|
},
|
|
604
566
|
});
|
|
605
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
567
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
606
568
|
const result1 = await handler1.handleToolCall(event, makeCtx());
|
|
607
569
|
expect(result1).toEqual({});
|
|
608
570
|
|
|
@@ -633,7 +595,7 @@ describe("external_directory decision event fields", () => {
|
|
|
633
595
|
const { handler, events } = makeHandler({
|
|
634
596
|
session: { checkPermission: makeCheckPermission("deny") },
|
|
635
597
|
});
|
|
636
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
598
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
637
599
|
await handler.handleToolCall(event, makeCtx());
|
|
638
600
|
const decisions = getDecisionEvents(events);
|
|
639
601
|
const extDirDecision = decisions.find(
|
|
@@ -650,7 +612,7 @@ describe("external_directory decision event fields", () => {
|
|
|
650
612
|
resolveAgentName: vi.fn().mockReturnValue("my-agent"),
|
|
651
613
|
},
|
|
652
614
|
});
|
|
653
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
615
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
654
616
|
await handler.handleToolCall(event, makeCtx());
|
|
655
617
|
const decisions = getDecisionEvents(events);
|
|
656
618
|
const extDirDecision = decisions.find(
|
|
@@ -665,7 +627,7 @@ describe("external_directory decision event fields", () => {
|
|
|
665
627
|
const { handler, events } = makeHandler({
|
|
666
628
|
session: { checkPermission: makeCheckPermission("allow") },
|
|
667
629
|
});
|
|
668
|
-
const event = makeToolCallEvent("read", { path: EXTERNAL_PATH });
|
|
630
|
+
const event = makeToolCallEvent("read", { input: { path: EXTERNAL_PATH } });
|
|
669
631
|
await handler.handleToolCall(event, makeCtx());
|
|
670
632
|
const decisions = getDecisionEvents(events);
|
|
671
633
|
const extDirDecision = decisions.find(
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* the real interaction between PermissionSession, SessionRules, and
|
|
9
9
|
* PermissionManager.
|
|
10
10
|
*/
|
|
11
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
11
|
import { describe, expect, it, vi } from "vitest";
|
|
13
12
|
|
|
14
13
|
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
@@ -20,6 +19,8 @@ import type { ToolRegistry } from "#src/tool-registry";
|
|
|
20
19
|
import type { PermissionCheckResult } from "#src/types";
|
|
21
20
|
import { wildcardMatch } from "#src/wildcard-matcher";
|
|
22
21
|
|
|
22
|
+
import { makeCtx, makeEvents } from "#test/helpers/handler-fixtures";
|
|
23
|
+
|
|
23
24
|
// ── SDK stub ───────────────────────────────────────────────────────────────
|
|
24
25
|
vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
25
26
|
const original =
|
|
@@ -29,27 +30,6 @@ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
|
29
30
|
|
|
30
31
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
31
32
|
|
|
32
|
-
const CWD = "/test/project";
|
|
33
|
-
|
|
34
|
-
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
35
|
-
return {
|
|
36
|
-
cwd: CWD,
|
|
37
|
-
hasUI: true,
|
|
38
|
-
ui: {
|
|
39
|
-
setStatus: vi.fn(),
|
|
40
|
-
notify: vi.fn(),
|
|
41
|
-
select: vi.fn(),
|
|
42
|
-
input: vi.fn(),
|
|
43
|
-
},
|
|
44
|
-
sessionManager: {
|
|
45
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
46
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
47
|
-
addEntry: vi.fn(),
|
|
48
|
-
},
|
|
49
|
-
...overrides,
|
|
50
|
-
} as unknown as ExtensionContext;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
33
|
/**
|
|
54
34
|
* Build a PermissionSession mock with stateful session-rule tracking.
|
|
55
35
|
*
|
|
@@ -152,13 +132,6 @@ function makeStatefulSession(
|
|
|
152
132
|
} as unknown as PermissionSession;
|
|
153
133
|
}
|
|
154
134
|
|
|
155
|
-
function makeEvents() {
|
|
156
|
-
return {
|
|
157
|
-
emit: vi.fn(),
|
|
158
|
-
on: vi.fn().mockReturnValue(() => undefined),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
135
|
function makeToolRegistry(): ToolRegistry {
|
|
163
136
|
return {
|
|
164
137
|
getAll: vi
|
|
@@ -15,39 +15,18 @@ import type {
|
|
|
15
15
|
GateDescriptor,
|
|
16
16
|
} from "#src/handlers/gates/descriptor";
|
|
17
17
|
import { isGateBypass, isGateDescriptor } from "#src/handlers/gates/descriptor";
|
|
18
|
-
import type { ToolCallContext } from "#src/handlers/gates/types";
|
|
19
18
|
import type { Rule } from "#src/rule";
|
|
20
19
|
import type { PermissionCheckResult } from "#src/types";
|
|
21
20
|
|
|
21
|
+
import {
|
|
22
|
+
makeGateCheckResult as makeCheckResult,
|
|
23
|
+
makeTcc,
|
|
24
|
+
} from "#test/helpers/gate-fixtures";
|
|
25
|
+
|
|
22
26
|
afterEach(() => {
|
|
23
27
|
vi.restoreAllMocks();
|
|
24
28
|
});
|
|
25
29
|
|
|
26
|
-
// ── helpers ────────────────────────────────────────────────────────────────
|
|
27
|
-
|
|
28
|
-
function makeTcc(overrides: Partial<ToolCallContext> = {}): ToolCallContext {
|
|
29
|
-
return {
|
|
30
|
-
toolName: "bash",
|
|
31
|
-
agentName: null,
|
|
32
|
-
input: { command: "cat .env" },
|
|
33
|
-
toolCallId: "tc-1",
|
|
34
|
-
cwd: "/test/project",
|
|
35
|
-
...overrides,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function makeCheckResult(
|
|
40
|
-
overrides: Partial<PermissionCheckResult> = {},
|
|
41
|
-
): PermissionCheckResult {
|
|
42
|
-
return {
|
|
43
|
-
toolName: "path",
|
|
44
|
-
state: "allow",
|
|
45
|
-
source: "special",
|
|
46
|
-
origin: "global",
|
|
47
|
-
...overrides,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
30
|
type CheckPermissionFn = (
|
|
52
31
|
surface: string,
|
|
53
32
|
input: unknown,
|