@gotgenes/pi-permission-system 8.2.0 → 8.2.1
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 +7 -0
- package/package.json +1 -1
- 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/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-session.test.ts +1 -19
- package/test/permission-system.test.ts +4 -40
|
@@ -1,99 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tests that handleInput emits permissions:decision events for skill input gates.
|
|
3
3
|
*/
|
|
4
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
5
4
|
import { describe, expect, it, vi } from "vitest";
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
import {
|
|
7
|
+
getDecisionEvents,
|
|
8
|
+
makeCheckResult,
|
|
9
|
+
makeCtx,
|
|
10
|
+
makeHandler,
|
|
11
|
+
} from "#test/helpers/handler-fixtures";
|
|
12
12
|
|
|
13
13
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
23
|
-
return {
|
|
24
|
-
cwd: "/test/project",
|
|
25
|
-
hasUI: true,
|
|
26
|
-
ui: {
|
|
27
|
-
setStatus: vi.fn(),
|
|
28
|
-
notify: vi.fn(),
|
|
29
|
-
select: vi.fn(),
|
|
30
|
-
input: vi.fn(),
|
|
31
|
-
},
|
|
32
|
-
sessionManager: {
|
|
33
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
34
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
35
|
-
addEntry: vi.fn(),
|
|
36
|
-
},
|
|
37
|
-
...overrides,
|
|
38
|
-
} as unknown as ExtensionContext;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function makeSession(
|
|
42
|
-
state: "allow" | "deny" | "ask" = "allow",
|
|
43
|
-
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
44
|
-
): PermissionSession {
|
|
45
|
-
return {
|
|
46
|
-
logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
|
|
47
|
-
activate: vi.fn(),
|
|
48
|
-
resolveAgentName: vi.fn().mockReturnValue(null),
|
|
49
|
-
checkPermission: vi.fn().mockReturnValue({
|
|
15
|
+
/** Build a checkPermission mock returning a skill-surface result. */
|
|
16
|
+
function makeSkillCheckPermission(state: "allow" | "deny" | "ask") {
|
|
17
|
+
return vi.fn().mockReturnValue(
|
|
18
|
+
makeCheckResult({
|
|
50
19
|
state,
|
|
51
20
|
toolName: "skill",
|
|
52
21
|
source: "skill",
|
|
53
22
|
origin: "global",
|
|
54
23
|
matchedPattern: "*",
|
|
55
24
|
}),
|
|
56
|
-
getToolPermission: vi.fn().mockReturnValue("allow"),
|
|
57
|
-
getSessionRuleset: vi.fn().mockReturnValue([]),
|
|
58
|
-
recordSessionApproval: vi.fn(),
|
|
59
|
-
canPrompt: vi.fn().mockReturnValue(true),
|
|
60
|
-
prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
|
|
61
|
-
createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
|
|
62
|
-
...overrides,
|
|
63
|
-
} as unknown as PermissionSession;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function makeToolRegistry(): ToolRegistry {
|
|
67
|
-
return {
|
|
68
|
-
getAll: vi.fn().mockReturnValue([]),
|
|
69
|
-
setActive: vi.fn(),
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function makeHandler(
|
|
74
|
-
state: "allow" | "deny" | "ask" = "allow",
|
|
75
|
-
sessionOverrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
76
|
-
): {
|
|
77
|
-
handler: PermissionGateHandler;
|
|
78
|
-
events: ReturnType<typeof makeEvents>;
|
|
79
|
-
} {
|
|
80
|
-
const session = makeSession(state, sessionOverrides);
|
|
81
|
-
const events = makeEvents();
|
|
82
|
-
const handler = new PermissionGateHandler(
|
|
83
|
-
session,
|
|
84
|
-
events,
|
|
85
|
-
makeToolRegistry(),
|
|
86
25
|
);
|
|
87
|
-
return { handler, events };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Extract all permissions:decision payloads from the events.emit mock. */
|
|
91
|
-
function getDecisionEvents(
|
|
92
|
-
events: ReturnType<typeof makeEvents>,
|
|
93
|
-
): PermissionDecisionEvent[] {
|
|
94
|
-
return events.emit.mock.calls
|
|
95
|
-
.filter(([channel]) => channel === PERMISSIONS_DECISION_CHANNEL)
|
|
96
|
-
.map(([, payload]) => payload as PermissionDecisionEvent);
|
|
97
26
|
}
|
|
98
27
|
|
|
99
28
|
// ── tests ──────────────────────────────────────────────────────────────────
|
|
@@ -106,7 +35,9 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
106
35
|
});
|
|
107
36
|
|
|
108
37
|
it("emits allow with policy_allow for an allowed skill", async () => {
|
|
109
|
-
const { handler, events } = makeHandler(
|
|
38
|
+
const { handler, events } = makeHandler({
|
|
39
|
+
session: { checkPermission: makeSkillCheckPermission("allow") },
|
|
40
|
+
});
|
|
110
41
|
await handler.handleInput({ text: "/skill:librarian" }, makeCtx());
|
|
111
42
|
|
|
112
43
|
const decisions = getDecisionEvents(events);
|
|
@@ -120,7 +51,9 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
120
51
|
});
|
|
121
52
|
|
|
122
53
|
it("emits deny with policy_deny for a denied skill", async () => {
|
|
123
|
-
const { handler, events } = makeHandler(
|
|
54
|
+
const { handler, events } = makeHandler({
|
|
55
|
+
session: { checkPermission: makeSkillCheckPermission("deny") },
|
|
56
|
+
});
|
|
124
57
|
await handler.handleInput({ text: "/skill:restricted" }, makeCtx());
|
|
125
58
|
|
|
126
59
|
const decisions = getDecisionEvents(events);
|
|
@@ -134,8 +67,13 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
134
67
|
});
|
|
135
68
|
|
|
136
69
|
it("emits allow with user_approved when state=ask and user approves", async () => {
|
|
137
|
-
const { handler, events } = makeHandler(
|
|
138
|
-
|
|
70
|
+
const { handler, events } = makeHandler({
|
|
71
|
+
session: {
|
|
72
|
+
checkPermission: makeSkillCheckPermission("ask"),
|
|
73
|
+
prompt: vi
|
|
74
|
+
.fn()
|
|
75
|
+
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
76
|
+
},
|
|
139
77
|
});
|
|
140
78
|
await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
|
|
141
79
|
|
|
@@ -150,8 +88,11 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
150
88
|
});
|
|
151
89
|
|
|
152
90
|
it("emits deny with user_denied when state=ask and user denies", async () => {
|
|
153
|
-
const { handler, events } = makeHandler(
|
|
154
|
-
|
|
91
|
+
const { handler, events } = makeHandler({
|
|
92
|
+
session: {
|
|
93
|
+
checkPermission: makeSkillCheckPermission("ask"),
|
|
94
|
+
prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
|
|
95
|
+
},
|
|
155
96
|
});
|
|
156
97
|
await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
|
|
157
98
|
|
|
@@ -166,8 +107,11 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
166
107
|
});
|
|
167
108
|
|
|
168
109
|
it("emits deny with confirmation_unavailable when state=ask but no UI", async () => {
|
|
169
|
-
const { handler, events } = makeHandler(
|
|
170
|
-
|
|
110
|
+
const { handler, events } = makeHandler({
|
|
111
|
+
session: {
|
|
112
|
+
checkPermission: makeSkillCheckPermission("ask"),
|
|
113
|
+
canPrompt: vi.fn().mockReturnValue(false),
|
|
114
|
+
},
|
|
171
115
|
});
|
|
172
116
|
await handler.handleInput(
|
|
173
117
|
{ text: "/skill:explorer" },
|
|
@@ -185,12 +129,15 @@ describe("handleInput decision events — skill gate", () => {
|
|
|
185
129
|
});
|
|
186
130
|
|
|
187
131
|
it("emits allow with auto_approved when prompt returns autoApproved:true", async () => {
|
|
188
|
-
const { handler, events } = makeHandler(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
132
|
+
const { handler, events } = makeHandler({
|
|
133
|
+
session: {
|
|
134
|
+
checkPermission: makeSkillCheckPermission("ask"),
|
|
135
|
+
prompt: vi.fn().mockResolvedValue({
|
|
136
|
+
approved: true,
|
|
137
|
+
state: "approved",
|
|
138
|
+
autoApproved: true,
|
|
139
|
+
}),
|
|
140
|
+
},
|
|
194
141
|
});
|
|
195
142
|
await handler.handleInput({ text: "/skill:explorer" }, makeCtx());
|
|
196
143
|
|
|
@@ -1,83 +1,15 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
1
|
import { describe, expect, it, vi } from "vitest";
|
|
3
2
|
|
|
4
|
-
import {
|
|
5
|
-
extractSkillNameFromInput,
|
|
6
|
-
PermissionGateHandler,
|
|
7
|
-
} from "#src/handlers/permission-gate-handler";
|
|
8
|
-
import type { PermissionSession } from "#src/permission-session";
|
|
9
|
-
import type { ToolRegistry } from "#src/tool-registry";
|
|
3
|
+
import { extractSkillNameFromInput } from "#src/handlers/permission-gate-handler";
|
|
10
4
|
|
|
11
|
-
|
|
5
|
+
import { makeCtx, makeHandler } from "#test/helpers/handler-fixtures";
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
cwd: "/test/project",
|
|
16
|
-
hasUI: true,
|
|
17
|
-
ui: {
|
|
18
|
-
setStatus: vi.fn(),
|
|
19
|
-
notify: vi.fn(),
|
|
20
|
-
select: vi.fn(),
|
|
21
|
-
input: vi.fn(),
|
|
22
|
-
},
|
|
23
|
-
sessionManager: {
|
|
24
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
25
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
26
|
-
addEntry: vi.fn(),
|
|
27
|
-
},
|
|
28
|
-
...overrides,
|
|
29
|
-
} as unknown as ExtensionContext;
|
|
30
|
-
}
|
|
7
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
31
8
|
|
|
32
9
|
function makeInputEvent(text: string) {
|
|
33
10
|
return { text };
|
|
34
11
|
}
|
|
35
12
|
|
|
36
|
-
function makeSession(
|
|
37
|
-
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
38
|
-
): PermissionSession {
|
|
39
|
-
return {
|
|
40
|
-
logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
|
|
41
|
-
activate: vi.fn(),
|
|
42
|
-
resolveAgentName: vi.fn().mockReturnValue(null),
|
|
43
|
-
checkPermission: vi.fn().mockReturnValue({ state: "allow" }),
|
|
44
|
-
getToolPermission: vi.fn().mockReturnValue("allow"),
|
|
45
|
-
getSessionRuleset: vi.fn().mockReturnValue([]),
|
|
46
|
-
recordSessionApproval: vi.fn(),
|
|
47
|
-
canPrompt: vi.fn().mockReturnValue(true),
|
|
48
|
-
prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
|
|
49
|
-
createPermissionRequestId: vi.fn().mockReturnValue("req-id"),
|
|
50
|
-
...overrides,
|
|
51
|
-
} as unknown as PermissionSession;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function makeEvents() {
|
|
55
|
-
return {
|
|
56
|
-
emit: vi.fn(),
|
|
57
|
-
on: vi.fn().mockReturnValue(() => undefined),
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function makeToolRegistry(): ToolRegistry {
|
|
62
|
-
return {
|
|
63
|
-
getAll: vi.fn().mockReturnValue([]),
|
|
64
|
-
setActive: vi.fn(),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function makeHandler(overrides?: {
|
|
69
|
-
session?: Partial<Record<keyof PermissionSession, unknown>>;
|
|
70
|
-
}): {
|
|
71
|
-
handler: PermissionGateHandler;
|
|
72
|
-
session: PermissionSession;
|
|
73
|
-
} {
|
|
74
|
-
const session = makeSession(overrides?.session);
|
|
75
|
-
const events = makeEvents();
|
|
76
|
-
const toolRegistry = makeToolRegistry();
|
|
77
|
-
const handler = new PermissionGateHandler(session, events, toolRegistry);
|
|
78
|
-
return { handler, session };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
13
|
// ── extractSkillNameFromInput ──────────────────────────────────────────────
|
|
82
14
|
|
|
83
15
|
describe("extractSkillNameFromInput", () => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
3
|
import { SessionLifecycleHandler } from "#src/handlers/lifecycle";
|
|
4
4
|
import type { PermissionSession } from "#src/permission-session";
|
|
5
5
|
|
|
6
|
+
import { makeCtx } from "#test/helpers/handler-fixtures";
|
|
7
|
+
|
|
6
8
|
// ── status stub ────────────────────────────────────────────────────────────
|
|
7
9
|
vi.mock("../../src/status", () => ({
|
|
8
10
|
PERMISSION_SYSTEM_STATUS_KEY: "permission-system",
|
|
@@ -12,25 +14,6 @@ vi.mock("../../src/status", () => ({
|
|
|
12
14
|
|
|
13
15
|
// ── helpers ────────────────────────────────────────────────────────────────
|
|
14
16
|
|
|
15
|
-
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
16
|
-
return {
|
|
17
|
-
cwd: "/test/project",
|
|
18
|
-
hasUI: true,
|
|
19
|
-
ui: {
|
|
20
|
-
setStatus: vi.fn(),
|
|
21
|
-
notify: vi.fn(),
|
|
22
|
-
select: vi.fn(),
|
|
23
|
-
input: vi.fn(),
|
|
24
|
-
},
|
|
25
|
-
sessionManager: {
|
|
26
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
27
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
28
|
-
addEntry: vi.fn(),
|
|
29
|
-
},
|
|
30
|
-
...overrides,
|
|
31
|
-
} as unknown as ExtensionContext;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
17
|
function makeSession(
|
|
35
18
|
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
36
19
|
): PermissionSession {
|
|
@@ -2,125 +2,15 @@
|
|
|
2
2
|
* Tests that handleToolCall emits permissions:decision events at every
|
|
3
3
|
* gate resolution and fast-path site.
|
|
4
4
|
*/
|
|
5
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
6
5
|
import { describe, expect, it, vi } from "vitest";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// ── helpers ────────────────────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
function makeEvents() {
|
|
18
|
-
return {
|
|
19
|
-
emit: vi.fn(),
|
|
20
|
-
on: vi.fn().mockReturnValue(() => undefined),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
|
|
25
|
-
return {
|
|
26
|
-
cwd: "/test/project",
|
|
27
|
-
hasUI: true,
|
|
28
|
-
ui: {
|
|
29
|
-
setStatus: vi.fn(),
|
|
30
|
-
notify: vi.fn(),
|
|
31
|
-
select: vi.fn(),
|
|
32
|
-
input: vi.fn(),
|
|
33
|
-
},
|
|
34
|
-
sessionManager: {
|
|
35
|
-
getEntries: vi.fn().mockReturnValue([]),
|
|
36
|
-
getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
|
|
37
|
-
addEntry: vi.fn(),
|
|
38
|
-
},
|
|
39
|
-
...overrides,
|
|
40
|
-
} as unknown as ExtensionContext;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function makeToolCallEvent(
|
|
44
|
-
toolName: string,
|
|
45
|
-
extraFields: Record<string, unknown> = {},
|
|
46
|
-
) {
|
|
47
|
-
return {
|
|
48
|
-
type: "tool_call",
|
|
49
|
-
toolCallId: "tc-1",
|
|
50
|
-
name: toolName,
|
|
51
|
-
input: {},
|
|
52
|
-
...extraFields,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function makeCheckResult(
|
|
57
|
-
state: "allow" | "deny" | "ask",
|
|
58
|
-
overrides: Partial<PermissionCheckResult> = {},
|
|
59
|
-
): PermissionCheckResult {
|
|
60
|
-
return {
|
|
61
|
-
state,
|
|
62
|
-
toolName: "read",
|
|
63
|
-
source: "tool",
|
|
64
|
-
origin: "builtin",
|
|
65
|
-
matchedPattern: "*",
|
|
66
|
-
...overrides,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function makeSession(
|
|
71
|
-
overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
|
|
72
|
-
): PermissionSession {
|
|
73
|
-
return {
|
|
74
|
-
logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
|
|
75
|
-
activate: vi.fn(),
|
|
76
|
-
resolveAgentName: vi.fn().mockReturnValue(null),
|
|
77
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult("allow")),
|
|
78
|
-
getToolPermission: vi.fn().mockReturnValue("allow"),
|
|
79
|
-
getSessionRuleset: vi.fn().mockReturnValue([]),
|
|
80
|
-
recordSessionApproval: vi.fn(),
|
|
81
|
-
getActiveSkillEntries: vi.fn().mockReturnValue([]),
|
|
82
|
-
getInfrastructureDirs: vi
|
|
83
|
-
.fn()
|
|
84
|
-
.mockReturnValue(["/test/agent", "/test/agent/git"]),
|
|
85
|
-
getInfrastructureReadPaths: vi.fn().mockReturnValue([]),
|
|
86
|
-
config: DEFAULT_EXTENSION_CONFIG,
|
|
87
|
-
canPrompt: vi.fn().mockReturnValue(true),
|
|
88
|
-
prompt: vi.fn().mockResolvedValue({ approved: true, state: "approved" }),
|
|
89
|
-
...overrides,
|
|
90
|
-
} as unknown as PermissionSession;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function makeToolRegistry(overrides: Partial<ToolRegistry> = {}): ToolRegistry {
|
|
94
|
-
return {
|
|
95
|
-
getAll: vi.fn().mockReturnValue([{ name: "read" }, { name: "bash" }]),
|
|
96
|
-
setActive: vi.fn(),
|
|
97
|
-
...overrides,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function makeHandler(overrides?: {
|
|
102
|
-
session?: Partial<Record<keyof PermissionSession, unknown>>;
|
|
103
|
-
toolRegistry?: Partial<ToolRegistry>;
|
|
104
|
-
}): {
|
|
105
|
-
handler: PermissionGateHandler;
|
|
106
|
-
events: ReturnType<typeof makeEvents>;
|
|
107
|
-
session: PermissionSession;
|
|
108
|
-
} {
|
|
109
|
-
const session = makeSession(overrides?.session);
|
|
110
|
-
const events = makeEvents();
|
|
111
|
-
const toolRegistry = makeToolRegistry(overrides?.toolRegistry);
|
|
112
|
-
const handler = new PermissionGateHandler(session, events, toolRegistry);
|
|
113
|
-
return { handler, events, session };
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Extract all permissions:decision payloads from the events.emit mock. */
|
|
117
|
-
function getDecisionEvents(
|
|
118
|
-
events: ReturnType<typeof makeEvents>,
|
|
119
|
-
): PermissionDecisionEvent[] {
|
|
120
|
-
return events.emit.mock.calls
|
|
121
|
-
.filter(([channel]) => channel === PERMISSIONS_DECISION_CHANNEL)
|
|
122
|
-
.map(([, payload]) => payload as PermissionDecisionEvent);
|
|
123
|
-
}
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
getDecisionEvents,
|
|
9
|
+
makeCheckResult,
|
|
10
|
+
makeCtx,
|
|
11
|
+
makeHandler,
|
|
12
|
+
makeToolCallEvent,
|
|
13
|
+
} from "#test/helpers/handler-fixtures";
|
|
124
14
|
|
|
125
15
|
// ── policy_allow path ──────────────────────────────────────────────────────
|
|
126
16
|
|
|
@@ -129,7 +19,8 @@ describe("handleToolCall decision events — policy_allow", () => {
|
|
|
129
19
|
const { handler, events } = makeHandler({
|
|
130
20
|
session: {
|
|
131
21
|
checkPermission: vi.fn().mockReturnValue(
|
|
132
|
-
makeCheckResult(
|
|
22
|
+
makeCheckResult({
|
|
23
|
+
state: "allow",
|
|
133
24
|
origin: "global",
|
|
134
25
|
matchedPattern: "*",
|
|
135
26
|
}),
|
|
@@ -158,7 +49,8 @@ describe("handleToolCall decision events — policy_deny", () => {
|
|
|
158
49
|
const { handler, events } = makeHandler({
|
|
159
50
|
session: {
|
|
160
51
|
checkPermission: vi.fn().mockReturnValue(
|
|
161
|
-
makeCheckResult(
|
|
52
|
+
makeCheckResult({
|
|
53
|
+
state: "deny",
|
|
162
54
|
origin: "project",
|
|
163
55
|
matchedPattern: "read",
|
|
164
56
|
}),
|
|
@@ -185,7 +77,8 @@ describe("handleToolCall decision events — session_approved", () => {
|
|
|
185
77
|
const { handler, events } = makeHandler({
|
|
186
78
|
session: {
|
|
187
79
|
checkPermission: vi.fn().mockReturnValue(
|
|
188
|
-
makeCheckResult(
|
|
80
|
+
makeCheckResult({
|
|
81
|
+
state: "allow",
|
|
189
82
|
source: "session",
|
|
190
83
|
matchedPattern: "git *",
|
|
191
84
|
}),
|
|
@@ -214,7 +107,9 @@ describe("handleToolCall decision events — user_approved", () => {
|
|
|
214
107
|
it("emits allow with user_approved when state=ask and user approves once", async () => {
|
|
215
108
|
const { handler, events } = makeHandler({
|
|
216
109
|
session: {
|
|
217
|
-
checkPermission: vi
|
|
110
|
+
checkPermission: vi
|
|
111
|
+
.fn()
|
|
112
|
+
.mockReturnValue(makeCheckResult({ state: "ask" })),
|
|
218
113
|
prompt: vi
|
|
219
114
|
.fn()
|
|
220
115
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
@@ -234,7 +129,9 @@ describe("handleToolCall decision events — user_approved", () => {
|
|
|
234
129
|
it("emits allow with user_approved_for_session when user approves for session", async () => {
|
|
235
130
|
const { handler, events } = makeHandler({
|
|
236
131
|
session: {
|
|
237
|
-
checkPermission: vi
|
|
132
|
+
checkPermission: vi
|
|
133
|
+
.fn()
|
|
134
|
+
.mockReturnValue(makeCheckResult({ state: "ask" })),
|
|
238
135
|
prompt: vi.fn().mockResolvedValue({
|
|
239
136
|
approved: true,
|
|
240
137
|
state: "approved_for_session",
|
|
@@ -259,7 +156,9 @@ describe("handleToolCall decision events — user_denied", () => {
|
|
|
259
156
|
it("emits deny with user_denied when state=ask and user denies", async () => {
|
|
260
157
|
const { handler, events } = makeHandler({
|
|
261
158
|
session: {
|
|
262
|
-
checkPermission: vi
|
|
159
|
+
checkPermission: vi
|
|
160
|
+
.fn()
|
|
161
|
+
.mockReturnValue(makeCheckResult({ state: "ask" })),
|
|
263
162
|
prompt: vi.fn().mockResolvedValue({ approved: false, state: "denied" }),
|
|
264
163
|
},
|
|
265
164
|
});
|
|
@@ -281,7 +180,9 @@ describe("handleToolCall decision events — confirmation_unavailable", () => {
|
|
|
281
180
|
it("emits deny with confirmation_unavailable when state=ask but no UI", async () => {
|
|
282
181
|
const { handler, events } = makeHandler({
|
|
283
182
|
session: {
|
|
284
|
-
checkPermission: vi
|
|
183
|
+
checkPermission: vi
|
|
184
|
+
.fn()
|
|
185
|
+
.mockReturnValue(makeCheckResult({ state: "ask" })),
|
|
285
186
|
canPrompt: vi.fn().mockReturnValue(false),
|
|
286
187
|
},
|
|
287
188
|
});
|
|
@@ -307,7 +208,7 @@ describe("handleToolCall decision events — infrastructure_auto_allowed", () =>
|
|
|
307
208
|
const infraDir = "/test/agent";
|
|
308
209
|
const { handler, events } = makeHandler({
|
|
309
210
|
session: {
|
|
310
|
-
checkPermission: vi.fn().mockReturnValue(makeCheckResult(
|
|
211
|
+
checkPermission: vi.fn().mockReturnValue(makeCheckResult()),
|
|
311
212
|
getInfrastructureDirs: vi.fn().mockReturnValue([infraDir]),
|
|
312
213
|
},
|
|
313
214
|
});
|
|
@@ -335,7 +236,9 @@ describe("handleToolCall decision events — auto_approved", () => {
|
|
|
335
236
|
it("emits allow with auto_approved when prompt returns autoApproved:true", async () => {
|
|
336
237
|
const { handler, events } = makeHandler({
|
|
337
238
|
session: {
|
|
338
|
-
checkPermission: vi
|
|
239
|
+
checkPermission: vi
|
|
240
|
+
.fn()
|
|
241
|
+
.mockReturnValue(makeCheckResult({ state: "ask" })),
|
|
339
242
|
prompt: vi.fn().mockResolvedValue({
|
|
340
243
|
approved: true,
|
|
341
244
|
state: "approved",
|