@gotgenes/pi-permission-system 3.7.0 → 3.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 +39 -0
- package/package.json +1 -1
- package/src/defaults.ts +60 -0
- package/src/forwarded-permissions/io.ts +47 -12
- package/src/forwarded-permissions/polling.ts +33 -11
- package/src/handlers/before-agent-start.ts +7 -7
- package/src/handlers/input.ts +7 -5
- package/src/handlers/lifecycle.ts +25 -24
- package/src/handlers/tool-call.ts +32 -22
- package/src/handlers/types.ts +7 -30
- package/src/index.ts +47 -417
- package/src/normalize.ts +70 -0
- package/src/permission-manager.ts +127 -254
- package/src/rule.ts +7 -23
- package/src/runtime.ts +484 -0
- package/src/types.ts +13 -18
- package/tests/defaults.test.ts +105 -0
- package/tests/forwarded-permissions/io.test.ts +135 -0
- package/tests/handlers/before-agent-start.test.ts +47 -31
- package/tests/handlers/input.test.ts +69 -39
- package/tests/handlers/lifecycle.test.ts +86 -65
- package/tests/handlers/tool-call.test.ts +92 -69
- package/tests/normalize.test.ts +121 -0
- package/tests/permission-system.test.ts +11 -39
- package/tests/rule.test.ts +24 -42
- package/tests/runtime.test.ts +618 -0
- package/tests/session-start.test.ts +2 -2
- package/src/bash-filter.ts +0 -51
- package/tests/bash-filter.test.ts +0 -142
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
|
-
|
|
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,76 @@ import {
|
|
|
51
11
|
handleSessionStart,
|
|
52
12
|
handleToolCall,
|
|
53
13
|
} from "./handlers";
|
|
54
|
-
import
|
|
55
|
-
import { createPermissionSystemLogger } from "./logging";
|
|
14
|
+
import { requestPermissionDecisionFromUi } from "./permission-dialog";
|
|
56
15
|
import {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} from "./status";
|
|
16
|
+
createExtensionRuntime,
|
|
17
|
+
createPermissionManagerForCwd,
|
|
18
|
+
logResolvedConfigPaths,
|
|
19
|
+
promptPermission,
|
|
20
|
+
refreshExtensionConfig,
|
|
21
|
+
resolveAgentName,
|
|
22
|
+
saveExtensionConfig,
|
|
23
|
+
startForwardedPermissionPolling,
|
|
24
|
+
stopForwardedPermissionPolling,
|
|
25
|
+
} from "./runtime";
|
|
68
26
|
import { isSubagentExecutionContext } from "./subagent-context";
|
|
69
27
|
import {
|
|
70
28
|
canResolveAskPermissionRequest,
|
|
71
29
|
shouldAutoApprovePermissionState,
|
|
72
30
|
} from "./yolo-mode";
|
|
73
31
|
|
|
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
32
|
export default function piPermissionSystemExtension(pi: ExtensionAPI): void {
|
|
157
|
-
|
|
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 });
|
|
33
|
+
const runtime = createExtensionRuntime();
|
|
260
34
|
|
|
261
35
|
const forwardingDeps: PermissionForwardingDeps = {
|
|
262
|
-
forwardingDir:
|
|
263
|
-
subagentSessionsDir:
|
|
264
|
-
|
|
36
|
+
forwardingDir: runtime.forwardingDir,
|
|
37
|
+
subagentSessionsDir: runtime.subagentSessionsDir,
|
|
38
|
+
logger: {
|
|
39
|
+
writeReviewLog: runtime.writeReviewLog.bind(runtime),
|
|
40
|
+
writeDebugLog: runtime.writeDebugLog.bind(runtime),
|
|
41
|
+
},
|
|
42
|
+
writeReviewLog: runtime.writeReviewLog.bind(runtime),
|
|
265
43
|
requestPermissionDecisionFromUi,
|
|
266
44
|
shouldAutoApprove: () =>
|
|
267
|
-
shouldAutoApprovePermissionState("ask",
|
|
45
|
+
shouldAutoApprovePermissionState("ask", runtime.config),
|
|
268
46
|
};
|
|
269
47
|
|
|
270
|
-
refreshExtensionConfig();
|
|
48
|
+
refreshExtensionConfig(runtime);
|
|
271
49
|
registerPermissionSystemCommand(pi, {
|
|
272
|
-
getConfig: () =>
|
|
273
|
-
setConfig: saveExtensionConfig,
|
|
274
|
-
getConfigPath: () => getGlobalConfigPath(
|
|
50
|
+
getConfig: () => runtime.config,
|
|
51
|
+
setConfig: (next, ctx) => saveExtensionConfig(runtime, next, ctx),
|
|
52
|
+
getConfigPath: () => getGlobalConfigPath(runtime.agentDir),
|
|
275
53
|
});
|
|
276
54
|
|
|
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
55
|
const createPermissionRequestId = (prefix: string): string =>
|
|
310
56
|
`${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 10)}-${process.pid}`;
|
|
311
57
|
|
|
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
58
|
const deps: HandlerDeps = {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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,
|
|
59
|
+
runtime,
|
|
60
|
+
createPermissionManagerForCwd: (cwd) =>
|
|
61
|
+
createPermissionManagerForCwd(runtime.agentDir, cwd),
|
|
62
|
+
refreshExtensionConfig: (ctx) => refreshExtensionConfig(runtime, ctx),
|
|
63
|
+
notifyWarning: (message) =>
|
|
64
|
+
runtime.runtimeContext?.ui.notify(message, "warning"),
|
|
65
|
+
logResolvedConfigPaths: () => logResolvedConfigPaths(runtime),
|
|
66
|
+
resolveAgentName: (ctx, systemPrompt) =>
|
|
67
|
+
resolveAgentName(runtime, ctx, systemPrompt),
|
|
442
68
|
canRequestPermissionConfirmation: (ctx) =>
|
|
443
69
|
canResolveAskPermissionRequest({
|
|
444
|
-
config:
|
|
70
|
+
config: runtime.config,
|
|
445
71
|
hasUI: ctx.hasUI,
|
|
446
|
-
isSubagent: isSubagentExecutionContext(
|
|
72
|
+
isSubagent: isSubagentExecutionContext(
|
|
73
|
+
ctx,
|
|
74
|
+
runtime.subagentSessionsDir,
|
|
75
|
+
),
|
|
447
76
|
}),
|
|
448
|
-
promptPermission,
|
|
77
|
+
promptPermission: (ctx, details) =>
|
|
78
|
+
promptPermission(runtime, forwardingDeps, ctx, details),
|
|
449
79
|
createPermissionRequestId,
|
|
450
|
-
startForwardedPermissionPolling
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
80
|
+
startForwardedPermissionPolling: (ctx) =>
|
|
81
|
+
startForwardedPermissionPolling(runtime, forwardingDeps, ctx),
|
|
82
|
+
stopForwardedPermissionPolling: () =>
|
|
83
|
+
stopForwardedPermissionPolling(runtime),
|
|
454
84
|
getAllTools: () => pi.getAllTools(),
|
|
455
85
|
setActiveTools: (names) => pi.setActiveTools(names),
|
|
456
86
|
};
|
package/src/normalize.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Rule, Ruleset } from "./rule";
|
|
2
|
+
import type { PermissionState } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Subset of UnifiedPermissionConfig covering only policy fields.
|
|
6
|
+
* Used as the input shape for normalizeConfig().
|
|
7
|
+
*/
|
|
8
|
+
export interface NormalizableConfig {
|
|
9
|
+
tools?: Record<string, PermissionState>;
|
|
10
|
+
bash?: Record<string, PermissionState>;
|
|
11
|
+
mcp?: Record<string, PermissionState>;
|
|
12
|
+
skills?: Record<string, PermissionState>;
|
|
13
|
+
special?: Record<string, PermissionState>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Keys in the `tools` map that serve as fallback defaults for their
|
|
18
|
+
* respective pattern-based surfaces rather than as tool-level rules.
|
|
19
|
+
*
|
|
20
|
+
* `tools.bash` sets the bash default (fallback when no bash pattern matches).
|
|
21
|
+
* `tools.mcp` sets the tool-level MCP fallback.
|
|
22
|
+
*
|
|
23
|
+
* These are NOT normalized into the Ruleset — they are extracted by the
|
|
24
|
+
* caller and handled as separate fallbacks to preserve the semantic that
|
|
25
|
+
* specific bash/mcp patterns always have priority.
|
|
26
|
+
*/
|
|
27
|
+
export const TOOL_SURFACE_OVERRIDE_KEYS: ReadonlySet<string> = new Set([
|
|
28
|
+
"bash",
|
|
29
|
+
"mcp",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Convert the on-disk config shape into a flat Ruleset.
|
|
34
|
+
*
|
|
35
|
+
* Ordering within a scope:
|
|
36
|
+
* 1. tools entries (tool-name-as-surface, pattern "*") — excluding bash/mcp
|
|
37
|
+
* 2. bash entries (surface "bash", pattern = command glob)
|
|
38
|
+
* 3. mcp entries (surface "mcp", pattern = target glob)
|
|
39
|
+
* 4. skills entries (surface "skill", pattern = skill glob)
|
|
40
|
+
* 5. special entries (surface "special", pattern = key name)
|
|
41
|
+
*
|
|
42
|
+
* `tools.bash` and `tools.mcp` are excluded — see TOOL_SURFACE_OVERRIDE_KEYS.
|
|
43
|
+
* `defaultPolicy` is NOT included — handled separately by the caller.
|
|
44
|
+
*/
|
|
45
|
+
export function normalizeConfig(config: NormalizableConfig): Ruleset {
|
|
46
|
+
const rules: Rule[] = [];
|
|
47
|
+
|
|
48
|
+
for (const [name, action] of Object.entries(config.tools ?? {})) {
|
|
49
|
+
if (TOOL_SURFACE_OVERRIDE_KEYS.has(name)) continue;
|
|
50
|
+
rules.push({ surface: name, pattern: "*", action });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const [pattern, action] of Object.entries(config.bash ?? {})) {
|
|
54
|
+
rules.push({ surface: "bash", pattern, action });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const [pattern, action] of Object.entries(config.mcp ?? {})) {
|
|
58
|
+
rules.push({ surface: "mcp", pattern, action });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const [pattern, action] of Object.entries(config.skills ?? {})) {
|
|
62
|
+
rules.push({ surface: "skill", pattern, action });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
for (const [name, action] of Object.entries(config.special ?? {})) {
|
|
66
|
+
rules.push({ surface: "special", pattern: name, action });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return rules;
|
|
70
|
+
}
|