@gotgenes/pi-permission-system 4.7.0 → 4.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 +35 -0
- package/README.md +43 -8
- package/config/config.example.json +6 -1
- package/package.json +1 -1
- package/schemas/permissions.schema.json +12 -2
- package/src/expand-home.ts +28 -0
- package/src/extension-config.ts +13 -1
- package/src/external-directory.ts +96 -1
- package/src/handlers/tool-call.ts +87 -61
- package/src/runtime.ts +17 -0
- package/src/wildcard-matcher.ts +4 -1
- package/tests/bash-external-directory.test.ts +50 -0
- package/tests/expand-home.test.ts +93 -0
- package/tests/handlers/tool-call.test.ts +147 -0
- package/tests/permission-manager-unified.test.ts +74 -1
- package/tests/pi-infrastructure-read.test.ts +245 -0
- package/tests/runtime.test.ts +45 -0
- package/tests/wildcard-matcher.test.ts +58 -0
package/tests/runtime.test.ts
CHANGED
|
@@ -11,6 +11,7 @@ const {
|
|
|
11
11
|
mockGetActiveAgentName,
|
|
12
12
|
mockGetActiveAgentNameFromSystemPrompt,
|
|
13
13
|
mockBuildResolvedConfigLogEntry,
|
|
14
|
+
mockDiscoverGlobalNodeModulesRoot,
|
|
14
15
|
} = vi.hoisted(() => ({
|
|
15
16
|
mockLoggerDebug:
|
|
16
17
|
vi.fn<
|
|
@@ -27,6 +28,7 @@ const {
|
|
|
27
28
|
mockGetActiveAgentNameFromSystemPrompt:
|
|
28
29
|
vi.fn<(prompt?: string) => string | null>(),
|
|
29
30
|
mockBuildResolvedConfigLogEntry: vi.fn(),
|
|
31
|
+
mockDiscoverGlobalNodeModulesRoot: vi.fn<() => string | null>(),
|
|
30
32
|
}));
|
|
31
33
|
|
|
32
34
|
vi.mock("../src/logging", () => ({
|
|
@@ -65,6 +67,10 @@ vi.mock("../src/subagent-context", () => ({
|
|
|
65
67
|
isSubagentExecutionContext: vi.fn().mockReturnValue(false),
|
|
66
68
|
}));
|
|
67
69
|
|
|
70
|
+
vi.mock("../src/external-directory", () => ({
|
|
71
|
+
discoverGlobalNodeModulesRoot: mockDiscoverGlobalNodeModulesRoot,
|
|
72
|
+
}));
|
|
73
|
+
|
|
68
74
|
vi.mock("../src/session-rules", () => ({
|
|
69
75
|
SessionRules: vi.fn(),
|
|
70
76
|
deriveApprovalPattern: vi.fn(),
|
|
@@ -99,6 +105,10 @@ describe("createExtensionRuntime", () => {
|
|
|
99
105
|
debug: mockLoggerDebug,
|
|
100
106
|
review: mockLoggerReview,
|
|
101
107
|
});
|
|
108
|
+
mockDiscoverGlobalNodeModulesRoot.mockReset();
|
|
109
|
+
mockDiscoverGlobalNodeModulesRoot.mockReturnValue(
|
|
110
|
+
"/mock/global/node_modules",
|
|
111
|
+
);
|
|
102
112
|
});
|
|
103
113
|
|
|
104
114
|
// ── Path derivation ──────────────────────────────────────────────────────
|
|
@@ -130,6 +140,41 @@ describe("createExtensionRuntime", () => {
|
|
|
130
140
|
expect(runtime.globalLogsDir).toBe(getGlobalLogsDir("/test/agent"));
|
|
131
141
|
});
|
|
132
142
|
|
|
143
|
+
// ── piInfrastructureDirs ─────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
it("includes agentDir in piInfrastructureDirs", () => {
|
|
146
|
+
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
147
|
+
expect(runtime.piInfrastructureDirs).toContain("/test/agent");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("includes agentDir/git in piInfrastructureDirs", () => {
|
|
151
|
+
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
152
|
+
expect(runtime.piInfrastructureDirs).toContain("/test/agent/git");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("includes discovered global node_modules root in piInfrastructureDirs", () => {
|
|
156
|
+
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
157
|
+
expect(runtime.piInfrastructureDirs).toContain("/mock/global/node_modules");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("excludes null when discoverGlobalNodeModulesRoot returns null", () => {
|
|
161
|
+
mockDiscoverGlobalNodeModulesRoot.mockReturnValue(null);
|
|
162
|
+
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
163
|
+
for (const dir of runtime.piInfrastructureDirs) {
|
|
164
|
+
expect(dir).not.toBeNull();
|
|
165
|
+
expect(typeof dir).toBe("string");
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("omits global node_modules from piInfrastructureDirs when discovery returns null", () => {
|
|
170
|
+
mockDiscoverGlobalNodeModulesRoot.mockReturnValue(null);
|
|
171
|
+
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
172
|
+
// Only agentDir and agentDir/git should be present.
|
|
173
|
+
expect(runtime.piInfrastructureDirs).toHaveLength(2);
|
|
174
|
+
expect(runtime.piInfrastructureDirs).toContain("/test/agent");
|
|
175
|
+
expect(runtime.piInfrastructureDirs).toContain("/test/agent/git");
|
|
176
|
+
});
|
|
177
|
+
|
|
133
178
|
// ── Default mutable state ────────────────────────────────────────────────
|
|
134
179
|
|
|
135
180
|
it("initializes config to DEFAULT_EXTENSION_CONFIG", () => {
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
1
2
|
import { afterEach, describe, expect, test, vi } from "vitest";
|
|
2
3
|
|
|
4
|
+
const mockHomedir = vi.hoisted(() => vi.fn(() => "/home/testuser"));
|
|
5
|
+
|
|
6
|
+
vi.mock("node:os", () => ({
|
|
7
|
+
homedir: mockHomedir,
|
|
8
|
+
default: { homedir: mockHomedir },
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
const FAKE_HOME = "/home/testuser";
|
|
12
|
+
|
|
3
13
|
import {
|
|
4
14
|
compileWildcardPattern,
|
|
5
15
|
compileWildcardPatternEntries,
|
|
@@ -9,6 +19,7 @@ import {
|
|
|
9
19
|
} from "../src/wildcard-matcher";
|
|
10
20
|
|
|
11
21
|
afterEach(() => {
|
|
22
|
+
mockHomedir.mockClear();
|
|
12
23
|
vi.restoreAllMocks();
|
|
13
24
|
});
|
|
14
25
|
|
|
@@ -233,3 +244,50 @@ describe("wildcardMatch", () => {
|
|
|
233
244
|
expect(wildcardMatch("tool.name", "toolXname")).toBe(false);
|
|
234
245
|
});
|
|
235
246
|
});
|
|
247
|
+
|
|
248
|
+
describe("home path expansion in patterns", () => {
|
|
249
|
+
test("wildcardMatch expands ~ prefix in pattern before matching", () => {
|
|
250
|
+
const expandedPath = join(FAKE_HOME, "dev/project");
|
|
251
|
+
expect(wildcardMatch("~/dev/project", expandedPath)).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("wildcardMatch expands ~/glob in pattern", () => {
|
|
255
|
+
const expandedFile = join(FAKE_HOME, "dev/project/file.ts");
|
|
256
|
+
expect(wildcardMatch("~/dev/*", expandedFile)).toBe(true);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("wildcardMatch ~/glob does not match a different home directory", () => {
|
|
260
|
+
expect(wildcardMatch("~/dev/*", "/other/user/dev/file.ts")).toBe(false);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("wildcardMatch expands $HOME prefix in pattern before matching", () => {
|
|
264
|
+
const expandedPath = join(FAKE_HOME, "dev/project");
|
|
265
|
+
expect(wildcardMatch("$HOME/dev/project", expandedPath)).toBe(true);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("wildcardMatch expands $HOME/glob in pattern", () => {
|
|
269
|
+
const expandedFile = join(FAKE_HOME, "work/file.ts");
|
|
270
|
+
expect(wildcardMatch("$HOME/work/*", expandedFile)).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("compileWildcardPattern retains original ~ pattern in .pattern field", () => {
|
|
274
|
+
const compiled = compileWildcardPattern("~/dev/*", "allow");
|
|
275
|
+
expect(compiled.pattern).toBe("~/dev/*");
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("compileWildcardPattern retains original $HOME pattern in .pattern field", () => {
|
|
279
|
+
const compiled = compileWildcardPattern("$HOME/dev/*", "allow");
|
|
280
|
+
expect(compiled.pattern).toBe("$HOME/dev/*");
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("compileWildcardPattern expanded regex matches the expanded path", () => {
|
|
284
|
+
const compiled = compileWildcardPattern("~/dev/*", "allow");
|
|
285
|
+
const expandedFile = join(FAKE_HOME, "dev/file.ts");
|
|
286
|
+
expect(compiled.regex.test(expandedFile)).toBe(true);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
test("non-home pattern is unaffected", () => {
|
|
290
|
+
expect(wildcardMatch("/absolute/path/*", "/absolute/path/file")).toBe(true);
|
|
291
|
+
expect(wildcardMatch("/absolute/path/*", "/other/file")).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
});
|