@gotgenes/pi-permission-system 10.2.0 → 10.3.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 +7 -0
- package/package.json +1 -1
- package/src/config-modal.ts +7 -10
- package/src/config-store.ts +243 -0
- package/src/index.ts +7 -15
- package/src/permission-prompter.ts +3 -3
- package/src/permission-session.ts +7 -10
- package/src/runtime.ts +34 -167
- package/test/config-modal.test.ts +15 -10
- package/test/config-store.test.ts +452 -0
- package/test/permission-prompter.test.ts +12 -5
- package/test/permission-session.test.ts +44 -26
- package/test/runtime.test.ts +10 -194
package/test/runtime.test.ts
CHANGED
|
@@ -5,9 +5,6 @@ const {
|
|
|
5
5
|
mockLoggerDebug,
|
|
6
6
|
mockLoggerReview,
|
|
7
7
|
mockCreateLogger,
|
|
8
|
-
mockLoadAndMergeConfigs,
|
|
9
|
-
mockSyncPermissionSystemStatus,
|
|
10
|
-
mockBuildResolvedConfigLogEntry,
|
|
11
8
|
mockDiscoverGlobalNodeModulesRoot,
|
|
12
9
|
} = vi.hoisted(() => ({
|
|
13
10
|
mockLoggerDebug:
|
|
@@ -19,9 +16,6 @@ const {
|
|
|
19
16
|
(event: string, details?: Record<string, unknown>) => string | undefined
|
|
20
17
|
>(),
|
|
21
18
|
mockCreateLogger: vi.fn(),
|
|
22
|
-
mockLoadAndMergeConfigs: vi.fn(),
|
|
23
|
-
mockSyncPermissionSystemStatus: vi.fn(),
|
|
24
|
-
mockBuildResolvedConfigLogEntry: vi.fn(),
|
|
25
19
|
mockDiscoverGlobalNodeModulesRoot: vi.fn<() => string | null>(),
|
|
26
20
|
}));
|
|
27
21
|
|
|
@@ -33,21 +27,6 @@ vi.mock("../src/permission-manager", () => ({
|
|
|
33
27
|
PermissionManager: vi.fn(),
|
|
34
28
|
}));
|
|
35
29
|
|
|
36
|
-
vi.mock("../src/config-loader", () => ({
|
|
37
|
-
loadAndMergeConfigs: mockLoadAndMergeConfigs,
|
|
38
|
-
loadUnifiedConfig: vi.fn().mockReturnValue({ config: {} }),
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
vi.mock("../src/status", () => ({
|
|
42
|
-
PERMISSION_SYSTEM_STATUS_KEY: "permission-system",
|
|
43
|
-
syncPermissionSystemStatus: mockSyncPermissionSystemStatus,
|
|
44
|
-
getPermissionSystemStatus: vi.fn(),
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
vi.mock("../src/config-reporter", () => ({
|
|
48
|
-
buildResolvedConfigLogEntry: mockBuildResolvedConfigLogEntry,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
30
|
vi.mock("../src/subagent-context", () => ({
|
|
52
31
|
isSubagentExecutionContext: vi.fn().mockReturnValue(false),
|
|
53
32
|
}));
|
|
@@ -61,10 +40,9 @@ vi.mock("../src/session-rules", () => ({
|
|
|
61
40
|
deriveApprovalPattern: vi.fn(),
|
|
62
41
|
}));
|
|
63
42
|
|
|
64
|
-
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
65
43
|
import { getGlobalLogsDir } from "#src/config-paths";
|
|
66
44
|
import { DEFAULT_EXTENSION_CONFIG } from "#src/extension-config";
|
|
67
|
-
import { createExtensionRuntime
|
|
45
|
+
import { createExtensionRuntime } from "#src/runtime";
|
|
68
46
|
|
|
69
47
|
// ── test suite ─────────────────────────────────────────────────────────────
|
|
70
48
|
|
|
@@ -151,9 +129,9 @@ describe("createExtensionRuntime", () => {
|
|
|
151
129
|
|
|
152
130
|
// ── Default mutable state ────────────────────────────────────────────────
|
|
153
131
|
|
|
154
|
-
it("initializes
|
|
132
|
+
it("initializes configStore.current() to DEFAULT_EXTENSION_CONFIG", () => {
|
|
155
133
|
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
156
|
-
expect(runtime.
|
|
134
|
+
expect(runtime.configStore.current()).toEqual(DEFAULT_EXTENSION_CONFIG);
|
|
157
135
|
});
|
|
158
136
|
|
|
159
137
|
it("initializes runtimeContext to null", () => {
|
|
@@ -181,11 +159,6 @@ describe("createExtensionRuntime", () => {
|
|
|
181
159
|
expect(runtime.lastPromptStateCacheKey).toBeNull();
|
|
182
160
|
});
|
|
183
161
|
|
|
184
|
-
it("initializes lastConfigWarning to null", () => {
|
|
185
|
-
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
186
|
-
expect(runtime.lastConfigWarning).toBeNull();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
162
|
it("creates a sessionRules instance", () => {
|
|
190
163
|
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
191
164
|
expect(runtime.sessionRules).toBeDefined();
|
|
@@ -193,17 +166,6 @@ describe("createExtensionRuntime", () => {
|
|
|
193
166
|
|
|
194
167
|
// ── Mutable state is writable ──────────────────────────────────────────
|
|
195
168
|
|
|
196
|
-
it("allows config to be updated", () => {
|
|
197
|
-
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
198
|
-
const newConfig = {
|
|
199
|
-
debugLog: true,
|
|
200
|
-
permissionReviewLog: false,
|
|
201
|
-
yoloMode: false,
|
|
202
|
-
};
|
|
203
|
-
runtime.config = newConfig;
|
|
204
|
-
expect(runtime.config).toEqual(newConfig);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
169
|
it("allows runtimeContext to be updated", () => {
|
|
208
170
|
const runtime = createExtensionRuntime({ agentDir: "/test/agent" });
|
|
209
171
|
const mockCtx = { hasUI: false } as never;
|
|
@@ -226,19 +188,13 @@ describe("createExtensionRuntime", () => {
|
|
|
226
188
|
expect(opts.reviewLogPath).toContain(expectedLogsDir);
|
|
227
189
|
});
|
|
228
190
|
|
|
229
|
-
it("passes getConfig that reads
|
|
230
|
-
|
|
191
|
+
it("passes getConfig that reads from configStore.current()", () => {
|
|
192
|
+
createExtensionRuntime({ agentDir: "/test/agent" });
|
|
231
193
|
const opts = mockCreateLogger.mock.calls[0][0] as {
|
|
232
194
|
getConfig: () => typeof DEFAULT_EXTENSION_CONFIG;
|
|
233
195
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
permissionReviewLog: false,
|
|
237
|
-
yoloMode: false,
|
|
238
|
-
};
|
|
239
|
-
runtime.config = updatedConfig;
|
|
240
|
-
// getConfig() should reflect the updated value
|
|
241
|
-
expect(opts.getConfig()).toEqual(updatedConfig);
|
|
196
|
+
// getConfig() reads from configStore.current() — DEFAULT_EXTENSION_CONFIG at startup
|
|
197
|
+
expect(opts.getConfig()).toEqual(DEFAULT_EXTENSION_CONFIG);
|
|
242
198
|
});
|
|
243
199
|
|
|
244
200
|
// ── writeDebugLog delegates to logger.debug ──────────────────────────────
|
|
@@ -340,148 +296,8 @@ describe("createExtensionRuntime", () => {
|
|
|
340
296
|
});
|
|
341
297
|
});
|
|
342
298
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
describe("refreshExtensionConfig", () => {
|
|
346
|
-
function makeRuntime() {
|
|
347
|
-
mockCreateLogger.mockReturnValue({
|
|
348
|
-
debug: mockLoggerDebug,
|
|
349
|
-
review: mockLoggerReview,
|
|
350
|
-
});
|
|
351
|
-
return createExtensionRuntime({ agentDir: "/test/agent" });
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
function makeCtx(
|
|
355
|
-
overrides: Partial<ExtensionContext> = {},
|
|
356
|
-
): ExtensionContext {
|
|
357
|
-
return {
|
|
358
|
-
cwd: "/test/project",
|
|
359
|
-
hasUI: false,
|
|
360
|
-
ui: { notify: vi.fn(), setStatus: vi.fn() },
|
|
361
|
-
sessionManager: { getEntries: vi.fn(), addEntry: vi.fn() },
|
|
362
|
-
...overrides,
|
|
363
|
-
} as unknown as ExtensionContext;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
beforeEach(() => {
|
|
367
|
-
mockLoggerDebug.mockReset().mockReturnValue(undefined);
|
|
368
|
-
mockLoggerReview.mockReset().mockReturnValue(undefined);
|
|
369
|
-
mockLoadAndMergeConfigs.mockReset().mockReturnValue({
|
|
370
|
-
merged: { ...DEFAULT_EXTENSION_CONFIG },
|
|
371
|
-
issues: [],
|
|
372
|
-
});
|
|
373
|
-
mockSyncPermissionSystemStatus.mockReset();
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
it("updates runtime.runtimeContext when ctx is provided", () => {
|
|
377
|
-
const runtime = makeRuntime();
|
|
378
|
-
const ctx = makeCtx();
|
|
379
|
-
refreshExtensionConfig(runtime, ctx);
|
|
380
|
-
expect(runtime.runtimeContext).toBe(ctx);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("does not override runtimeContext when ctx is omitted", () => {
|
|
384
|
-
const runtime = makeRuntime();
|
|
385
|
-
const existing = makeCtx();
|
|
386
|
-
runtime.runtimeContext = existing;
|
|
387
|
-
refreshExtensionConfig(runtime);
|
|
388
|
-
expect(runtime.runtimeContext).toBe(existing);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
it("updates runtime.config with normalized merged result", () => {
|
|
392
|
-
const runtime = makeRuntime();
|
|
393
|
-
mockLoadAndMergeConfigs.mockReturnValue({
|
|
394
|
-
merged: { debugLog: true, permissionReviewLog: false, yoloMode: false },
|
|
395
|
-
issues: [],
|
|
396
|
-
});
|
|
397
|
-
refreshExtensionConfig(runtime);
|
|
398
|
-
expect(runtime.config.debugLog).toBe(true);
|
|
399
|
-
expect(runtime.config.permissionReviewLog).toBe(false);
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
it("calls loadAndMergeConfigs with runtime.agentDir and cwd", () => {
|
|
403
|
-
const runtime = makeRuntime();
|
|
404
|
-
const ctx = makeCtx({ cwd: "/my/project" });
|
|
405
|
-
refreshExtensionConfig(runtime, ctx);
|
|
406
|
-
expect(mockLoadAndMergeConfigs).toHaveBeenCalledWith(
|
|
407
|
-
"/test/agent",
|
|
408
|
-
"/my/project",
|
|
409
|
-
expect.any(String), // EXTENSION_ROOT
|
|
410
|
-
);
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it("writes config.loaded debug log", () => {
|
|
414
|
-
const runtime = makeRuntime();
|
|
415
|
-
refreshExtensionConfig(runtime);
|
|
416
|
-
expect(mockLoggerDebug).toHaveBeenCalledWith(
|
|
417
|
-
"config.loaded",
|
|
418
|
-
expect.objectContaining({ debugLog: false }),
|
|
419
|
-
);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
it("sets lastConfigWarning when issues are present", () => {
|
|
423
|
-
const runtime = makeRuntime();
|
|
424
|
-
mockLoadAndMergeConfigs.mockReturnValue({
|
|
425
|
-
merged: { ...DEFAULT_EXTENSION_CONFIG },
|
|
426
|
-
issues: ["legacy config detected"],
|
|
427
|
-
});
|
|
428
|
-
refreshExtensionConfig(runtime);
|
|
429
|
-
expect(runtime.lastConfigWarning).toBe("legacy config detected");
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
it("clears lastConfigWarning when no issues", () => {
|
|
433
|
-
const runtime = makeRuntime();
|
|
434
|
-
runtime.lastConfigWarning = "old warning";
|
|
435
|
-
mockLoadAndMergeConfigs.mockReturnValue({
|
|
436
|
-
merged: { ...DEFAULT_EXTENSION_CONFIG },
|
|
437
|
-
issues: [],
|
|
438
|
-
});
|
|
439
|
-
refreshExtensionConfig(runtime);
|
|
440
|
-
expect(runtime.lastConfigWarning).toBeNull();
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
it("notifies UI when a new warning appears and hasUI is true", () => {
|
|
444
|
-
const runtime = makeRuntime();
|
|
445
|
-
const mockNotify = vi.fn();
|
|
446
|
-
const ctx = makeCtx({ hasUI: true, ui: { notify: mockNotify } as never });
|
|
447
|
-
mockLoadAndMergeConfigs.mockReturnValue({
|
|
448
|
-
merged: { ...DEFAULT_EXTENSION_CONFIG },
|
|
449
|
-
issues: ["new warning"],
|
|
450
|
-
});
|
|
451
|
-
refreshExtensionConfig(runtime, ctx);
|
|
452
|
-
expect(mockNotify).toHaveBeenCalledWith("new warning", "warning");
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it("does not re-notify the same warning on subsequent calls", () => {
|
|
456
|
-
const runtime = makeRuntime();
|
|
457
|
-
const mockNotify = vi.fn();
|
|
458
|
-
const ctx = makeCtx({ hasUI: true, ui: { notify: mockNotify } as never });
|
|
459
|
-
mockLoadAndMergeConfigs.mockReturnValue({
|
|
460
|
-
merged: { ...DEFAULT_EXTENSION_CONFIG },
|
|
461
|
-
issues: ["persistent warning"],
|
|
462
|
-
});
|
|
463
|
-
refreshExtensionConfig(runtime, ctx);
|
|
464
|
-
refreshExtensionConfig(runtime, ctx);
|
|
465
|
-
expect(mockNotify).toHaveBeenCalledTimes(1);
|
|
466
|
-
});
|
|
467
|
-
|
|
468
|
-
it("calls syncPermissionSystemStatus when hasUI is true", () => {
|
|
469
|
-
const runtime = makeRuntime();
|
|
470
|
-
const ctx = makeCtx({ hasUI: true });
|
|
471
|
-
refreshExtensionConfig(runtime, ctx);
|
|
472
|
-
expect(mockSyncPermissionSystemStatus).toHaveBeenCalledWith(
|
|
473
|
-
ctx,
|
|
474
|
-
expect.any(Object),
|
|
475
|
-
);
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
it("does not call syncPermissionSystemStatus when hasUI is false", () => {
|
|
479
|
-
const runtime = makeRuntime();
|
|
480
|
-
const ctx = makeCtx({ hasUI: false });
|
|
481
|
-
refreshExtensionConfig(runtime, ctx);
|
|
482
|
-
expect(mockSyncPermissionSystemStatus).not.toHaveBeenCalled();
|
|
483
|
-
});
|
|
484
|
-
});
|
|
299
|
+
// refreshExtensionConfig / saveExtensionConfig / logResolvedConfigPaths are
|
|
300
|
+
// thin delegators to runtime.configStore — behavior covered in config-store.test.ts.
|
|
485
301
|
|
|
486
302
|
// resolveAgentName was moved to PermissionSession (#129)
|
|
487
|
-
// Tests live in
|
|
303
|
+
// Tests live in test/permission-session.test.ts
|