@gotgenes/pi-permission-system 3.7.0 → 3.8.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/src/index.ts CHANGED
@@ -1,47 +1,7 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- renameSync,
5
- unlinkSync,
6
- writeFileSync,
7
- } from "node:fs";
8
- import { dirname, join, normalize } from "node:path";
9
- import {
10
- type ExtensionAPI,
11
- type ExtensionCommandContext,
12
- type ExtensionContext,
13
- getAgentDir,
14
- } from "@mariozechner/pi-coding-agent";
15
- import {
16
- getActiveAgentName,
17
- getActiveAgentNameFromSystemPrompt,
18
- } from "./active-agent";
19
- import { loadAndMergeConfigs, loadUnifiedConfig } from "./config-loader";
1
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
20
2
  import { registerPermissionSystemCommand } from "./config-modal";
21
- import {
22
- DEBUG_LOG_FILENAME,
23
- getGlobalConfigPath,
24
- getGlobalLogsDir,
25
- getLegacyExtensionConfigPath,
26
- getLegacyGlobalPolicyPath,
27
- getLegacyProjectPolicyPath,
28
- getProjectConfigPath,
29
- REVIEW_LOG_FILENAME,
30
- } from "./config-paths";
31
- import { buildResolvedConfigLogEntry } from "./config-reporter";
32
- import {
33
- DEFAULT_EXTENSION_CONFIG,
34
- EXTENSION_ROOT,
35
- ensurePermissionSystemLogsDirectory,
36
- normalizePermissionSystemConfig,
37
- type PermissionSystemExtensionConfig,
38
- } from "./extension-config";
39
- import { setForwardedPermissionLogger } from "./forwarded-permissions/io";
40
- import {
41
- confirmPermission,
42
- type PermissionForwardingDeps,
43
- processForwardedPermissionRequests,
44
- } from "./forwarded-permissions/polling";
3
+ import { getGlobalConfigPath } from "./config-paths";
4
+ import type { PermissionForwardingDeps } from "./forwarded-permissions/polling";
45
5
  import {
46
6
  type HandlerDeps,
47
7
  handleBeforeAgentStart,
@@ -51,406 +11,79 @@ import {
51
11
  handleSessionStart,
52
12
  handleToolCall,
53
13
  } from "./handlers";
54
- import type { PromptPermissionDetails } from "./handlers/types";
55
- import { createPermissionSystemLogger } from "./logging";
56
14
  import {
57
15
  type PermissionPromptDecision,
58
16
  requestPermissionDecisionFromUi,
59
17
  } from "./permission-dialog";
60
- import { PERMISSION_FORWARDING_POLL_INTERVAL_MS } from "./permission-forwarding";
61
- import { PermissionManager } from "./permission-manager";
62
- import { SessionApprovalCache } from "./session-approval-cache";
63
- import type { SkillPromptEntry } from "./skill-prompt-sanitizer";
64
18
  import {
65
- PERMISSION_SYSTEM_STATUS_KEY,
66
- syncPermissionSystemStatus,
67
- } from "./status";
19
+ createExtensionRuntime,
20
+ createPermissionManagerForCwd,
21
+ logResolvedConfigPaths,
22
+ promptPermission,
23
+ refreshExtensionConfig,
24
+ resolveAgentName,
25
+ saveExtensionConfig,
26
+ startForwardedPermissionPolling,
27
+ stopForwardedPermissionPolling,
28
+ } from "./runtime";
68
29
  import { isSubagentExecutionContext } from "./subagent-context";
69
30
  import {
70
31
  canResolveAskPermissionRequest,
71
32
  shouldAutoApprovePermissionState,
72
33
  } from "./yolo-mode";
73
34
 
74
- const PI_AGENT_DIR = getAgentDir();
75
- const SESSIONS_DIR = join(PI_AGENT_DIR, "sessions");
76
- const SUBAGENT_SESSIONS_DIR = join(PI_AGENT_DIR, "subagent-sessions");
77
- const PERMISSION_FORWARDING_DIR = join(SESSIONS_DIR, "permission-forwarding");
78
-
79
- let extensionConfig: PermissionSystemExtensionConfig = {
80
- ...DEFAULT_EXTENSION_CONFIG,
81
- };
82
- const GLOBAL_LOGS_DIR = getGlobalLogsDir(PI_AGENT_DIR);
83
- const extensionLogger = createPermissionSystemLogger({
84
- getConfig: () => extensionConfig,
85
- debugLogPath: join(GLOBAL_LOGS_DIR, DEBUG_LOG_FILENAME),
86
- reviewLogPath: join(GLOBAL_LOGS_DIR, REVIEW_LOG_FILENAME),
87
- ensureLogsDirectory: () =>
88
- ensurePermissionSystemLogsDirectory(GLOBAL_LOGS_DIR),
89
- });
90
- const reportedLoggingWarnings = new Set<string>();
91
- let loggingWarningReporter: ((message: string) => void) | null = null;
92
-
93
- function setExtensionConfig(config: PermissionSystemExtensionConfig): void {
94
- extensionConfig = normalizePermissionSystemConfig(config);
95
- }
96
-
97
- function setLoggingWarningReporter(
98
- reporter: ((message: string) => void) | null,
99
- ): void {
100
- loggingWarningReporter = reporter;
101
- }
102
-
103
- function reportLoggingWarning(message: string): void {
104
- if (!loggingWarningReporter || reportedLoggingWarnings.has(message)) {
105
- return;
106
- }
107
- reportedLoggingWarnings.add(message);
108
- loggingWarningReporter(message);
109
- }
110
-
111
- function writeDebugLog(
112
- event: string,
113
- details: Record<string, unknown> = {},
114
- ): void {
115
- const warning = extensionLogger.debug(event, details);
116
- if (warning) {
117
- reportLoggingWarning(warning);
118
- }
119
- }
120
-
121
- function writeReviewLog(
122
- event: string,
123
- details: Record<string, unknown> = {},
124
- ): void {
125
- const warning = extensionLogger.review(event, details);
126
- if (warning) {
127
- reportLoggingWarning(warning);
128
- }
129
- }
130
-
131
- function derivePiProjectPaths(cwd: string | undefined | null): {
132
- projectGlobalConfigPath: string;
133
- projectAgentsDir: string;
134
- } | null {
135
- if (!cwd) {
136
- return null;
137
- }
138
- return {
139
- projectGlobalConfigPath: getProjectConfigPath(cwd),
140
- projectAgentsDir: join(cwd, ".pi", "agent", "agents"),
141
- };
142
- }
143
-
144
- function createPermissionManagerForCwd(
145
- cwd: string | undefined | null,
146
- ): PermissionManager {
147
- const agentDir = getAgentDir();
148
- const projectPaths = derivePiProjectPaths(cwd);
149
- return new PermissionManager({
150
- globalConfigPath: getGlobalConfigPath(agentDir),
151
- projectGlobalConfigPath: projectPaths?.projectGlobalConfigPath,
152
- projectAgentsDir: projectPaths?.projectAgentsDir,
153
- });
154
- }
155
-
156
35
  export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
157
- let permissionManager = new PermissionManager();
158
- const sessionApprovalCache = new SessionApprovalCache();
159
- let activeSkillEntries: SkillPromptEntry[] = [];
160
- let lastKnownActiveAgentName: string | null = null;
161
- let lastActiveToolsCacheKey: string | null = null;
162
- let lastPromptStateCacheKey: string | null = null;
163
- let permissionForwardingContext: ExtensionContext | null = null;
164
- let permissionForwardingTimer: NodeJS.Timeout | null = null;
165
- let isProcessingForwardedRequests = false;
166
- let runtimeContext: ExtensionContext | null = null;
167
- let lastConfigWarning: string | null = null;
168
-
169
- const notifyWarning = (message: string): void => {
170
- if (!runtimeContext?.hasUI) {
171
- return;
172
- }
173
- runtimeContext.ui.notify(message, "warning");
174
- };
175
-
176
- const refreshExtensionConfig = (ctx?: ExtensionContext): void => {
177
- if (ctx) {
178
- runtimeContext = ctx;
179
- }
180
- const cwd = runtimeContext?.cwd ?? null;
181
- const agentDir = getAgentDir();
182
- const mergeResult = loadAndMergeConfigs(
183
- agentDir,
184
- cwd ?? "",
185
- EXTENSION_ROOT,
186
- );
187
- const runtimeConfig = normalizePermissionSystemConfig(mergeResult.merged);
188
- setExtensionConfig(runtimeConfig);
189
-
190
- if (runtimeContext?.hasUI) {
191
- syncPermissionSystemStatus(runtimeContext, runtimeConfig);
192
- }
193
-
194
- const warning =
195
- mergeResult.issues.length > 0 ? mergeResult.issues.join("\n") : undefined;
196
- if (warning && warning !== lastConfigWarning) {
197
- lastConfigWarning = warning;
198
- notifyWarning(warning);
199
- } else if (!warning) {
200
- lastConfigWarning = null;
201
- }
202
-
203
- writeDebugLog("config.loaded", {
204
- warning: warning ?? null,
205
- debugLog: runtimeConfig.debugLog,
206
- permissionReviewLog: runtimeConfig.permissionReviewLog,
207
- yoloMode: runtimeConfig.yoloMode,
208
- });
209
- };
210
-
211
- const saveExtensionConfig = (
212
- next: PermissionSystemExtensionConfig,
213
- ctx: ExtensionCommandContext,
214
- ): void => {
215
- const normalized = normalizePermissionSystemConfig(next);
216
- const globalPath = getGlobalConfigPath(getAgentDir());
217
-
218
- const existing = loadUnifiedConfig(globalPath);
219
- const merged = {
220
- ...existing.config,
221
- debugLog: normalized.debugLog,
222
- permissionReviewLog: normalized.permissionReviewLog,
223
- yoloMode: normalized.yoloMode,
224
- };
225
-
226
- const tmpPath = `${globalPath}.tmp`;
227
- try {
228
- mkdirSync(dirname(globalPath), { recursive: true });
229
- writeFileSync(tmpPath, `${JSON.stringify(merged, null, 2)}\n`, "utf-8");
230
- renameSync(tmpPath, globalPath);
231
- } catch (error) {
232
- try {
233
- if (existsSync(tmpPath)) {
234
- unlinkSync(tmpPath);
235
- }
236
- } catch {
237
- // Ignore cleanup failures.
238
- }
239
- const message = error instanceof Error ? error.message : String(error);
240
- ctx.ui.notify(
241
- `Failed to save permission-system config at '${globalPath}': ${message}`,
242
- "error",
243
- );
244
- return;
245
- }
246
-
247
- setExtensionConfig(normalized);
248
- syncPermissionSystemStatus(ctx, normalized);
249
- lastConfigWarning = null;
250
-
251
- writeDebugLog("config.saved", {
252
- debugLog: normalized.debugLog,
253
- permissionReviewLog: normalized.permissionReviewLog,
254
- yoloMode: normalized.yoloMode,
255
- });
256
- };
257
-
258
- setLoggingWarningReporter(notifyWarning);
259
- setForwardedPermissionLogger({ writeReviewLog, writeDebugLog });
36
+ const runtime = createExtensionRuntime();
260
37
 
261
38
  const forwardingDeps: PermissionForwardingDeps = {
262
- forwardingDir: PERMISSION_FORWARDING_DIR,
263
- subagentSessionsDir: SUBAGENT_SESSIONS_DIR,
264
- writeReviewLog,
39
+ forwardingDir: runtime.forwardingDir,
40
+ subagentSessionsDir: runtime.subagentSessionsDir,
41
+ logger: {
42
+ writeReviewLog: runtime.writeReviewLog.bind(runtime),
43
+ writeDebugLog: runtime.writeDebugLog.bind(runtime),
44
+ },
45
+ writeReviewLog: runtime.writeReviewLog.bind(runtime),
265
46
  requestPermissionDecisionFromUi,
266
47
  shouldAutoApprove: () =>
267
- shouldAutoApprovePermissionState("ask", extensionConfig),
48
+ shouldAutoApprovePermissionState("ask", runtime.config),
268
49
  };
269
50
 
270
- refreshExtensionConfig();
51
+ refreshExtensionConfig(runtime);
271
52
  registerPermissionSystemCommand(pi, {
272
- getConfig: () => extensionConfig,
273
- setConfig: saveExtensionConfig,
274
- getConfigPath: () => getGlobalConfigPath(getAgentDir()),
53
+ getConfig: () => runtime.config,
54
+ setConfig: (next, ctx) => saveExtensionConfig(runtime, next, ctx),
55
+ getConfigPath: () => getGlobalConfigPath(runtime.agentDir),
275
56
  });
276
57
 
277
- const stopForwardedPermissionPolling = (): void => {
278
- if (permissionForwardingTimer) {
279
- clearInterval(permissionForwardingTimer);
280
- permissionForwardingTimer = null;
281
- }
282
- permissionForwardingContext = null;
283
- isProcessingForwardedRequests = false;
284
- };
285
-
286
- const startForwardedPermissionPolling = (ctx: ExtensionContext): void => {
287
- if (!ctx.hasUI || isSubagentExecutionContext(ctx, SUBAGENT_SESSIONS_DIR)) {
288
- stopForwardedPermissionPolling();
289
- return;
290
- }
291
- permissionForwardingContext = ctx;
292
- if (permissionForwardingTimer) {
293
- return;
294
- }
295
- permissionForwardingTimer = setInterval(() => {
296
- if (!permissionForwardingContext || isProcessingForwardedRequests) {
297
- return;
298
- }
299
- isProcessingForwardedRequests = true;
300
- void processForwardedPermissionRequests(
301
- permissionForwardingContext,
302
- forwardingDeps,
303
- ).finally(() => {
304
- isProcessingForwardedRequests = false;
305
- });
306
- }, PERMISSION_FORWARDING_POLL_INTERVAL_MS);
307
- };
308
-
309
58
  const createPermissionRequestId = (prefix: string): string =>
310
59
  `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${process.pid}`;
311
60
 
312
- const reviewPermissionDecision = (
313
- event: string,
314
- details: PromptPermissionDetails & {
315
- resolution?: string;
316
- denialReason?: string;
317
- },
318
- ): void => {
319
- writeReviewLog(event, {
320
- requestId: details.requestId,
321
- source: details.source,
322
- agentName: details.agentName,
323
- message: details.message,
324
- toolCallId: details.toolCallId ?? null,
325
- toolName: details.toolName ?? null,
326
- skillName: details.skillName ?? null,
327
- path: details.path ?? null,
328
- command: details.command ?? null,
329
- target: details.target ?? null,
330
- toolInputPreview: details.toolInputPreview ?? null,
331
- resolution: details.resolution ?? null,
332
- denialReason: details.denialReason ?? null,
333
- });
334
- };
335
-
336
- const promptPermission = async (
337
- ctx: ExtensionContext,
338
- details: PromptPermissionDetails,
339
- ): Promise<PermissionPromptDecision> => {
340
- if (shouldAutoApprovePermissionState("ask", extensionConfig)) {
341
- reviewPermissionDecision("permission_request.auto_approved", details);
342
- return { approved: true, state: "approved" };
343
- }
344
- reviewPermissionDecision("permission_request.waiting", details);
345
- const decision = await confirmPermission(
346
- ctx,
347
- details.message,
348
- forwardingDeps,
349
- );
350
- reviewPermissionDecision(
351
- decision.approved
352
- ? "permission_request.approved"
353
- : "permission_request.denied",
354
- {
355
- ...details,
356
- resolution: decision.state,
357
- denialReason: decision.denialReason,
358
- },
359
- );
360
- return decision;
361
- };
362
-
363
- const resolveAgentName = (
364
- ctx: ExtensionContext,
365
- systemPrompt?: string,
366
- ): string | null => {
367
- const fromSession = getActiveAgentName(ctx);
368
- if (fromSession) {
369
- lastKnownActiveAgentName = fromSession;
370
- return fromSession;
371
- }
372
- const fromSystemPrompt = getActiveAgentNameFromSystemPrompt(systemPrompt);
373
- if (fromSystemPrompt) {
374
- lastKnownActiveAgentName = fromSystemPrompt;
375
- return fromSystemPrompt;
376
- }
377
- return lastKnownActiveAgentName;
378
- };
379
-
380
- const logResolvedConfigPaths = (): void => {
381
- const policyPaths = permissionManager.getResolvedPolicyPaths();
382
- const cwd = runtimeContext?.cwd ?? null;
383
- const agentDir = getAgentDir();
384
- const legacyGlobalPolicyDetected = existsSync(
385
- getLegacyGlobalPolicyPath(agentDir),
386
- );
387
- const legacyProjectPolicyDetected = cwd
388
- ? existsSync(getLegacyProjectPolicyPath(cwd))
389
- : false;
390
- const legacyExtConfigPath = getLegacyExtensionConfigPath(EXTENSION_ROOT);
391
- const newGlobalPath = getGlobalConfigPath(agentDir);
392
- const legacyExtensionConfigDetected =
393
- normalize(legacyExtConfigPath) !== normalize(newGlobalPath) &&
394
- existsSync(legacyExtConfigPath);
395
- const entry = buildResolvedConfigLogEntry({
396
- policyPaths,
397
- legacyGlobalPolicyDetected,
398
- legacyProjectPolicyDetected,
399
- legacyExtensionConfigDetected,
400
- });
401
- writeReviewLog(
402
- "config.resolved",
403
- entry as unknown as Record<string, unknown>,
404
- );
405
- writeDebugLog(
406
- "config.resolved",
407
- entry as unknown as Record<string, unknown>,
408
- );
409
- };
410
-
411
61
  const deps: HandlerDeps = {
412
- getPermissionManager: () => permissionManager,
413
- setPermissionManager: (pm) => {
414
- permissionManager = pm;
415
- },
416
- getRuntimeContext: () => runtimeContext,
417
- setRuntimeContext: (ctx) => {
418
- runtimeContext = ctx;
419
- },
420
- getActiveSkillEntries: () => activeSkillEntries,
421
- setActiveSkillEntries: (entries) => {
422
- activeSkillEntries = entries;
423
- },
424
- getLastKnownActiveAgentName: () => lastKnownActiveAgentName,
425
- setLastKnownActiveAgentName: (name) => {
426
- lastKnownActiveAgentName = name;
427
- },
428
- getLastActiveToolsCacheKey: () => lastActiveToolsCacheKey,
429
- setLastActiveToolsCacheKey: (key) => {
430
- lastActiveToolsCacheKey = key;
431
- },
432
- getLastPromptStateCacheKey: () => lastPromptStateCacheKey,
433
- setLastPromptStateCacheKey: (key) => {
434
- lastPromptStateCacheKey = key;
435
- },
436
- sessionApprovalCache,
437
- createPermissionManagerForCwd,
438
- refreshExtensionConfig,
439
- notifyWarning,
440
- logResolvedConfigPaths,
441
- resolveAgentName,
62
+ runtime,
63
+ createPermissionManagerForCwd: (cwd) =>
64
+ createPermissionManagerForCwd(runtime.agentDir, cwd),
65
+ refreshExtensionConfig: (ctx) => refreshExtensionConfig(runtime, ctx),
66
+ notifyWarning: (message) =>
67
+ runtime.runtimeContext?.ui.notify(message, "warning"),
68
+ logResolvedConfigPaths: () => logResolvedConfigPaths(runtime),
69
+ resolveAgentName: (ctx, systemPrompt) =>
70
+ resolveAgentName(runtime, ctx, systemPrompt),
442
71
  canRequestPermissionConfirmation: (ctx) =>
443
72
  canResolveAskPermissionRequest({
444
- config: extensionConfig,
73
+ config: runtime.config,
445
74
  hasUI: ctx.hasUI,
446
- isSubagent: isSubagentExecutionContext(ctx, SUBAGENT_SESSIONS_DIR),
75
+ isSubagent: isSubagentExecutionContext(
76
+ ctx,
77
+ runtime.subagentSessionsDir,
78
+ ),
447
79
  }),
448
- promptPermission,
80
+ promptPermission: (ctx, details) =>
81
+ promptPermission(runtime, forwardingDeps, ctx, details),
449
82
  createPermissionRequestId,
450
- startForwardedPermissionPolling,
451
- stopForwardedPermissionPolling,
452
- writeReviewLog,
453
- writeDebugLog,
83
+ startForwardedPermissionPolling: (ctx) =>
84
+ startForwardedPermissionPolling(runtime, forwardingDeps, ctx),
85
+ stopForwardedPermissionPolling: () =>
86
+ stopForwardedPermissionPolling(runtime),
454
87
  getAllTools: () => pi.getAllTools(),
455
88
  setActiveTools: (names) => pi.setActiveTools(names),
456
89
  };