@gotgenes/pi-permission-system 5.5.0 → 5.6.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/handlers/before-agent-start.ts +7 -7
- package/src/handlers/gates/bash-external-directory.ts +70 -67
- package/src/handlers/gates/descriptor.ts +115 -0
- package/src/handlers/gates/external-directory.ts +62 -130
- package/src/handlers/gates/index.ts +12 -4
- package/src/handlers/gates/runner.ts +144 -0
- package/src/handlers/gates/skill-read.ts +40 -59
- package/src/handlers/gates/tool.ts +35 -104
- package/src/handlers/gates/types.ts +0 -2
- package/src/handlers/input.ts +3 -3
- package/src/handlers/lifecycle.ts +21 -21
- package/src/handlers/tool-call.ts +121 -20
- package/src/handlers/types.ts +20 -7
- package/src/index.ts +6 -1
- package/src/runtime.ts +17 -9
- package/tests/handlers/before-agent-start.test.ts +17 -27
- package/tests/handlers/gates/bash-external-directory.test.ts +129 -184
- package/tests/handlers/gates/external-directory.test.ts +118 -264
- package/tests/handlers/gates/runner.test.ts +361 -0
- package/tests/handlers/gates/skill-read.test.ts +86 -137
- package/tests/handlers/gates/tool.test.ts +109 -346
- package/tests/handlers/input-events.test.ts +10 -21
- package/tests/handlers/input.test.ts +26 -43
- package/tests/handlers/lifecycle.test.ts +47 -66
- package/tests/handlers/tool-call-events.test.ts +29 -40
- package/tests/handlers/tool-call.test.ts +19 -30
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { GateDescriptor } from "../../../src/handlers/gates/descriptor";
|
|
4
|
+
import { describeToolGate } from "../../../src/handlers/gates/tool";
|
|
4
5
|
import type { ToolCallContext } from "../../../src/handlers/gates/types";
|
|
5
|
-
import type { HandlerDeps } from "../../../src/handlers/types";
|
|
6
|
-
import type { PermissionEventBus } from "../../../src/permission-events";
|
|
7
6
|
import type { PermissionCheckResult } from "../../../src/types";
|
|
8
7
|
|
|
9
|
-
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
10
|
-
vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
|
|
11
|
-
const original =
|
|
12
|
-
await importOriginal<typeof import("@mariozechner/pi-coding-agent")>();
|
|
13
|
-
return { ...original };
|
|
14
|
-
});
|
|
15
|
-
|
|
16
8
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
17
9
|
|
|
18
10
|
function makeTcc(overrides: Partial<ToolCallContext> = {}): ToolCallContext {
|
|
19
11
|
return {
|
|
20
12
|
toolName: "read",
|
|
21
13
|
agentName: null,
|
|
22
|
-
input: {
|
|
14
|
+
input: {},
|
|
23
15
|
toolCallId: "tc-1",
|
|
24
16
|
cwd: "/test/project",
|
|
25
17
|
...overrides,
|
|
@@ -35,383 +27,154 @@ function makeCheckResult(
|
|
|
35
27
|
toolName: "read",
|
|
36
28
|
source: "tool",
|
|
37
29
|
origin: "builtin",
|
|
30
|
+
matchedPattern: "*",
|
|
38
31
|
...overrides,
|
|
39
32
|
};
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
function makeEvents(): PermissionEventBus {
|
|
43
|
-
return { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function makeRuntime(
|
|
47
|
-
overrides: Record<string, unknown> = {},
|
|
48
|
-
): HandlerDeps["runtime"] {
|
|
49
|
-
return {
|
|
50
|
-
config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
|
|
51
|
-
runtimeContext: {} as HandlerDeps["runtime"]["runtimeContext"],
|
|
52
|
-
permissionManager: {
|
|
53
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
|
|
54
|
-
},
|
|
55
|
-
sessionRules: {
|
|
56
|
-
approve: vi.fn(),
|
|
57
|
-
getRuleset: vi.fn().mockReturnValue([]),
|
|
58
|
-
clear: vi.fn(),
|
|
59
|
-
},
|
|
60
|
-
writeReviewLog: vi.fn(),
|
|
61
|
-
...overrides,
|
|
62
|
-
} as unknown as HandlerDeps["runtime"];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
|
|
66
|
-
const { runtime: runtimeOverrides, events, ...rest } = overrides;
|
|
67
|
-
return {
|
|
68
|
-
runtime: makeRuntime(runtimeOverrides as Record<string, unknown>),
|
|
69
|
-
events: events ?? makeEvents(),
|
|
70
|
-
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|
|
71
|
-
promptPermission: vi
|
|
72
|
-
.fn()
|
|
73
|
-
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
74
|
-
...rest,
|
|
75
|
-
} as unknown as HandlerDeps;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
35
|
// ── tests ──────────────────────────────────────────────────────────────────
|
|
79
36
|
|
|
80
|
-
describe("
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const deps = makeDeps({
|
|
86
|
-
runtime: {
|
|
87
|
-
permissionManager: {
|
|
88
|
-
checkPermission: vi.fn().mockReturnValue(
|
|
89
|
-
makeCheckResult("allow", {
|
|
90
|
-
source: "session",
|
|
91
|
-
toolName: "bash",
|
|
92
|
-
command: "git status",
|
|
93
|
-
matchedPattern: "git *",
|
|
94
|
-
}),
|
|
95
|
-
),
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
events,
|
|
99
|
-
});
|
|
100
|
-
const tcc = makeTcc({
|
|
101
|
-
toolName: "bash",
|
|
102
|
-
input: { command: "git status" },
|
|
103
|
-
});
|
|
104
|
-
const result = await evaluateToolGate(tcc, deps);
|
|
105
|
-
expect(result).toEqual({ action: "allow" });
|
|
106
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
107
|
-
"permissions:decision",
|
|
108
|
-
expect.objectContaining({
|
|
109
|
-
surface: "bash",
|
|
110
|
-
value: "git status",
|
|
111
|
-
result: "allow",
|
|
112
|
-
resolution: "session_approved",
|
|
113
|
-
matchedPattern: "git *",
|
|
114
|
-
}),
|
|
37
|
+
describe("describeToolGate", () => {
|
|
38
|
+
it("returns descriptor with tool name as surface for standard tools", () => {
|
|
39
|
+
const desc = describeToolGate(
|
|
40
|
+
makeTcc({ toolName: "read" }),
|
|
41
|
+
makeCheckResult("ask"),
|
|
115
42
|
);
|
|
43
|
+
expect(desc.surface).toBe("read");
|
|
44
|
+
expect(desc.decision.surface).toBe("read");
|
|
116
45
|
});
|
|
117
46
|
|
|
118
|
-
it("
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
clear: vi.fn(),
|
|
123
|
-
};
|
|
124
|
-
const deps = makeDeps({
|
|
125
|
-
runtime: {
|
|
126
|
-
permissionManager: {
|
|
127
|
-
checkPermission: vi.fn().mockReturnValue(
|
|
128
|
-
makeCheckResult("allow", {
|
|
129
|
-
source: "session",
|
|
130
|
-
matchedPattern: "git *",
|
|
131
|
-
}),
|
|
132
|
-
),
|
|
133
|
-
},
|
|
134
|
-
sessionRules,
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
const tcc = makeTcc({
|
|
138
|
-
toolName: "bash",
|
|
139
|
-
input: { command: "git status" },
|
|
140
|
-
});
|
|
141
|
-
await evaluateToolGate(tcc, deps);
|
|
142
|
-
expect(sessionRules.approve).not.toHaveBeenCalled();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// ── Policy allow ─────────────────────────────────────────────────────────
|
|
146
|
-
|
|
147
|
-
it("allows and emits policy_allow", async () => {
|
|
148
|
-
const events = makeEvents();
|
|
149
|
-
const deps = makeDeps({ events });
|
|
150
|
-
const tcc = makeTcc();
|
|
151
|
-
const result = await evaluateToolGate(tcc, deps);
|
|
152
|
-
expect(result).toEqual({ action: "allow" });
|
|
153
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
154
|
-
"permissions:decision",
|
|
155
|
-
expect.objectContaining({
|
|
156
|
-
result: "allow",
|
|
157
|
-
resolution: "policy_allow",
|
|
158
|
-
}),
|
|
47
|
+
it("returns descriptor with tool name as decision value for standard tools", () => {
|
|
48
|
+
const desc = describeToolGate(
|
|
49
|
+
makeTcc({ toolName: "write" }),
|
|
50
|
+
makeCheckResult("ask"),
|
|
159
51
|
);
|
|
52
|
+
expect(desc.decision.value).toBe("write");
|
|
160
53
|
});
|
|
161
54
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const deps = makeDeps({
|
|
167
|
-
runtime: {
|
|
168
|
-
permissionManager: {
|
|
169
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("deny")),
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
events,
|
|
55
|
+
it("returns bash surface with command in decision.value for bash tools", () => {
|
|
56
|
+
const check = makeCheckResult("ask", {
|
|
57
|
+
toolName: "bash",
|
|
58
|
+
command: "git status",
|
|
173
59
|
});
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
178
|
-
"permissions:decision",
|
|
179
|
-
expect.objectContaining({
|
|
180
|
-
result: "deny",
|
|
181
|
-
resolution: "policy_deny",
|
|
182
|
-
}),
|
|
60
|
+
const desc = describeToolGate(
|
|
61
|
+
makeTcc({ toolName: "bash", input: { command: "git status" } }),
|
|
62
|
+
check,
|
|
183
63
|
);
|
|
64
|
+
expect(desc.surface).toBe("bash");
|
|
65
|
+
expect(desc.decision.surface).toBe("bash");
|
|
66
|
+
expect(desc.decision.value).toBe("git status");
|
|
184
67
|
});
|
|
185
68
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const sessionRules = {
|
|
191
|
-
approve: vi.fn(),
|
|
192
|
-
getRuleset: vi.fn().mockReturnValue([]),
|
|
193
|
-
clear: vi.fn(),
|
|
194
|
-
};
|
|
195
|
-
const deps = makeDeps({
|
|
196
|
-
runtime: {
|
|
197
|
-
permissionManager: {
|
|
198
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
|
|
199
|
-
},
|
|
200
|
-
sessionRules,
|
|
201
|
-
},
|
|
202
|
-
events,
|
|
203
|
-
promptPermission: vi
|
|
204
|
-
.fn()
|
|
205
|
-
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
69
|
+
it("returns mcp surface with target in decision.value for MCP tools", () => {
|
|
70
|
+
const check = makeCheckResult("ask", {
|
|
71
|
+
toolName: "mcp",
|
|
72
|
+
target: "server:tool",
|
|
206
73
|
});
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
expect(sessionRules.approve).not.toHaveBeenCalled();
|
|
211
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
212
|
-
"permissions:decision",
|
|
213
|
-
expect.objectContaining({
|
|
214
|
-
result: "allow",
|
|
215
|
-
resolution: "user_approved",
|
|
216
|
-
}),
|
|
74
|
+
const desc = describeToolGate(
|
|
75
|
+
makeTcc({ toolName: "mcp", input: { tool: "server:tool" } }),
|
|
76
|
+
check,
|
|
217
77
|
);
|
|
78
|
+
expect(desc.surface).toBe("mcp");
|
|
79
|
+
expect(desc.decision.surface).toBe("mcp");
|
|
80
|
+
expect(desc.decision.value).toBe("server:tool");
|
|
218
81
|
});
|
|
219
82
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
approve: vi.fn(),
|
|
226
|
-
getRuleset: vi.fn().mockReturnValue([]),
|
|
227
|
-
clear: vi.fn(),
|
|
228
|
-
};
|
|
229
|
-
const deps = makeDeps({
|
|
230
|
-
runtime: {
|
|
231
|
-
permissionManager: {
|
|
232
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
|
|
233
|
-
},
|
|
234
|
-
sessionRules,
|
|
235
|
-
},
|
|
236
|
-
events,
|
|
237
|
-
promptPermission: vi
|
|
238
|
-
.fn()
|
|
239
|
-
.mockResolvedValue({ approved: true, state: "approved_for_session" }),
|
|
240
|
-
});
|
|
241
|
-
const tcc = makeTcc();
|
|
242
|
-
const result = await evaluateToolGate(tcc, deps);
|
|
243
|
-
expect(result).toEqual({ action: "allow" });
|
|
244
|
-
expect(sessionRules.approve).toHaveBeenCalledWith("read", "*");
|
|
245
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
246
|
-
"permissions:decision",
|
|
247
|
-
expect.objectContaining({
|
|
248
|
-
resolution: "user_approved_for_session",
|
|
249
|
-
}),
|
|
250
|
-
);
|
|
83
|
+
it("populates messages.denyReason via formatDenyReason", () => {
|
|
84
|
+
const check = makeCheckResult("deny", { toolName: "read" });
|
|
85
|
+
const desc = describeToolGate(makeTcc(), check);
|
|
86
|
+
expect(desc.messages.denyReason).toContain("read");
|
|
87
|
+
expect(desc.messages.denyReason).toContain("not permitted");
|
|
251
88
|
});
|
|
252
89
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const deps = makeDeps({
|
|
258
|
-
runtime: {
|
|
259
|
-
permissionManager: {
|
|
260
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
events,
|
|
264
|
-
promptPermission: vi
|
|
265
|
-
.fn()
|
|
266
|
-
.mockResolvedValue({ approved: false, state: "denied" }),
|
|
90
|
+
it("populates messages.unavailableReason with bash command when tool is bash", () => {
|
|
91
|
+
const check = makeCheckResult("ask", {
|
|
92
|
+
toolName: "bash",
|
|
93
|
+
command: "rm -rf /",
|
|
267
94
|
});
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
272
|
-
"permissions:decision",
|
|
273
|
-
expect.objectContaining({
|
|
274
|
-
result: "deny",
|
|
275
|
-
resolution: "user_denied",
|
|
276
|
-
}),
|
|
95
|
+
const desc = describeToolGate(
|
|
96
|
+
makeTcc({ toolName: "bash", input: { command: "rm -rf /" } }),
|
|
97
|
+
check,
|
|
277
98
|
);
|
|
99
|
+
expect(desc.messages.unavailableReason).toContain("rm -rf /");
|
|
100
|
+
expect(desc.messages.unavailableReason).toContain("no interactive UI");
|
|
278
101
|
});
|
|
279
102
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const deps = makeDeps({
|
|
285
|
-
runtime: {
|
|
286
|
-
permissionManager: {
|
|
287
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("ask")),
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
events,
|
|
291
|
-
canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
|
|
292
|
-
});
|
|
293
|
-
const tcc = makeTcc();
|
|
294
|
-
const result = await evaluateToolGate(tcc, deps);
|
|
295
|
-
expect(result).toMatchObject({ action: "block" });
|
|
296
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
297
|
-
"permissions:decision",
|
|
298
|
-
expect.objectContaining({
|
|
299
|
-
result: "deny",
|
|
300
|
-
resolution: "confirmation_unavailable",
|
|
301
|
-
}),
|
|
103
|
+
it("populates messages.unavailableReason with tool name for non-bash tools", () => {
|
|
104
|
+
const desc = describeToolGate(
|
|
105
|
+
makeTcc({ toolName: "write" }),
|
|
106
|
+
makeCheckResult("ask"),
|
|
302
107
|
);
|
|
108
|
+
expect(desc.messages.unavailableReason).toContain("write");
|
|
109
|
+
expect(desc.messages.unavailableReason).toContain("no interactive UI");
|
|
303
110
|
});
|
|
304
111
|
|
|
305
|
-
|
|
112
|
+
it("populates messages.unavailableReason with mcp for mcp tool", () => {
|
|
113
|
+
const check = makeCheckResult("ask", { toolName: "mcp", target: "s:t" });
|
|
114
|
+
const desc = describeToolGate(makeTcc({ toolName: "mcp" }), check);
|
|
115
|
+
expect(desc.messages.unavailableReason).toContain("mcp");
|
|
116
|
+
});
|
|
306
117
|
|
|
307
|
-
it("
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
},
|
|
315
|
-
events,
|
|
316
|
-
promptPermission: vi.fn().mockResolvedValue({
|
|
317
|
-
approved: true,
|
|
318
|
-
state: "approved",
|
|
319
|
-
autoApproved: true,
|
|
320
|
-
}),
|
|
118
|
+
it("populates messages.userDeniedReason as a function", () => {
|
|
119
|
+
const check = makeCheckResult("ask", { toolName: "read" });
|
|
120
|
+
const desc = describeToolGate(makeTcc(), check);
|
|
121
|
+
const reason = desc.messages.userDeniedReason({
|
|
122
|
+
approved: false,
|
|
123
|
+
state: "denied",
|
|
124
|
+
denialReason: "too risky",
|
|
321
125
|
});
|
|
322
|
-
|
|
323
|
-
const result = await evaluateToolGate(tcc, deps);
|
|
324
|
-
expect(result).toEqual({ action: "allow" });
|
|
325
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
326
|
-
"permissions:decision",
|
|
327
|
-
expect.objectContaining({
|
|
328
|
-
resolution: "auto_approved",
|
|
329
|
-
}),
|
|
330
|
-
);
|
|
126
|
+
expect(reason).toContain("too risky");
|
|
331
127
|
});
|
|
332
128
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
it("uses command as decision value for bash tool", async () => {
|
|
336
|
-
const events = makeEvents();
|
|
337
|
-
const deps = makeDeps({
|
|
338
|
-
runtime: {
|
|
339
|
-
permissionManager: {
|
|
340
|
-
checkPermission: vi.fn().mockReturnValue(
|
|
341
|
-
makeCheckResult("allow", {
|
|
342
|
-
toolName: "bash",
|
|
343
|
-
command: "git status",
|
|
344
|
-
}),
|
|
345
|
-
),
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
events,
|
|
349
|
-
});
|
|
350
|
-
const tcc = makeTcc({
|
|
129
|
+
it("populates sessionApproval via suggestSessionPattern", () => {
|
|
130
|
+
const check = makeCheckResult("ask", {
|
|
351
131
|
toolName: "bash",
|
|
352
|
-
|
|
132
|
+
command: "git status",
|
|
353
133
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
expect.objectContaining({
|
|
358
|
-
surface: "bash",
|
|
359
|
-
value: "git status",
|
|
360
|
-
}),
|
|
134
|
+
const desc = describeToolGate(
|
|
135
|
+
makeTcc({ toolName: "bash", input: { command: "git status" } }),
|
|
136
|
+
check,
|
|
361
137
|
);
|
|
138
|
+
expect(desc.sessionApproval).toBeDefined();
|
|
139
|
+
expect(desc.sessionApproval!).toHaveProperty("surface", "bash");
|
|
140
|
+
expect(desc.sessionApproval!).toHaveProperty("pattern");
|
|
362
141
|
});
|
|
363
142
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
runtime: {
|
|
370
|
-
permissionManager: {
|
|
371
|
-
checkPermission: vi.fn().mockReturnValue(
|
|
372
|
-
makeCheckResult("allow", {
|
|
373
|
-
toolName: "mcp",
|
|
374
|
-
target: "exa:search",
|
|
375
|
-
}),
|
|
376
|
-
),
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
events,
|
|
380
|
-
});
|
|
381
|
-
const tcc = makeTcc({ toolName: "mcp", input: { tool: "exa:search" } });
|
|
382
|
-
await evaluateToolGate(tcc, deps);
|
|
383
|
-
expect(events.emit).toHaveBeenCalledWith(
|
|
384
|
-
"permissions:decision",
|
|
385
|
-
expect.objectContaining({
|
|
386
|
-
surface: "mcp",
|
|
387
|
-
value: "exa:search",
|
|
388
|
-
}),
|
|
143
|
+
it("populates promptDetails with correct fields", () => {
|
|
144
|
+
const check = makeCheckResult("ask");
|
|
145
|
+
const desc = describeToolGate(
|
|
146
|
+
makeTcc({ toolName: "read", agentName: "my-agent", toolCallId: "tc-42" }),
|
|
147
|
+
check,
|
|
389
148
|
);
|
|
149
|
+
expect(desc.promptDetails).toMatchObject({
|
|
150
|
+
source: "tool_call",
|
|
151
|
+
agentName: "my-agent",
|
|
152
|
+
toolCallId: "tc-42",
|
|
153
|
+
toolName: "read",
|
|
154
|
+
});
|
|
155
|
+
expect(desc.promptDetails.message).toBeDefined();
|
|
156
|
+
expect(desc.promptDetails.sessionLabel).toBeDefined();
|
|
390
157
|
});
|
|
391
158
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
.mockReturnValue(
|
|
401
|
-
makeCheckResult("ask", { toolName: "bash", command: "rm -rf /" }),
|
|
402
|
-
),
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
|
|
406
|
-
});
|
|
407
|
-
const tcc = makeTcc({
|
|
159
|
+
it("populates logContext with tool input preview fields", () => {
|
|
160
|
+
const check = makeCheckResult("ask", { toolName: "bash", command: "ls" });
|
|
161
|
+
const desc = describeToolGate(
|
|
162
|
+
makeTcc({ toolName: "bash", input: { command: "ls" } }),
|
|
163
|
+
check,
|
|
164
|
+
);
|
|
165
|
+
expect(desc.logContext).toMatchObject({
|
|
166
|
+
source: "tool_call",
|
|
408
167
|
toolName: "bash",
|
|
409
|
-
input: { command: "rm -rf /" },
|
|
410
168
|
});
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
169
|
+
expect(desc.logContext.command).toBe("ls");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("uses toolName as input for checkPermission surface", () => {
|
|
173
|
+
const desc = describeToolGate(
|
|
174
|
+
makeTcc({ toolName: "edit", input: { path: "/a.ts" } }),
|
|
175
|
+
makeCheckResult("ask", { toolName: "edit" }),
|
|
176
|
+
);
|
|
177
|
+
expect(desc.surface).toBe("edit");
|
|
178
|
+
expect(desc.input).toEqual({ path: "/a.ts" });
|
|
416
179
|
});
|
|
417
180
|
});
|
|
@@ -8,7 +8,7 @@ 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 {
|
|
11
|
+
import type { SessionState } from "../../src/runtime";
|
|
12
12
|
|
|
13
13
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
@@ -38,17 +38,8 @@ function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
|
38
38
|
} as unknown as ExtensionContext;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function
|
|
42
|
-
state: "allow" | "deny" | "ask" = "allow",
|
|
43
|
-
): ExtensionRuntime {
|
|
41
|
+
function makeSession(state: "allow" | "deny" | "ask" = "allow"): SessionState {
|
|
44
42
|
return {
|
|
45
|
-
agentDir: "/test/agent",
|
|
46
|
-
sessionsDir: "/test/agent/sessions",
|
|
47
|
-
subagentSessionsDir: "/test/agent/subagent-sessions",
|
|
48
|
-
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
|
49
|
-
globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
|
|
50
|
-
piInfrastructureDirs: ["/test/agent"],
|
|
51
|
-
config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
|
|
52
43
|
runtimeContext: null,
|
|
53
44
|
permissionManager: {
|
|
54
45
|
checkPermission: vi.fn().mockReturnValue({
|
|
@@ -58,23 +49,17 @@ function makeRuntime(
|
|
|
58
49
|
origin: "global",
|
|
59
50
|
matchedPattern: "*",
|
|
60
51
|
}),
|
|
61
|
-
} as unknown as
|
|
52
|
+
} as unknown as SessionState["permissionManager"],
|
|
62
53
|
activeSkillEntries: [],
|
|
63
54
|
lastKnownActiveAgentName: null,
|
|
64
55
|
lastActiveToolsCacheKey: null,
|
|
65
56
|
lastPromptStateCacheKey: null,
|
|
66
|
-
lastConfigWarning: null,
|
|
67
57
|
sessionRules: {
|
|
68
58
|
approve: vi.fn(),
|
|
69
59
|
getRuleset: vi.fn().mockReturnValue([]),
|
|
70
60
|
clear: vi.fn(),
|
|
71
|
-
} as unknown as
|
|
72
|
-
|
|
73
|
-
permissionForwardingTimer: null,
|
|
74
|
-
isProcessingForwardedRequests: false,
|
|
75
|
-
writeDebugLog: vi.fn(),
|
|
76
|
-
writeReviewLog: vi.fn(),
|
|
77
|
-
} as ExtensionRuntime;
|
|
61
|
+
} as unknown as SessionState["sessionRules"],
|
|
62
|
+
};
|
|
78
63
|
}
|
|
79
64
|
|
|
80
65
|
function makeDeps(
|
|
@@ -82,7 +67,11 @@ function makeDeps(
|
|
|
82
67
|
overrides: Partial<HandlerDeps> = {},
|
|
83
68
|
): HandlerDeps {
|
|
84
69
|
return {
|
|
85
|
-
|
|
70
|
+
session: makeSession(state),
|
|
71
|
+
writeDebugLog: vi.fn(),
|
|
72
|
+
writeReviewLog: vi.fn(),
|
|
73
|
+
piInfrastructureDirs: ["/test/agent"],
|
|
74
|
+
getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
|
|
86
75
|
events: makeEvents(),
|
|
87
76
|
createPermissionManagerForCwd: vi.fn(),
|
|
88
77
|
refreshExtensionConfig: vi.fn(),
|