@gotgenes/pi-permission-system 5.5.0 → 5.5.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 +9 -0
- package/package.json +1 -1
- package/src/handlers/before-agent-start.ts +7 -7
- package/src/handlers/gates/bash-external-directory.ts +22 -24
- package/src/handlers/gates/external-directory.ts +32 -41
- package/src/handlers/gates/skill-read.ts +10 -12
- package/src/handlers/gates/tool.ts +20 -27
- package/src/handlers/gates/types.ts +75 -0
- package/src/handlers/input.ts +3 -3
- package/src/handlers/lifecycle.ts +21 -21
- package/src/handlers/tool-call.ts +77 -7
- 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 +48 -105
- package/tests/handlers/gates/external-directory.test.ts +65 -140
- package/tests/handlers/gates/skill-read.test.ts +50 -65
- package/tests/handlers/gates/tool.test.ts +90 -334
- 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,9 +1,10 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { evaluateSkillReadGate } from "../../../src/handlers/gates/skill-read";
|
|
4
|
-
import type {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import type {
|
|
5
|
+
SkillReadGateDeps,
|
|
6
|
+
ToolCallContext,
|
|
7
|
+
} from "../../../src/handlers/gates/types";
|
|
7
8
|
import type { SkillPromptEntry } from "../../../src/skill-prompt-sanitizer";
|
|
8
9
|
|
|
9
10
|
// ── SDK stubs ──────────────────────────────────────────────────────────────
|
|
@@ -40,31 +41,19 @@ function makeTcc(overrides: Partial<ToolCallContext> = {}): ToolCallContext {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
function makeRuntime(
|
|
48
|
-
overrides: Record<string, unknown> = {},
|
|
49
|
-
): HandlerDeps["runtime"] {
|
|
44
|
+
function makeSkillReadGateDeps(
|
|
45
|
+
overrides: Partial<SkillReadGateDeps> = {},
|
|
46
|
+
): SkillReadGateDeps {
|
|
50
47
|
return {
|
|
51
|
-
|
|
48
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([]),
|
|
52
49
|
writeReviewLog: vi.fn(),
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
|
|
58
|
-
const { runtime: runtimeOverrides, events, ...rest } = overrides;
|
|
59
|
-
return {
|
|
60
|
-
runtime: makeRuntime(runtimeOverrides as Record<string, unknown>),
|
|
61
|
-
events: events ?? makeEvents(),
|
|
62
|
-
canRequestPermissionConfirmation: vi.fn().mockReturnValue(true),
|
|
50
|
+
emitDecision: vi.fn(),
|
|
51
|
+
canConfirm: vi.fn().mockReturnValue(true),
|
|
63
52
|
promptPermission: vi
|
|
64
53
|
.fn()
|
|
65
54
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
66
|
-
...
|
|
67
|
-
}
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
68
57
|
}
|
|
69
58
|
|
|
70
59
|
// ── tests ──────────────────────────────────────────────────────────────────
|
|
@@ -72,8 +61,8 @@ function makeDeps(overrides: Record<string, unknown> = {}): HandlerDeps {
|
|
|
72
61
|
describe("evaluateSkillReadGate", () => {
|
|
73
62
|
it("returns null when tool is not read", async () => {
|
|
74
63
|
const tcc = makeTcc({ toolName: "write" });
|
|
75
|
-
const deps =
|
|
76
|
-
|
|
64
|
+
const deps = makeSkillReadGateDeps({
|
|
65
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([makeSkillEntry()]),
|
|
77
66
|
});
|
|
78
67
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
79
68
|
expect(result).toBeNull();
|
|
@@ -81,15 +70,17 @@ describe("evaluateSkillReadGate", () => {
|
|
|
81
70
|
|
|
82
71
|
it("returns null when no active skill entries", async () => {
|
|
83
72
|
const tcc = makeTcc();
|
|
84
|
-
const deps =
|
|
73
|
+
const deps = makeSkillReadGateDeps({
|
|
74
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([]),
|
|
75
|
+
});
|
|
85
76
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
86
77
|
expect(result).toBeNull();
|
|
87
78
|
});
|
|
88
79
|
|
|
89
80
|
it("returns null when read path does not match any skill", async () => {
|
|
90
81
|
const tcc = makeTcc({ input: { path: "/test/project/src/index.ts" } });
|
|
91
|
-
const deps =
|
|
92
|
-
|
|
82
|
+
const deps = makeSkillReadGateDeps({
|
|
83
|
+
getActiveSkillEntries: vi.fn().mockReturnValue([makeSkillEntry()]),
|
|
93
84
|
});
|
|
94
85
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
95
86
|
expect(result).toBeNull();
|
|
@@ -97,10 +88,10 @@ describe("evaluateSkillReadGate", () => {
|
|
|
97
88
|
|
|
98
89
|
it("returns allow when skill state is allow", async () => {
|
|
99
90
|
const tcc = makeTcc();
|
|
100
|
-
const deps =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
91
|
+
const deps = makeSkillReadGateDeps({
|
|
92
|
+
getActiveSkillEntries: vi
|
|
93
|
+
.fn()
|
|
94
|
+
.mockReturnValue([makeSkillEntry({ state: "allow" })]),
|
|
104
95
|
});
|
|
105
96
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
106
97
|
expect(result).toEqual({ action: "allow" });
|
|
@@ -108,10 +99,10 @@ describe("evaluateSkillReadGate", () => {
|
|
|
108
99
|
|
|
109
100
|
it("returns block when skill state is deny", async () => {
|
|
110
101
|
const tcc = makeTcc();
|
|
111
|
-
const deps =
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
102
|
+
const deps = makeSkillReadGateDeps({
|
|
103
|
+
getActiveSkillEntries: vi
|
|
104
|
+
.fn()
|
|
105
|
+
.mockReturnValue([makeSkillEntry({ state: "deny" })]),
|
|
115
106
|
});
|
|
116
107
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
117
108
|
expect(result).toMatchObject({ action: "block" });
|
|
@@ -119,10 +110,10 @@ describe("evaluateSkillReadGate", () => {
|
|
|
119
110
|
|
|
120
111
|
it("returns allow when state is ask and user approves", async () => {
|
|
121
112
|
const tcc = makeTcc();
|
|
122
|
-
const deps =
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
113
|
+
const deps = makeSkillReadGateDeps({
|
|
114
|
+
getActiveSkillEntries: vi
|
|
115
|
+
.fn()
|
|
116
|
+
.mockReturnValue([makeSkillEntry({ state: "ask" })]),
|
|
126
117
|
promptPermission: vi
|
|
127
118
|
.fn()
|
|
128
119
|
.mockResolvedValue({ approved: true, state: "approved" }),
|
|
@@ -133,10 +124,10 @@ describe("evaluateSkillReadGate", () => {
|
|
|
133
124
|
|
|
134
125
|
it("returns block when state is ask and user denies", async () => {
|
|
135
126
|
const tcc = makeTcc();
|
|
136
|
-
const deps =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
const deps = makeSkillReadGateDeps({
|
|
128
|
+
getActiveSkillEntries: vi
|
|
129
|
+
.fn()
|
|
130
|
+
.mockReturnValue([makeSkillEntry({ state: "ask" })]),
|
|
140
131
|
promptPermission: vi
|
|
141
132
|
.fn()
|
|
142
133
|
.mockResolvedValue({ approved: false, state: "denied" }),
|
|
@@ -147,28 +138,25 @@ describe("evaluateSkillReadGate", () => {
|
|
|
147
138
|
|
|
148
139
|
it("returns block when state is ask and no UI available", async () => {
|
|
149
140
|
const tcc = makeTcc();
|
|
150
|
-
const deps =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
141
|
+
const deps = makeSkillReadGateDeps({
|
|
142
|
+
getActiveSkillEntries: vi
|
|
143
|
+
.fn()
|
|
144
|
+
.mockReturnValue([makeSkillEntry({ state: "ask" })]),
|
|
145
|
+
canConfirm: vi.fn().mockReturnValue(false),
|
|
155
146
|
});
|
|
156
147
|
const result = await evaluateSkillReadGate(tcc, deps);
|
|
157
148
|
expect(result).toMatchObject({ action: "block" });
|
|
158
149
|
});
|
|
159
150
|
|
|
160
151
|
it("emits decision event with correct fields on deny", async () => {
|
|
161
|
-
const events = makeEvents();
|
|
162
152
|
const tcc = makeTcc({ agentName: "test-agent" });
|
|
163
|
-
const deps =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
events,
|
|
153
|
+
const deps = makeSkillReadGateDeps({
|
|
154
|
+
getActiveSkillEntries: vi
|
|
155
|
+
.fn()
|
|
156
|
+
.mockReturnValue([makeSkillEntry({ state: "deny" })]),
|
|
168
157
|
});
|
|
169
158
|
await evaluateSkillReadGate(tcc, deps);
|
|
170
|
-
expect(
|
|
171
|
-
"permissions:decision",
|
|
159
|
+
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
172
160
|
expect.objectContaining({
|
|
173
161
|
surface: "skill",
|
|
174
162
|
value: "librarian",
|
|
@@ -182,17 +170,14 @@ describe("evaluateSkillReadGate", () => {
|
|
|
182
170
|
});
|
|
183
171
|
|
|
184
172
|
it("emits decision event with correct fields on allow", async () => {
|
|
185
|
-
const events = makeEvents();
|
|
186
173
|
const tcc = makeTcc();
|
|
187
|
-
const deps =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
events,
|
|
174
|
+
const deps = makeSkillReadGateDeps({
|
|
175
|
+
getActiveSkillEntries: vi
|
|
176
|
+
.fn()
|
|
177
|
+
.mockReturnValue([makeSkillEntry({ state: "allow" })]),
|
|
192
178
|
});
|
|
193
179
|
await evaluateSkillReadGate(tcc, deps);
|
|
194
|
-
expect(
|
|
195
|
-
"permissions:decision",
|
|
180
|
+
expect(deps.emitDecision).toHaveBeenCalledWith(
|
|
196
181
|
expect.objectContaining({
|
|
197
182
|
surface: "skill",
|
|
198
183
|
value: "librarian",
|