@gotgenes/pi-permission-system 5.11.0 → 5.14.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/package.json +7 -7
  3. package/src/active-agent.ts +1 -1
  4. package/src/config-modal.ts +2 -2
  5. package/src/extension-config.ts +2 -139
  6. package/src/forwarded-permissions/polling.ts +1 -1
  7. package/src/forwarding-manager.ts +1 -1
  8. package/src/handlers/before-agent-start.ts +1 -1
  9. package/src/handlers/lifecycle.ts +1 -1
  10. package/src/handlers/permission-gate-handler.ts +1 -1
  11. package/src/index.ts +1 -1
  12. package/src/logging.ts +4 -12
  13. package/src/permission-event-rpc.ts +1 -1
  14. package/src/permission-prompter.ts +1 -1
  15. package/src/permission-session.ts +1 -1
  16. package/src/policy-loader.ts +1 -1
  17. package/src/runtime.ts +1 -1
  18. package/src/status.ts +1 -1
  19. package/src/subagent-context.ts +1 -1
  20. package/src/wildcard-matcher.ts +11 -3
  21. package/tests/active-agent.test.ts +1 -1
  22. package/tests/config-modal.test.ts +22 -12
  23. package/tests/config-reporter.test.ts +2 -0
  24. package/tests/extension-config.test.ts +1 -60
  25. package/tests/forwarding-manager.test.ts +1 -1
  26. package/tests/handlers/before-agent-start.test.ts +3 -3
  27. package/tests/handlers/external-directory-integration.test.ts +609 -0
  28. package/tests/handlers/external-directory-session-dedup.test.ts +367 -0
  29. package/tests/handlers/gates/skill-read.test.ts +2 -2
  30. package/tests/handlers/input-events.test.ts +1 -1
  31. package/tests/handlers/input.test.ts +1 -1
  32. package/tests/handlers/lifecycle.test.ts +1 -1
  33. package/tests/handlers/tool-call-events.test.ts +1 -1
  34. package/tests/handlers/tool-call.test.ts +3 -3
  35. package/tests/permission-event-rpc.test.ts +1 -1
  36. package/tests/permission-prompter.test.ts +1 -1
  37. package/tests/permission-session.test.ts +1 -1
  38. package/tests/permission-system.test.ts +1 -122
  39. package/tests/runtime.test.ts +1 -1
  40. package/tests/subagent-context.test.ts +1 -1
  41. package/tests/wildcard-matcher.test.ts +91 -0
@@ -0,0 +1,367 @@
1
+ /**
2
+ * Integration tests verifying that sequential tool calls to the same
3
+ * external path only prompt once — the session-approval recorded by the
4
+ * first call covers the second.
5
+ *
6
+ * These tests use stateful mocks: `approveSessionRule` records rules,
7
+ * and `checkPermission` consults them via `getSessionRuleset`, mirroring
8
+ * the real interaction between PermissionSession, SessionRules, and
9
+ * PermissionManager.
10
+ */
11
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
12
+ import { describe, expect, it, vi } from "vitest";
13
+
14
+ import { PermissionGateHandler } from "../../src/handlers/permission-gate-handler";
15
+ import type { PermissionSession } from "../../src/permission-session";
16
+ import type { Rule } from "../../src/rule";
17
+ import type { ToolRegistry } from "../../src/tool-registry";
18
+ import type { PermissionCheckResult } from "../../src/types";
19
+ import { wildcardMatch } from "../../src/wildcard-matcher";
20
+
21
+ // ── SDK stub ───────────────────────────────────────────────────────────────
22
+ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
23
+ const original =
24
+ await importOriginal<typeof import("@earendil-works/pi-coding-agent")>();
25
+ return { ...original };
26
+ });
27
+
28
+ // ── helpers ────────────────────────────────────────────────────────────────
29
+
30
+ const CWD = "/test/project";
31
+
32
+ function makeCtx(overrides: Partial<ExtensionContext> = {}): ExtensionContext {
33
+ return {
34
+ cwd: CWD,
35
+ hasUI: true,
36
+ ui: {
37
+ setStatus: vi.fn(),
38
+ notify: vi.fn(),
39
+ select: vi.fn(),
40
+ input: vi.fn(),
41
+ },
42
+ sessionManager: {
43
+ getEntries: vi.fn().mockReturnValue([]),
44
+ getSessionDir: vi.fn().mockReturnValue("/sessions/test"),
45
+ addEntry: vi.fn(),
46
+ },
47
+ ...overrides,
48
+ } as unknown as ExtensionContext;
49
+ }
50
+
51
+ /**
52
+ * Build a PermissionSession mock with stateful session-rule tracking.
53
+ *
54
+ * `checkPermission` returns "ask" for `external_directory` unless a
55
+ * matching session rule exists (via `approveSessionRule`), in which case
56
+ * it returns "allow" with `source: "session"`. All other surfaces return
57
+ * "allow" by default.
58
+ */
59
+ function makeStatefulSession(
60
+ overrides: Partial<Record<keyof PermissionSession, unknown>> = {},
61
+ ): PermissionSession {
62
+ const sessionRules: Rule[] = [];
63
+
64
+ const checkPermission = vi
65
+ .fn()
66
+ .mockImplementation(
67
+ (
68
+ surface: string,
69
+ input: unknown,
70
+ _agentName?: string,
71
+ rules?: Rule[],
72
+ ): PermissionCheckResult => {
73
+ // Merge stored session rules with any passed-in rules
74
+ const allRules = [...sessionRules, ...(rules ?? [])];
75
+
76
+ if (surface === "external_directory") {
77
+ const record = (input ?? {}) as Record<string, unknown>;
78
+ const pathValue =
79
+ typeof record.path === "string" ? record.path : null;
80
+
81
+ if (pathValue && allRules.length > 0) {
82
+ const match = allRules.findLast(
83
+ (r) =>
84
+ r.surface === "external_directory" &&
85
+ wildcardMatch(r.pattern, pathValue),
86
+ );
87
+ if (match) {
88
+ return {
89
+ state: "allow",
90
+ toolName: surface,
91
+ source: "session",
92
+ origin: "session",
93
+ matchedPattern: match.pattern,
94
+ };
95
+ }
96
+ }
97
+
98
+ // No session match → config-level "ask"
99
+ return {
100
+ state: "ask",
101
+ toolName: surface,
102
+ source: "special",
103
+ origin: "global",
104
+ };
105
+ }
106
+
107
+ // All other surfaces: allow
108
+ return {
109
+ state: "allow",
110
+ toolName: surface,
111
+ source: "tool",
112
+ origin: "builtin",
113
+ };
114
+ },
115
+ );
116
+
117
+ const approveSessionRule = vi
118
+ .fn()
119
+ .mockImplementation((surface: string, pattern: string) => {
120
+ sessionRules.push({
121
+ surface,
122
+ pattern,
123
+ action: "allow",
124
+ layer: "session",
125
+ origin: "session",
126
+ });
127
+ });
128
+
129
+ const getSessionRuleset = vi.fn().mockImplementation(() => [...sessionRules]);
130
+
131
+ return {
132
+ logger: { debug: vi.fn(), review: vi.fn(), warn: vi.fn() },
133
+ activate: vi.fn(),
134
+ resolveAgentName: vi.fn().mockReturnValue(null),
135
+ checkPermission,
136
+ getToolPermission: vi.fn().mockReturnValue("allow"),
137
+ getSessionRuleset,
138
+ approveSessionRule,
139
+ getActiveSkillEntries: vi.fn().mockReturnValue([]),
140
+ getInfrastructureDirs: vi.fn().mockReturnValue([]),
141
+ getInfrastructureReadPaths: vi.fn().mockReturnValue([]),
142
+ canPrompt: vi.fn().mockReturnValue(true),
143
+ prompt: vi
144
+ .fn()
145
+ .mockResolvedValue({ approved: true, state: "approved_for_session" }),
146
+ ...overrides,
147
+ } as unknown as PermissionSession;
148
+ }
149
+
150
+ function makeEvents() {
151
+ return {
152
+ emit: vi.fn(),
153
+ on: vi.fn().mockReturnValue(() => undefined),
154
+ };
155
+ }
156
+
157
+ function makeToolRegistry(): ToolRegistry {
158
+ return {
159
+ getAll: vi
160
+ .fn()
161
+ .mockReturnValue([
162
+ { name: "read" },
163
+ { name: "write" },
164
+ { name: "edit" },
165
+ { name: "bash" },
166
+ ]),
167
+ setActive: vi.fn(),
168
+ };
169
+ }
170
+
171
+ // ── tests ──────────────────────────────────────────────────────────────────
172
+
173
+ describe("external-directory session dedup", () => {
174
+ describe("path-bearing tools (read, write, edit)", () => {
175
+ it("does not re-prompt for the same external path after session approval", async () => {
176
+ const session = makeStatefulSession();
177
+ const handler = new PermissionGateHandler(
178
+ session,
179
+ makeEvents(),
180
+ makeToolRegistry(),
181
+ );
182
+ const ctx = makeCtx();
183
+ const externalPath = "/outside/project/data.txt";
184
+
185
+ // First call — should prompt
186
+ const event1 = {
187
+ type: "tool_call",
188
+ toolCallId: "tc-1",
189
+ toolName: "read",
190
+ input: { path: externalPath },
191
+ };
192
+ const result1 = await handler.handleToolCall(event1, ctx);
193
+ expect(result1).toEqual({});
194
+ expect(session.prompt).toHaveBeenCalledTimes(1);
195
+
196
+ // Second call — same path, should hit session rule, no prompt
197
+ const event2 = {
198
+ type: "tool_call",
199
+ toolCallId: "tc-2",
200
+ toolName: "read",
201
+ input: { path: externalPath },
202
+ };
203
+ const result2 = await handler.handleToolCall(event2, ctx);
204
+ expect(result2).toEqual({});
205
+ expect(session.prompt).toHaveBeenCalledTimes(1);
206
+ });
207
+
208
+ it("does not re-prompt for a different file in the same external directory", async () => {
209
+ const session = makeStatefulSession();
210
+ const handler = new PermissionGateHandler(
211
+ session,
212
+ makeEvents(),
213
+ makeToolRegistry(),
214
+ );
215
+ const ctx = makeCtx();
216
+
217
+ // First call — prompt for /outside/project/a.txt
218
+ const event1 = {
219
+ type: "tool_call",
220
+ toolCallId: "tc-1",
221
+ toolName: "read",
222
+ input: { path: "/outside/project/a.txt" },
223
+ };
224
+ await handler.handleToolCall(event1, ctx);
225
+ expect(session.prompt).toHaveBeenCalledTimes(1);
226
+
227
+ // Second call — /outside/project/b.txt is in the same directory
228
+ const event2 = {
229
+ type: "tool_call",
230
+ toolCallId: "tc-2",
231
+ toolName: "read",
232
+ input: { path: "/outside/project/b.txt" },
233
+ };
234
+ await handler.handleToolCall(event2, ctx);
235
+ expect(session.prompt).toHaveBeenCalledTimes(1);
236
+ });
237
+
238
+ it("does prompt for a file in a different external directory", async () => {
239
+ const session = makeStatefulSession();
240
+ const handler = new PermissionGateHandler(
241
+ session,
242
+ makeEvents(),
243
+ makeToolRegistry(),
244
+ );
245
+ const ctx = makeCtx();
246
+
247
+ // First call — /outside/alpha/file.txt
248
+ const event1 = {
249
+ type: "tool_call",
250
+ toolCallId: "tc-1",
251
+ toolName: "read",
252
+ input: { path: "/outside/alpha/file.txt" },
253
+ };
254
+ await handler.handleToolCall(event1, ctx);
255
+ expect(session.prompt).toHaveBeenCalledTimes(1);
256
+
257
+ // Second call — /outside/beta/file.txt is a different directory
258
+ const event2 = {
259
+ type: "tool_call",
260
+ toolCallId: "tc-2",
261
+ toolName: "read",
262
+ input: { path: "/outside/beta/file.txt" },
263
+ };
264
+ await handler.handleToolCall(event2, ctx);
265
+ expect(session.prompt).toHaveBeenCalledTimes(2);
266
+ });
267
+
268
+ it("re-prompts when user approved once (not for session)", async () => {
269
+ const session = makeStatefulSession({
270
+ prompt: vi
271
+ .fn()
272
+ .mockResolvedValue({ approved: true, state: "approved" }),
273
+ });
274
+ const handler = new PermissionGateHandler(
275
+ session,
276
+ makeEvents(),
277
+ makeToolRegistry(),
278
+ );
279
+ const ctx = makeCtx();
280
+ const externalPath = "/outside/project/data.txt";
281
+
282
+ // First call — prompt, approved once
283
+ const event1 = {
284
+ type: "tool_call",
285
+ toolCallId: "tc-1",
286
+ toolName: "read",
287
+ input: { path: externalPath },
288
+ };
289
+ await handler.handleToolCall(event1, ctx);
290
+ expect(session.prompt).toHaveBeenCalledTimes(1);
291
+
292
+ // Second call — no session rule recorded, should prompt again
293
+ const event2 = {
294
+ type: "tool_call",
295
+ toolCallId: "tc-2",
296
+ toolName: "read",
297
+ input: { path: externalPath },
298
+ };
299
+ await handler.handleToolCall(event2, ctx);
300
+ expect(session.prompt).toHaveBeenCalledTimes(2);
301
+ });
302
+ });
303
+
304
+ describe("bash commands with external paths", () => {
305
+ it("does not re-prompt for a bash command referencing the same external path after session approval", async () => {
306
+ const session = makeStatefulSession();
307
+ const handler = new PermissionGateHandler(
308
+ session,
309
+ makeEvents(),
310
+ makeToolRegistry(),
311
+ );
312
+ const ctx = makeCtx();
313
+
314
+ // First call — bash referencing /tmp/out.txt
315
+ const event1 = {
316
+ type: "tool_call",
317
+ toolCallId: "tc-1",
318
+ toolName: "bash",
319
+ input: { command: "echo hello > /tmp/out.txt" },
320
+ };
321
+ const result1 = await handler.handleToolCall(event1, ctx);
322
+ expect(result1).toEqual({});
323
+ expect(session.prompt).toHaveBeenCalledTimes(1);
324
+
325
+ // Second call — different bash command, same external path
326
+ const event2 = {
327
+ type: "tool_call",
328
+ toolCallId: "tc-2",
329
+ toolName: "bash",
330
+ input: { command: "cat /tmp/out.txt" },
331
+ };
332
+ const result2 = await handler.handleToolCall(event2, ctx);
333
+ expect(result2).toEqual({});
334
+ expect(session.prompt).toHaveBeenCalledTimes(1);
335
+ });
336
+
337
+ it("does not re-prompt for read after bash already approved the same directory", async () => {
338
+ const session = makeStatefulSession();
339
+ const handler = new PermissionGateHandler(
340
+ session,
341
+ makeEvents(),
342
+ makeToolRegistry(),
343
+ );
344
+ const ctx = makeCtx();
345
+
346
+ // First call — bash writes to /tmp/out.txt
347
+ const event1 = {
348
+ type: "tool_call",
349
+ toolCallId: "tc-1",
350
+ toolName: "bash",
351
+ input: { command: "echo hello > /tmp/out.txt" },
352
+ };
353
+ await handler.handleToolCall(event1, ctx);
354
+ expect(session.prompt).toHaveBeenCalledTimes(1);
355
+
356
+ // Second call — read from /tmp/out.txt (same directory, different tool)
357
+ const event2 = {
358
+ type: "tool_call",
359
+ toolCallId: "tc-2",
360
+ toolName: "read",
361
+ input: { path: "/tmp/out.txt" },
362
+ };
363
+ await handler.handleToolCall(event2, ctx);
364
+ expect(session.prompt).toHaveBeenCalledTimes(1);
365
+ });
366
+ });
367
+ });
@@ -6,9 +6,9 @@ import type { ToolCallContext } from "../../../src/handlers/gates/types";
6
6
  import type { SkillPromptEntry } from "../../../src/skill-prompt-sanitizer";
7
7
 
8
8
  // ── SDK stubs ──────────────────────────────────────────────────────────────
9
- vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
9
+ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
10
10
  const original =
11
- await importOriginal<typeof import("@mariozechner/pi-coding-agent")>();
11
+ await importOriginal<typeof import("@earendil-works/pi-coding-agent")>();
12
12
  return { ...original };
13
13
  });
14
14
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Tests that handleInput emits permissions:decision events for skill input gates.
3
3
  */
4
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
4
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
5
  import { describe, expect, it, vi } from "vitest";
6
6
 
7
7
  import { PermissionGateHandler } from "../../src/handlers/permission-gate-handler";
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  import {
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
  import { SessionLifecycleHandler } from "../../src/handlers/lifecycle";
4
4
  import type { PermissionSession } from "../../src/permission-session";
@@ -2,7 +2,7 @@
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 "@mariozechner/pi-coding-agent";
5
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
6
6
  import { describe, expect, it, vi } from "vitest";
7
7
 
8
8
  import { PermissionGateHandler } from "../../src/handlers/permission-gate-handler";
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
 
4
4
  import {
@@ -10,9 +10,9 @@ import type { ToolRegistry } from "../../src/tool-registry";
10
10
  import type { PermissionCheckResult, PermissionState } from "../../src/types";
11
11
 
12
12
  // ── SDK stubs ──────────────────────────────────────────────────────────────
13
- vi.mock("@mariozechner/pi-coding-agent", async (importOriginal) => {
13
+ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
14
14
  const original =
15
- await importOriginal<typeof import("@mariozechner/pi-coding-agent")>();
15
+ await importOriginal<typeof import("@earendil-works/pi-coding-agent")>();
16
16
  return { ...original };
17
17
  });
18
18
 
@@ -1,4 +1,4 @@
1
- import { createEventBus } from "@mariozechner/pi-coding-agent";
1
+ import { createEventBus } from "@earendil-works/pi-coding-agent";
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
  import {
4
4
  type PermissionRpcDeps,
@@ -13,7 +13,7 @@ vi.mock("../src/forwarded-permissions/polling", () => ({
13
13
 
14
14
  // ── Imports (after mocks) ───────────────────────────────────────────────────
15
15
 
16
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
16
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
17
17
  import { DEFAULT_EXTENSION_CONFIG } from "../src/extension-config";
18
18
  import type { PermissionPromptDecision } from "../src/permission-dialog";
19
19
  import type { PromptPermissionDetails } from "../src/permission-prompter";
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
4
  // ── Module mocks (hoisted) ─────────────────────────────────────────────────
@@ -17,11 +17,7 @@ import {
17
17
  shouldApplyCachedAgentStartState,
18
18
  } from "../src/before-agent-start-cache";
19
19
  import { getGlobalConfigPath } from "../src/config-paths";
20
- import {
21
- DEFAULT_EXTENSION_CONFIG,
22
- loadPermissionSystemConfig,
23
- savePermissionSystemConfig,
24
- } from "../src/extension-config";
20
+ import { DEFAULT_EXTENSION_CONFIG } from "../src/extension-config";
25
21
  import piPermissionSystemExtension from "../src/index";
26
22
  import { createPermissionSystemLogger } from "../src/logging";
27
23
  import {
@@ -244,123 +240,6 @@ async function runToolCall(
244
240
  return (result ?? {}) as Record<string, unknown>;
245
241
  }
246
242
 
247
- test("Permission-system extension config defaults debug off, review log on, and yolo mode off", () => {
248
- const baseDir = mkdtempSync(join(tmpdir(), "pi-permission-system-config-"));
249
- const configPath = join(baseDir, "config.json");
250
-
251
- try {
252
- const result = loadPermissionSystemConfig(configPath);
253
- assert.equal(result.created, true);
254
- assert.equal(result.warning, undefined);
255
- assert.deepEqual(result.config, DEFAULT_EXTENSION_CONFIG);
256
- assert.equal(existsSync(configPath), true);
257
-
258
- const raw = JSON.parse(readFileSync(configPath, "utf8")) as Record<
259
- string,
260
- unknown
261
- >;
262
- assert.equal(raw.debugLog, false);
263
- assert.equal(raw.permissionReviewLog, true);
264
- assert.equal(raw.yoloMode, false);
265
- } finally {
266
- rmSync(baseDir, { recursive: true, force: true });
267
- }
268
- });
269
-
270
- test("Permission-system extension config loads yolo mode when explicitly enabled", () => {
271
- const baseDir = mkdtempSync(
272
- join(tmpdir(), "pi-permission-system-config-yolo-"),
273
- );
274
- const configPath = join(baseDir, "config.json");
275
-
276
- try {
277
- writeFileSync(
278
- configPath,
279
- `${JSON.stringify(
280
- {
281
- debugLog: true,
282
- permissionReviewLog: false,
283
- yoloMode: true,
284
- },
285
- null,
286
- 2,
287
- )}\n`,
288
- "utf8",
289
- );
290
-
291
- const result = loadPermissionSystemConfig(configPath);
292
- assert.equal(result.created, false);
293
- assert.equal(result.warning, undefined);
294
- assert.deepEqual(result.config, {
295
- debugLog: true,
296
- permissionReviewLog: false,
297
- yoloMode: true,
298
- });
299
- } finally {
300
- rmSync(baseDir, { recursive: true, force: true });
301
- }
302
- });
303
-
304
- test("Permission-system extension config normalizes invalid persisted values back to defaults", () => {
305
- const baseDir = mkdtempSync(
306
- join(tmpdir(), "pi-permission-system-config-invalid-"),
307
- );
308
- const configPath = join(baseDir, "config.json");
309
-
310
- try {
311
- writeFileSync(
312
- configPath,
313
- `${JSON.stringify(
314
- {
315
- debugLog: "true",
316
- permissionReviewLog: null,
317
- yoloMode: 1,
318
- },
319
- null,
320
- 2,
321
- )}\n`,
322
- "utf8",
323
- );
324
-
325
- const result = loadPermissionSystemConfig(configPath);
326
- assert.equal(result.created, false);
327
- assert.equal(result.warning, undefined);
328
- assert.deepEqual(result.config, DEFAULT_EXTENSION_CONFIG);
329
- } finally {
330
- rmSync(baseDir, { recursive: true, force: true });
331
- }
332
- });
333
-
334
- test("Permission-system extension config save persists normalized config", () => {
335
- const baseDir = mkdtempSync(
336
- join(tmpdir(), "pi-permission-system-config-save-"),
337
- );
338
- const configPath = join(baseDir, "config.json");
339
-
340
- try {
341
- const saved = savePermissionSystemConfig(
342
- {
343
- debugLog: true,
344
- permissionReviewLog: false,
345
- yoloMode: true,
346
- },
347
- configPath,
348
- );
349
-
350
- assert.equal(saved.success, true);
351
-
352
- const result = loadPermissionSystemConfig(configPath);
353
- assert.equal(result.warning, undefined);
354
- assert.deepEqual(result.config, {
355
- debugLog: true,
356
- permissionReviewLog: false,
357
- yoloMode: true,
358
- });
359
- } finally {
360
- rmSync(baseDir, { recursive: true, force: true });
361
- }
362
- });
363
-
364
243
  test("Yolo mode only auto-approves ask-state permissions", () => {
365
244
  assert.equal(
366
245
  shouldAutoApprovePermissionState("ask", DEFAULT_EXTENSION_CONFIG),
@@ -66,7 +66,7 @@ vi.mock("../src/session-rules", () => ({
66
66
  deriveApprovalPattern: vi.fn(),
67
67
  }));
68
68
 
69
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
69
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
70
70
  import {
71
71
  getGlobalConfigPath,
72
72
  getGlobalLogsDir,
@@ -1,4 +1,4 @@
1
- import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
1
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
2
2
  import { afterEach, describe, expect, test, vi } from "vitest";
3
3
  import { SUBAGENT_ENV_HINT_KEYS } from "../src/permission-forwarding";
4
4
  import {