@gotgenes/pi-permission-system 3.7.0 → 3.9.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 +39 -0
- package/package.json +1 -1
- package/src/defaults.ts +60 -0
- package/src/forwarded-permissions/io.ts +47 -12
- package/src/forwarded-permissions/polling.ts +33 -11
- package/src/handlers/before-agent-start.ts +7 -7
- package/src/handlers/input.ts +7 -5
- package/src/handlers/lifecycle.ts +25 -24
- package/src/handlers/tool-call.ts +32 -22
- package/src/handlers/types.ts +7 -30
- package/src/index.ts +47 -417
- package/src/normalize.ts +70 -0
- package/src/permission-manager.ts +127 -254
- package/src/rule.ts +7 -23
- package/src/runtime.ts +484 -0
- package/src/types.ts +13 -18
- package/tests/defaults.test.ts +105 -0
- package/tests/forwarded-permissions/io.test.ts +135 -0
- package/tests/handlers/before-agent-start.test.ts +47 -31
- package/tests/handlers/input.test.ts +69 -39
- package/tests/handlers/lifecycle.test.ts +86 -65
- package/tests/handlers/tool-call.test.ts +92 -69
- package/tests/normalize.test.ts +121 -0
- package/tests/permission-system.test.ts +11 -39
- package/tests/rule.test.ts +24 -42
- package/tests/runtime.test.ts +618 -0
- package/tests/session-start.test.ts +2 -2
- package/src/bash-filter.ts +0 -51
- package/tests/bash-filter.test.ts +0 -142
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import type { ForwardedPermissionLogger } from "../../src/forwarded-permissions/io";
|
|
4
|
+
import {
|
|
5
|
+
formatUnknownErrorMessage,
|
|
6
|
+
isErrnoCode,
|
|
7
|
+
logPermissionForwardingError,
|
|
8
|
+
logPermissionForwardingWarning,
|
|
9
|
+
} from "../../src/forwarded-permissions/io";
|
|
10
|
+
|
|
11
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
function makeLogger(): ForwardedPermissionLogger {
|
|
14
|
+
return {
|
|
15
|
+
writeReviewLog: vi.fn(),
|
|
16
|
+
writeDebugLog: vi.fn(),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── formatUnknownErrorMessage ──────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
describe("formatUnknownErrorMessage", () => {
|
|
23
|
+
it("returns the error message for Error instances", () => {
|
|
24
|
+
expect(formatUnknownErrorMessage(new Error("oops"))).toBe("oops");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("converts non-Error values to string", () => {
|
|
28
|
+
expect(formatUnknownErrorMessage("raw string")).toBe("raw string");
|
|
29
|
+
expect(formatUnknownErrorMessage(42)).toBe("42");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("falls back to String(error) for Error with empty message", () => {
|
|
33
|
+
// error.message is falsy (""), so the function falls through to String(error)
|
|
34
|
+
const e = new Error("");
|
|
35
|
+
expect(formatUnknownErrorMessage(e)).toBe("Error");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// ── isErrnoCode ────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
describe("isErrnoCode", () => {
|
|
42
|
+
it("returns true when code matches", () => {
|
|
43
|
+
expect(isErrnoCode({ code: "ENOENT" }, "ENOENT")).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns false when code does not match", () => {
|
|
47
|
+
expect(isErrnoCode({ code: "EACCES" }, "ENOENT")).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("returns false for null", () => {
|
|
51
|
+
expect(isErrnoCode(null, "ENOENT")).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns false when no code property", () => {
|
|
55
|
+
expect(isErrnoCode({}, "ENOENT")).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// ── logPermissionForwardingWarning ─────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
describe("logPermissionForwardingWarning", () => {
|
|
62
|
+
it("calls logger.writeReviewLog with the warning event", () => {
|
|
63
|
+
const logger = makeLogger();
|
|
64
|
+
logPermissionForwardingWarning(logger, "something went wrong");
|
|
65
|
+
expect(logger.writeReviewLog).toHaveBeenCalledWith(
|
|
66
|
+
"permission_forwarding.warning",
|
|
67
|
+
{ message: "something went wrong" },
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("calls logger.writeDebugLog with the warning event", () => {
|
|
72
|
+
const logger = makeLogger();
|
|
73
|
+
logPermissionForwardingWarning(logger, "something went wrong");
|
|
74
|
+
expect(logger.writeDebugLog).toHaveBeenCalledWith(
|
|
75
|
+
"permission_forwarding.warning",
|
|
76
|
+
{ message: "something went wrong" },
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("includes formatted error when an error is provided", () => {
|
|
81
|
+
const logger = makeLogger();
|
|
82
|
+
logPermissionForwardingWarning(logger, "bad thing", new Error("fs fail"));
|
|
83
|
+
expect(logger.writeReviewLog).toHaveBeenCalledWith(
|
|
84
|
+
"permission_forwarding.warning",
|
|
85
|
+
{ message: "bad thing", error: "fs fail" },
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("does not throw when logger is null", () => {
|
|
90
|
+
expect(() => logPermissionForwardingWarning(null, "ignored")).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("does not call anything when logger is null", () => {
|
|
94
|
+
// Verify the null-logger path is a true no-op — cannot easily spy on null,
|
|
95
|
+
// but we can verify the call succeeds silently.
|
|
96
|
+
expect(() =>
|
|
97
|
+
logPermissionForwardingWarning(null, "msg", new Error("err")),
|
|
98
|
+
).not.toThrow();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// ── logPermissionForwardingError ───────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
describe("logPermissionForwardingError", () => {
|
|
105
|
+
it("calls logger.writeReviewLog with the error event", () => {
|
|
106
|
+
const logger = makeLogger();
|
|
107
|
+
logPermissionForwardingError(logger, "critical failure");
|
|
108
|
+
expect(logger.writeReviewLog).toHaveBeenCalledWith(
|
|
109
|
+
"permission_forwarding.error",
|
|
110
|
+
{ message: "critical failure" },
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("calls logger.writeDebugLog with the error event", () => {
|
|
115
|
+
const logger = makeLogger();
|
|
116
|
+
logPermissionForwardingError(logger, "critical failure");
|
|
117
|
+
expect(logger.writeDebugLog).toHaveBeenCalledWith(
|
|
118
|
+
"permission_forwarding.error",
|
|
119
|
+
{ message: "critical failure" },
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("includes formatted error when an error is provided", () => {
|
|
124
|
+
const logger = makeLogger();
|
|
125
|
+
logPermissionForwardingError(logger, "io error", new Error("ENOENT"));
|
|
126
|
+
expect(logger.writeReviewLog).toHaveBeenCalledWith(
|
|
127
|
+
"permission_forwarding.error",
|
|
128
|
+
{ message: "io error", error: "ENOENT" },
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("does not throw when logger is null", () => {
|
|
133
|
+
expect(() => logPermissionForwardingError(null, "ignored")).not.toThrow();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
handleBeforeAgentStart,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "../../src/handlers/before-agent-start";
|
|
8
8
|
import type { HandlerDeps } from "../../src/handlers/types";
|
|
9
9
|
import type { PermissionManager } from "../../src/permission-manager";
|
|
10
|
+
import type { ExtensionRuntime } from "../../src/runtime";
|
|
10
11
|
import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
|
|
11
12
|
|
|
12
13
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
@@ -56,27 +57,41 @@ function makePm(
|
|
|
56
57
|
} as unknown as PermissionManager;
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
function
|
|
60
|
-
|
|
60
|
+
function makeRuntime(
|
|
61
|
+
overrides: Partial<ExtensionRuntime> = {},
|
|
62
|
+
): ExtensionRuntime {
|
|
61
63
|
return {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
agentDir: "/test/agent",
|
|
65
|
+
sessionsDir: "/test/agent/sessions",
|
|
66
|
+
subagentSessionsDir: "/test/agent/subagent-sessions",
|
|
67
|
+
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
|
68
|
+
globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
|
|
69
|
+
config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
|
|
70
|
+
runtimeContext: null,
|
|
71
|
+
permissionManager: makePm() as unknown as PermissionManager,
|
|
72
|
+
activeSkillEntries: [] as SkillPromptEntry[],
|
|
73
|
+
lastKnownActiveAgentName: null,
|
|
74
|
+
lastActiveToolsCacheKey: null,
|
|
75
|
+
lastPromptStateCacheKey: null,
|
|
76
|
+
lastConfigWarning: null,
|
|
74
77
|
sessionApprovalCache: {
|
|
75
78
|
approve: vi.fn(),
|
|
76
79
|
has: vi.fn(),
|
|
77
80
|
findMatchingPrefix: vi.fn(),
|
|
78
81
|
clear: vi.fn(),
|
|
79
|
-
} as unknown as
|
|
82
|
+
} as unknown as ExtensionRuntime["sessionApprovalCache"],
|
|
83
|
+
permissionForwardingContext: null,
|
|
84
|
+
permissionForwardingTimer: null,
|
|
85
|
+
isProcessingForwardedRequests: false,
|
|
86
|
+
writeDebugLog: vi.fn(),
|
|
87
|
+
writeReviewLog: vi.fn(),
|
|
88
|
+
...overrides,
|
|
89
|
+
} as ExtensionRuntime;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
93
|
+
return {
|
|
94
|
+
runtime: makeRuntime(),
|
|
80
95
|
createPermissionManagerForCwd: vi.fn().mockReturnValue(makePm()),
|
|
81
96
|
refreshExtensionConfig: vi.fn(),
|
|
82
97
|
notifyWarning: vi.fn(),
|
|
@@ -89,8 +104,6 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
|
89
104
|
createPermissionRequestId: vi.fn().mockReturnValue("test-id"),
|
|
90
105
|
startForwardedPermissionPolling: vi.fn(),
|
|
91
106
|
stopForwardedPermissionPolling: vi.fn(),
|
|
92
|
-
writeReviewLog: vi.fn(),
|
|
93
|
-
writeDebugLog: vi.fn(),
|
|
94
107
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
95
108
|
setActiveTools: vi.fn(),
|
|
96
109
|
...overrides,
|
|
@@ -162,7 +175,9 @@ describe("handleBeforeAgentStart", () => {
|
|
|
162
175
|
it("filters out denied tools from allowed list", async () => {
|
|
163
176
|
const pm = makePm("deny");
|
|
164
177
|
const deps = makeDeps({
|
|
165
|
-
|
|
178
|
+
runtime: makeRuntime({
|
|
179
|
+
permissionManager: pm as unknown as PermissionManager,
|
|
180
|
+
}),
|
|
166
181
|
getAllTools: vi
|
|
167
182
|
.fn()
|
|
168
183
|
.mockReturnValue([{ name: "write" }, { name: "read" }]),
|
|
@@ -175,7 +190,9 @@ describe("handleBeforeAgentStart", () => {
|
|
|
175
190
|
it("includes allowed and ask tools in the active list", async () => {
|
|
176
191
|
const pm = makePm("allow");
|
|
177
192
|
const deps = makeDeps({
|
|
178
|
-
|
|
193
|
+
runtime: makeRuntime({
|
|
194
|
+
permissionManager: pm as unknown as PermissionManager,
|
|
195
|
+
}),
|
|
179
196
|
getAllTools: vi
|
|
180
197
|
.fn()
|
|
181
198
|
.mockReturnValue([{ name: "read" }, { name: "write" }]),
|
|
@@ -187,10 +204,9 @@ describe("handleBeforeAgentStart", () => {
|
|
|
187
204
|
it("updates the active-tools cache key after applying", async () => {
|
|
188
205
|
const deps = makeDeps({
|
|
189
206
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
190
|
-
getLastActiveToolsCacheKey: vi.fn().mockReturnValue(null),
|
|
191
207
|
});
|
|
192
208
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
193
|
-
expect(deps.
|
|
209
|
+
expect(deps.runtime.lastActiveToolsCacheKey).not.toBeNull();
|
|
194
210
|
});
|
|
195
211
|
|
|
196
212
|
it("skips setActiveTools when cache key is unchanged", async () => {
|
|
@@ -200,8 +216,8 @@ describe("handleBeforeAgentStart", () => {
|
|
|
200
216
|
);
|
|
201
217
|
const key = createActiveToolsCacheKey(["read"]);
|
|
202
218
|
const deps = makeDeps({
|
|
219
|
+
runtime: makeRuntime({ lastActiveToolsCacheKey: key }),
|
|
203
220
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
204
|
-
getLastActiveToolsCacheKey: vi.fn().mockReturnValue(key),
|
|
205
221
|
});
|
|
206
222
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
207
223
|
expect(deps.setActiveTools).not.toHaveBeenCalled();
|
|
@@ -213,7 +229,6 @@ describe("handleBeforeAgentStart", () => {
|
|
|
213
229
|
const systemPrompt = `You are an assistant.\n\nAvailable tools:\n- read\n- write\n`;
|
|
214
230
|
const deps = makeDeps({
|
|
215
231
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
216
|
-
getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
|
|
217
232
|
});
|
|
218
233
|
const result = await handleBeforeAgentStart(
|
|
219
234
|
deps,
|
|
@@ -222,14 +237,13 @@ describe("handleBeforeAgentStart", () => {
|
|
|
222
237
|
);
|
|
223
238
|
// The prompt was modified, so systemPrompt should be returned
|
|
224
239
|
expect(result).toHaveProperty("systemPrompt");
|
|
225
|
-
expect(deps.
|
|
240
|
+
expect(deps.runtime.lastPromptStateCacheKey).not.toBeNull();
|
|
226
241
|
});
|
|
227
242
|
|
|
228
243
|
it("returns empty object when systemPrompt is unchanged", async () => {
|
|
229
244
|
const prompt = "No tools section here.";
|
|
230
245
|
const deps = makeDeps({
|
|
231
246
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
232
|
-
getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
|
|
233
247
|
});
|
|
234
248
|
const result = await handleBeforeAgentStart(
|
|
235
249
|
deps,
|
|
@@ -242,10 +256,9 @@ describe("handleBeforeAgentStart", () => {
|
|
|
242
256
|
it("stores resolved skill entries on deps", async () => {
|
|
243
257
|
const deps = makeDeps({
|
|
244
258
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
245
|
-
getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
|
|
246
259
|
});
|
|
247
260
|
await handleBeforeAgentStart(deps, makeEvent(), makeCtx());
|
|
248
|
-
expect(deps.
|
|
261
|
+
expect(deps.runtime.activeSkillEntries).toEqual(expect.any(Array));
|
|
249
262
|
});
|
|
250
263
|
|
|
251
264
|
it("returns empty object and skips prompt work when prompt cache key is unchanged", async () => {
|
|
@@ -263,12 +276,15 @@ describe("handleBeforeAgentStart", () => {
|
|
|
263
276
|
allowedToolNames: allowedTools,
|
|
264
277
|
});
|
|
265
278
|
const deps = makeDeps({
|
|
266
|
-
|
|
279
|
+
runtime: makeRuntime({
|
|
280
|
+
permissionManager: pm as unknown as PermissionManager,
|
|
281
|
+
lastPromptStateCacheKey: key,
|
|
282
|
+
}),
|
|
267
283
|
getAllTools: vi.fn().mockReturnValue([{ name: "read" }]),
|
|
268
|
-
getLastPromptStateCacheKey: vi.fn().mockReturnValue(key),
|
|
269
284
|
});
|
|
270
285
|
const result = await handleBeforeAgentStart(deps, makeEvent("hello"), ctx);
|
|
271
286
|
expect(result).toEqual({});
|
|
272
|
-
|
|
287
|
+
// activeSkillEntries was not assigned by the handler (early return)
|
|
288
|
+
expect(deps.runtime.activeSkillEntries).toEqual([]);
|
|
273
289
|
});
|
|
274
290
|
});
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
handleInput,
|
|
7
7
|
} from "../../src/handlers/input";
|
|
8
8
|
import type { HandlerDeps } from "../../src/handlers/types";
|
|
9
|
+
import type { ExtensionRuntime } from "../../src/runtime";
|
|
9
10
|
import type { SkillPromptEntry } from "../../src/skill-prompt-sanitizer";
|
|
10
11
|
|
|
11
12
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
@@ -33,29 +34,43 @@ function makeInputEvent(text: string) {
|
|
|
33
34
|
return { text };
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
function
|
|
37
|
+
function makeRuntime(
|
|
38
|
+
overrides: Partial<ExtensionRuntime> = {},
|
|
39
|
+
): ExtensionRuntime {
|
|
37
40
|
return {
|
|
38
|
-
|
|
41
|
+
agentDir: "/test/agent",
|
|
42
|
+
sessionsDir: "/test/agent/sessions",
|
|
43
|
+
subagentSessionsDir: "/test/agent/subagent-sessions",
|
|
44
|
+
forwardingDir: "/test/agent/sessions/permission-forwarding",
|
|
45
|
+
globalLogsDir: "/test/agent/extensions/pi-permission-system/logs",
|
|
46
|
+
config: { debugLog: false, permissionReviewLog: true, yoloMode: false },
|
|
47
|
+
runtimeContext: null,
|
|
48
|
+
permissionManager: {
|
|
39
49
|
checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
setActiveSkillEntries: vi.fn(),
|
|
47
|
-
getLastKnownActiveAgentName: vi.fn().mockReturnValue(null),
|
|
48
|
-
setLastKnownActiveAgentName: vi.fn(),
|
|
49
|
-
getLastActiveToolsCacheKey: vi.fn().mockReturnValue(null),
|
|
50
|
-
setLastActiveToolsCacheKey: vi.fn(),
|
|
51
|
-
getLastPromptStateCacheKey: vi.fn().mockReturnValue(null),
|
|
52
|
-
setLastPromptStateCacheKey: vi.fn(),
|
|
50
|
+
} as unknown as ExtensionRuntime["permissionManager"],
|
|
51
|
+
activeSkillEntries: [] as SkillPromptEntry[],
|
|
52
|
+
lastKnownActiveAgentName: null,
|
|
53
|
+
lastActiveToolsCacheKey: null,
|
|
54
|
+
lastPromptStateCacheKey: null,
|
|
55
|
+
lastConfigWarning: null,
|
|
53
56
|
sessionApprovalCache: {
|
|
54
57
|
approve: vi.fn(),
|
|
55
58
|
has: vi.fn(),
|
|
56
59
|
findMatchingPrefix: vi.fn(),
|
|
57
60
|
clear: vi.fn(),
|
|
58
|
-
} as unknown as
|
|
61
|
+
} as unknown as ExtensionRuntime["sessionApprovalCache"],
|
|
62
|
+
permissionForwardingContext: null,
|
|
63
|
+
permissionForwardingTimer: null,
|
|
64
|
+
isProcessingForwardedRequests: false,
|
|
65
|
+
writeDebugLog: vi.fn(),
|
|
66
|
+
writeReviewLog: vi.fn(),
|
|
67
|
+
...overrides,
|
|
68
|
+
} as ExtensionRuntime;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
72
|
+
return {
|
|
73
|
+
runtime: makeRuntime(),
|
|
59
74
|
createPermissionManagerForCwd: vi.fn(),
|
|
60
75
|
refreshExtensionConfig: vi.fn(),
|
|
61
76
|
notifyWarning: vi.fn(),
|
|
@@ -68,8 +83,6 @@ function makeDeps(overrides: Partial<HandlerDeps> = {}): HandlerDeps {
|
|
|
68
83
|
createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
|
|
69
84
|
startForwardedPermissionPolling: vi.fn(),
|
|
70
85
|
stopForwardedPermissionPolling: vi.fn(),
|
|
71
|
-
writeReviewLog: vi.fn(),
|
|
72
|
-
writeDebugLog: vi.fn(),
|
|
73
86
|
getAllTools: vi.fn().mockReturnValue([]),
|
|
74
87
|
setActiveTools: vi.fn(),
|
|
75
88
|
...overrides,
|
|
@@ -117,7 +130,7 @@ describe("handleInput", () => {
|
|
|
117
130
|
const ctx = makeCtx();
|
|
118
131
|
const deps = makeDeps();
|
|
119
132
|
await handleInput(deps, makeInputEvent("hello"), ctx);
|
|
120
|
-
expect(deps.
|
|
133
|
+
expect(deps.runtime.runtimeContext).toBe(ctx);
|
|
121
134
|
});
|
|
122
135
|
|
|
123
136
|
it("starts forwarded permission polling", async () => {
|
|
@@ -140,15 +153,14 @@ describe("handleInput", () => {
|
|
|
140
153
|
it("does not check permissions for non-skill input", async () => {
|
|
141
154
|
const deps = makeDeps();
|
|
142
155
|
await handleInput(deps, makeInputEvent("just a message"), makeCtx());
|
|
143
|
-
expect(
|
|
156
|
+
expect(
|
|
157
|
+
deps.runtime.permissionManager.checkPermission,
|
|
158
|
+
).not.toHaveBeenCalled();
|
|
144
159
|
});
|
|
145
160
|
|
|
146
161
|
it("returns continue when skill is allowed", async () => {
|
|
147
|
-
const deps = makeDeps(
|
|
148
|
-
|
|
149
|
-
checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
|
|
150
|
-
}),
|
|
151
|
-
});
|
|
162
|
+
const deps = makeDeps();
|
|
163
|
+
// default makeRuntime() has checkPermission → { state: "allow" }
|
|
152
164
|
const result = await handleInput(
|
|
153
165
|
deps,
|
|
154
166
|
makeInputEvent("/skill:librarian"),
|
|
@@ -158,9 +170,13 @@ describe("handleInput", () => {
|
|
|
158
170
|
});
|
|
159
171
|
|
|
160
172
|
it("returns handled when skill is denied", async () => {
|
|
173
|
+
const pm = {
|
|
174
|
+
checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
|
|
175
|
+
};
|
|
161
176
|
const deps = makeDeps({
|
|
162
|
-
|
|
163
|
-
|
|
177
|
+
runtime: makeRuntime({
|
|
178
|
+
permissionManager:
|
|
179
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
164
180
|
}),
|
|
165
181
|
});
|
|
166
182
|
const result = await handleInput(
|
|
@@ -173,9 +189,13 @@ describe("handleInput", () => {
|
|
|
173
189
|
|
|
174
190
|
it("shows a warning notification when skill is denied and UI is available", async () => {
|
|
175
191
|
const ctx = makeCtx({ hasUI: true });
|
|
192
|
+
const pm = {
|
|
193
|
+
checkPermission: vi.fn().mockReturnValue({ state: "deny" }),
|
|
194
|
+
};
|
|
176
195
|
const deps = makeDeps({
|
|
177
|
-
|
|
178
|
-
|
|
196
|
+
runtime: makeRuntime({
|
|
197
|
+
permissionManager:
|
|
198
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
179
199
|
}),
|
|
180
200
|
});
|
|
181
201
|
await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
|
|
@@ -187,9 +207,11 @@ describe("handleInput", () => {
|
|
|
187
207
|
|
|
188
208
|
it("does not show a warning notification when skill is denied and UI is absent", async () => {
|
|
189
209
|
const ctx = makeCtx({ hasUI: false });
|
|
210
|
+
const pm = { checkPermission: vi.fn().mockReturnValue({ state: "deny" }) };
|
|
190
211
|
const deps = makeDeps({
|
|
191
|
-
|
|
192
|
-
|
|
212
|
+
runtime: makeRuntime({
|
|
213
|
+
permissionManager:
|
|
214
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
193
215
|
}),
|
|
194
216
|
});
|
|
195
217
|
await handleInput(deps, makeInputEvent("/skill:librarian"), ctx);
|
|
@@ -197,9 +219,11 @@ describe("handleInput", () => {
|
|
|
197
219
|
});
|
|
198
220
|
|
|
199
221
|
it("returns handled when skill requires approval but no UI is available", async () => {
|
|
222
|
+
const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
|
|
200
223
|
const deps = makeDeps({
|
|
201
|
-
|
|
202
|
-
|
|
224
|
+
runtime: makeRuntime({
|
|
225
|
+
permissionManager:
|
|
226
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
203
227
|
}),
|
|
204
228
|
canRequestPermissionConfirmation: vi.fn().mockReturnValue(false),
|
|
205
229
|
});
|
|
@@ -212,9 +236,11 @@ describe("handleInput", () => {
|
|
|
212
236
|
});
|
|
213
237
|
|
|
214
238
|
it("prompts and returns continue when skill ask is approved", async () => {
|
|
239
|
+
const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
|
|
215
240
|
const deps = makeDeps({
|
|
216
|
-
|
|
217
|
-
|
|
241
|
+
runtime: makeRuntime({
|
|
242
|
+
permissionManager:
|
|
243
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
218
244
|
}),
|
|
219
245
|
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|
|
220
246
|
promptPermission: vi
|
|
@@ -231,9 +257,11 @@ describe("handleInput", () => {
|
|
|
231
257
|
});
|
|
232
258
|
|
|
233
259
|
it("returns handled when skill ask is denied by user", async () => {
|
|
260
|
+
const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
|
|
234
261
|
const deps = makeDeps({
|
|
235
|
-
|
|
236
|
-
|
|
262
|
+
runtime: makeRuntime({
|
|
263
|
+
permissionManager:
|
|
264
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
237
265
|
}),
|
|
238
266
|
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|
|
239
267
|
promptPermission: vi
|
|
@@ -249,9 +277,11 @@ describe("handleInput", () => {
|
|
|
249
277
|
});
|
|
250
278
|
|
|
251
279
|
it("passes agentName in the prompt permission request", async () => {
|
|
280
|
+
const pm = { checkPermission: vi.fn().mockReturnValue({ state: "ask" }) };
|
|
252
281
|
const deps = makeDeps({
|
|
253
|
-
|
|
254
|
-
|
|
282
|
+
runtime: makeRuntime({
|
|
283
|
+
permissionManager:
|
|
284
|
+
pm as unknown as ExtensionRuntime["permissionManager"],
|
|
255
285
|
}),
|
|
256
286
|
resolveAgentName: vi.fn().mockReturnValue("code-agent"),
|
|
257
287
|
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|