@gotgenes/pi-permission-system 5.8.0 → 5.10.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 +28 -0
- package/package.json +1 -1
- package/src/forwarding-manager.ts +76 -0
- package/src/handlers/before-agent-start.ts +19 -32
- package/src/handlers/input.ts +5 -5
- package/src/handlers/lifecycle.ts +17 -33
- package/src/handlers/tool-call.ts +11 -18
- package/src/handlers/types.ts +11 -38
- package/src/index.ts +15 -19
- package/src/permission-session.ts +252 -0
- package/src/runtime.ts +5 -96
- package/src/skill-prompt-sanitizer.ts +15 -4
- package/tests/forwarding-manager.test.ts +211 -0
- package/tests/handlers/before-agent-start.test.ts +79 -111
- package/tests/handlers/input-events.test.ts +19 -32
- package/tests/handlers/input.test.ts +41 -74
- package/tests/handlers/lifecycle.test.ts +61 -180
- package/tests/handlers/tool-call-events.test.ts +66 -93
- package/tests/handlers/tool-call.test.ts +40 -62
- package/tests/permission-session.test.ts +546 -0
- package/tests/runtime.test.ts +2 -92
|
@@ -3,8 +3,8 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
|
|
4
4
|
import { getEventInput, handleToolCall } from "../../src/handlers/tool-call";
|
|
5
5
|
import type { HandlerDeps } from "../../src/handlers/types";
|
|
6
|
-
import type {
|
|
7
|
-
import type { PermissionCheckResult } from "../../src/types";
|
|
6
|
+
import type { PermissionSession } from "../../src/permission-session";
|
|
7
|
+
import type { PermissionCheckResult, PermissionState } from "../../src/types";
|
|
8
8
|
|
|
9
9
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
10
10
|
vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
|
|
@@ -55,43 +55,35 @@ function makePermissionResult(
|
|
|
55
55
|
return { state, toolName: "read", source: "tool", origin: "builtin" };
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function makeSession(
|
|
58
|
+
function makeSession(
|
|
59
|
+
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
60
|
+
): PermissionSession {
|
|
59
61
|
return {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} as unknown as SessionState["sessionRules"],
|
|
62
|
+
logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
|
|
63
|
+
activate: vi.fn(),
|
|
64
|
+
resolveAgentName: vi.fn().mockReturnValue(null),
|
|
65
|
+
checkPermission: vi.fn().mockReturnValue(makePermissionResult("allow")),
|
|
66
|
+
getToolPermission: vi.fn().mockReturnValue("allow" as PermissionState),
|
|
67
|
+
getSessionRuleset: vi.fn().mockReturnValue([]),
|
|
68
|
+
approveSessionRule: vi.fn(),
|
|
69
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([]),
|
|
70
|
+
getInfrastructureDirs: vi
|
|
71
|
+
.fn()
|
|
72
|
+
.mockReturnValue(["/test/agent", "/test/agent/git"]),
|
|
73
|
+
getInfrastructureReadPaths: vi.fn().mockReturnValue([]),
|
|
73
74
|
...overrides,
|
|
74
|
-
};
|
|
75
|
+
} as unknown as PermissionSession;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
78
79
|
return {
|
|
79
80
|
session: makeSession(),
|
|
80
|
-
|
|
81
|
-
piInfrastructureDirs: ["/test/agent", "/test/agent/git"],
|
|
82
|
-
getPiInfrastructureReadPaths: vi.fn().mockReturnValue([]),
|
|
83
|
-
createPermissionManagerForCwd: vi.fn(),
|
|
84
|
-
refreshExtensionConfig: vi.fn(),
|
|
85
|
-
logResolvedConfigPaths: vi.fn(),
|
|
86
|
-
resolveAgentName: vi.fn().mockReturnValue(null),
|
|
81
|
+
events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
|
|
87
82
|
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|
|
88
83
|
promptPermission: vi
|
|
89
84
|
.fn()
|
|
90
85
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
91
86
|
createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
|
|
92
|
-
events: { emit: vi.fn(), on: vi.fn().mockReturnValue(() => undefined) },
|
|
93
|
-
startForwardedPermissionPolling: vi.fn(),
|
|
94
|
-
stopForwardedPermissionPolling: vi.fn(),
|
|
95
87
|
stopPermissionRpcHandlers: vi.fn(),
|
|
96
88
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }, { name: "bash" }]),
|
|
97
89
|
setActiveTools: vi.fn(),
|
|
@@ -128,23 +120,15 @@ describe("getEventInput", () => {
|
|
|
128
120
|
// ── handleToolCall ─────────────────────────────────────────────────────────
|
|
129
121
|
|
|
130
122
|
describe("handleToolCall", () => {
|
|
131
|
-
it("
|
|
132
|
-
const ctx = makeCtx();
|
|
133
|
-
const deps = makeDeps();
|
|
134
|
-
await handleToolCall(deps, makeToolCallEvent("read"), ctx);
|
|
135
|
-
expect(deps.session.runtimeContext).toBe(ctx);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("starts forwarded permission polling", async () => {
|
|
123
|
+
it("activates session with ctx", async () => {
|
|
139
124
|
const ctx = makeCtx();
|
|
140
125
|
const deps = makeDeps();
|
|
141
126
|
await handleToolCall(deps, makeToolCallEvent("read"), ctx);
|
|
142
|
-
expect(deps.
|
|
127
|
+
expect(deps.session.activate).toHaveBeenCalledWith(ctx);
|
|
143
128
|
});
|
|
144
129
|
|
|
145
130
|
it("blocks when tool name cannot be resolved", async () => {
|
|
146
131
|
const deps = makeDeps();
|
|
147
|
-
// An event with no recognisable name field
|
|
148
132
|
const result = await handleToolCall(deps, { type: "tool_call" }, makeCtx());
|
|
149
133
|
expect(result).toEqual({
|
|
150
134
|
block: true,
|
|
@@ -165,7 +149,6 @@ describe("handleToolCall", () => {
|
|
|
165
149
|
});
|
|
166
150
|
|
|
167
151
|
it("returns empty object when tool is allowed", async () => {
|
|
168
|
-
// default makeRuntime() has checkPermission → "allow"
|
|
169
152
|
const deps = makeDeps();
|
|
170
153
|
const result = await handleToolCall(
|
|
171
154
|
deps,
|
|
@@ -176,15 +159,10 @@ describe("handleToolCall", () => {
|
|
|
176
159
|
});
|
|
177
160
|
|
|
178
161
|
it("blocks when tool is denied by policy", async () => {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
permissionManager: {
|
|
182
|
-
checkPermission: vi
|
|
183
|
-
.fn()
|
|
184
|
-
.mockReturnValue(makePermissionResult("deny")),
|
|
185
|
-
} as unknown as SessionState["permissionManager"],
|
|
186
|
-
}),
|
|
162
|
+
const session = makeSession({
|
|
163
|
+
checkPermission: vi.fn().mockReturnValue(makePermissionResult("deny")),
|
|
187
164
|
});
|
|
165
|
+
const deps = makeDeps({ session });
|
|
188
166
|
const result = await handleToolCall(
|
|
189
167
|
deps,
|
|
190
168
|
makeToolCallEvent("read"),
|
|
@@ -206,8 +184,11 @@ describe("handleToolCall — skill-read gate", () => {
|
|
|
206
184
|
normalizedLocation: "/skills/librarian/SKILL.md",
|
|
207
185
|
normalizedBaseDir: "/skills/librarian",
|
|
208
186
|
};
|
|
187
|
+
const session = makeSession({
|
|
188
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([skillEntry]),
|
|
189
|
+
});
|
|
209
190
|
const deps = makeDeps({
|
|
210
|
-
session
|
|
191
|
+
session,
|
|
211
192
|
getAllTools: vi.fn().mockReturnValue([{ toolName: "read" }]),
|
|
212
193
|
});
|
|
213
194
|
const event = {
|
|
@@ -229,8 +210,11 @@ describe("handleToolCall — skill-read gate", () => {
|
|
|
229
210
|
normalizedLocation: "/skills/librarian/SKILL.md",
|
|
230
211
|
normalizedBaseDir: "/skills/librarian",
|
|
231
212
|
};
|
|
213
|
+
const session = makeSession({
|
|
214
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([skillEntry]),
|
|
215
|
+
});
|
|
232
216
|
const deps = makeDeps({
|
|
233
|
-
session
|
|
217
|
+
session,
|
|
234
218
|
getAllTools: vi.fn().mockReturnValue([{ toolName: "read" }]),
|
|
235
219
|
});
|
|
236
220
|
const event = {
|
|
@@ -248,14 +232,11 @@ describe("handleToolCall — skill-read gate", () => {
|
|
|
248
232
|
|
|
249
233
|
describe("handleToolCall — external-directory gate", () => {
|
|
250
234
|
it("blocks a read of a path outside cwd when policy is deny", async () => {
|
|
235
|
+
const session = makeSession({
|
|
236
|
+
checkPermission: vi.fn().mockReturnValue(makePermissionResult("deny")),
|
|
237
|
+
});
|
|
251
238
|
const deps = makeDeps({
|
|
252
|
-
session
|
|
253
|
-
permissionManager: {
|
|
254
|
-
checkPermission: vi
|
|
255
|
-
.fn()
|
|
256
|
-
.mockReturnValue(makePermissionResult("deny")),
|
|
257
|
-
} as unknown as SessionState["permissionManager"],
|
|
258
|
-
}),
|
|
239
|
+
session,
|
|
259
240
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
260
241
|
});
|
|
261
242
|
const event = {
|
|
@@ -273,14 +254,11 @@ describe("handleToolCall — external-directory gate", () => {
|
|
|
273
254
|
|
|
274
255
|
describe("handleToolCall — bash external-directory gate", () => {
|
|
275
256
|
it("blocks a bash command referencing an external path when policy is deny", async () => {
|
|
257
|
+
const session = makeSession({
|
|
258
|
+
checkPermission: vi.fn().mockReturnValue(makePermissionResult("deny")),
|
|
259
|
+
});
|
|
276
260
|
const deps = makeDeps({
|
|
277
|
-
session
|
|
278
|
-
permissionManager: {
|
|
279
|
-
checkPermission: vi
|
|
280
|
-
.fn()
|
|
281
|
-
.mockReturnValue(makePermissionResult("deny")),
|
|
282
|
-
} as unknown as SessionState["permissionManager"],
|
|
283
|
-
}),
|
|
261
|
+
session,
|
|
284
262
|
getAllTools: vi.fn().mockReturnValue([{ name: "bash" }]),
|
|
285
263
|
});
|
|
286
264
|
const event = {
|