@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/CHANGELOG.md +18 -0
- package/package.json +1 -1
- 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 +46 -413
- package/src/runtime.ts +484 -0
- package/tests/forwarded-permissions/io.test.ts +135 -0
- package/tests/handlers/before-agent-start.test.ts +46 -30
- 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/runtime.test.ts +618 -0
|
@@ -61,7 +61,7 @@ export async function handleToolCall(
|
|
|
61
61
|
event: unknown,
|
|
62
62
|
ctx: ExtensionContext,
|
|
63
63
|
): Promise<{ block?: true; reason?: string }> {
|
|
64
|
-
deps.
|
|
64
|
+
deps.runtime.runtimeContext = ctx;
|
|
65
65
|
deps.startForwardedPermissionPolling(ctx);
|
|
66
66
|
|
|
67
67
|
const agentName = deps.resolveAgentName(ctx);
|
|
@@ -92,7 +92,7 @@ export async function handleToolCall(
|
|
|
92
92
|
// ── Skill-read gate ──────────────────────────────────────────────────────
|
|
93
93
|
if (
|
|
94
94
|
isToolCallEventType("read", event as ToolCallEvent) &&
|
|
95
|
-
deps.
|
|
95
|
+
deps.runtime.activeSkillEntries.length > 0
|
|
96
96
|
) {
|
|
97
97
|
const normalizedReadPath = normalizePathForComparison(
|
|
98
98
|
(event as ToolCallEvent & { input: { path: string } }).input.path,
|
|
@@ -100,7 +100,7 @@ export async function handleToolCall(
|
|
|
100
100
|
);
|
|
101
101
|
const matchedSkill = findSkillPathMatch(
|
|
102
102
|
normalizedReadPath,
|
|
103
|
-
deps.
|
|
103
|
+
deps.runtime.activeSkillEntries,
|
|
104
104
|
);
|
|
105
105
|
|
|
106
106
|
if (matchedSkill) {
|
|
@@ -124,7 +124,7 @@ export async function handleToolCall(
|
|
|
124
124
|
skillName: matchedSkill.name,
|
|
125
125
|
path: readEvent.input.path,
|
|
126
126
|
}),
|
|
127
|
-
writeLog: deps.writeReviewLog,
|
|
127
|
+
writeLog: deps.runtime.writeReviewLog,
|
|
128
128
|
logContext: {
|
|
129
129
|
source: "skill_read",
|
|
130
130
|
skillName: matchedSkill.name,
|
|
@@ -169,13 +169,13 @@ export async function handleToolCall(
|
|
|
169
169
|
externalDirectoryPath,
|
|
170
170
|
ctx.cwd,
|
|
171
171
|
);
|
|
172
|
-
const sessionPrefix = deps.sessionApprovalCache.findMatchingPrefix(
|
|
172
|
+
const sessionPrefix = deps.runtime.sessionApprovalCache.findMatchingPrefix(
|
|
173
173
|
"external_directory",
|
|
174
174
|
normalizedExtPath,
|
|
175
175
|
);
|
|
176
176
|
|
|
177
177
|
if (sessionPrefix) {
|
|
178
|
-
deps.writeReviewLog("permission_request.session_approved", {
|
|
178
|
+
deps.runtime.writeReviewLog("permission_request.session_approved", {
|
|
179
179
|
source: "tool_call",
|
|
180
180
|
toolCallId: (event as { toolCallId: string }).toolCallId,
|
|
181
181
|
toolName,
|
|
@@ -186,9 +186,11 @@ export async function handleToolCall(
|
|
|
186
186
|
});
|
|
187
187
|
// Fall through to normal permission check
|
|
188
188
|
} else {
|
|
189
|
-
const extCheck = deps
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
const extCheck = deps.runtime.permissionManager.checkPermission(
|
|
190
|
+
"external_directory",
|
|
191
|
+
{},
|
|
192
|
+
agentName ?? undefined,
|
|
193
|
+
);
|
|
192
194
|
|
|
193
195
|
let extDirDecision: PermissionPromptDecision | null = null;
|
|
194
196
|
const extDirMessage = formatExternalDirectoryAskPrompt(
|
|
@@ -213,7 +215,7 @@ export async function handleToolCall(
|
|
|
213
215
|
extDirDecision = decision;
|
|
214
216
|
return decision;
|
|
215
217
|
},
|
|
216
|
-
writeLog: deps.writeReviewLog,
|
|
218
|
+
writeLog: deps.runtime.writeReviewLog,
|
|
217
219
|
logContext: {
|
|
218
220
|
source: "tool_call",
|
|
219
221
|
toolCallId: (event as { toolCallId: string }).toolCallId,
|
|
@@ -244,7 +246,7 @@ export async function handleToolCall(
|
|
|
244
246
|
|
|
245
247
|
if (extDirDecision?.state === "approved_for_session") {
|
|
246
248
|
const prefix = deriveApprovalPrefix(normalizedExtPath);
|
|
247
|
-
deps.sessionApprovalCache.approve("external_directory", prefix);
|
|
249
|
+
deps.runtime.sessionApprovalCache.approve("external_directory", prefix);
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
// Fall through to normal permission check
|
|
@@ -260,11 +262,12 @@ export async function handleToolCall(
|
|
|
260
262
|
);
|
|
261
263
|
if (externalPaths.length > 0) {
|
|
262
264
|
const uncoveredPaths = externalPaths.filter(
|
|
263
|
-
(p) =>
|
|
265
|
+
(p) =>
|
|
266
|
+
!deps.runtime.sessionApprovalCache.has("external_directory", p),
|
|
264
267
|
);
|
|
265
268
|
|
|
266
269
|
if (uncoveredPaths.length === 0) {
|
|
267
|
-
deps.writeReviewLog("permission_request.session_approved", {
|
|
270
|
+
deps.runtime.writeReviewLog("permission_request.session_approved", {
|
|
268
271
|
source: "tool_call",
|
|
269
272
|
toolCallId: (event as { toolCallId: string }).toolCallId,
|
|
270
273
|
toolName,
|
|
@@ -275,9 +278,11 @@ export async function handleToolCall(
|
|
|
275
278
|
});
|
|
276
279
|
// Fall through to normal bash permission check
|
|
277
280
|
} else {
|
|
278
|
-
const extCheck = deps
|
|
279
|
-
|
|
280
|
-
|
|
281
|
+
const extCheck = deps.runtime.permissionManager.checkPermission(
|
|
282
|
+
"external_directory",
|
|
283
|
+
{},
|
|
284
|
+
agentName ?? undefined,
|
|
285
|
+
);
|
|
281
286
|
|
|
282
287
|
let bashExtDecision: PermissionPromptDecision | null = null;
|
|
283
288
|
const bashExtMessage = formatBashExternalDirectoryAskPrompt(
|
|
@@ -302,7 +307,7 @@ export async function handleToolCall(
|
|
|
302
307
|
bashExtDecision = decision;
|
|
303
308
|
return decision;
|
|
304
309
|
},
|
|
305
|
-
writeLog: deps.writeReviewLog,
|
|
310
|
+
writeLog: deps.runtime.writeReviewLog,
|
|
306
311
|
logContext: {
|
|
307
312
|
source: "tool_call",
|
|
308
313
|
toolCallId: (event as { toolCallId: string }).toolCallId,
|
|
@@ -335,7 +340,10 @@ export async function handleToolCall(
|
|
|
335
340
|
if (bashExtDecision?.state === "approved_for_session") {
|
|
336
341
|
for (const extPath of uncoveredPaths) {
|
|
337
342
|
const prefix = deriveApprovalPrefix(extPath);
|
|
338
|
-
deps.sessionApprovalCache.approve(
|
|
343
|
+
deps.runtime.sessionApprovalCache.approve(
|
|
344
|
+
"external_directory",
|
|
345
|
+
prefix,
|
|
346
|
+
);
|
|
339
347
|
}
|
|
340
348
|
}
|
|
341
349
|
}
|
|
@@ -345,9 +353,11 @@ export async function handleToolCall(
|
|
|
345
353
|
}
|
|
346
354
|
|
|
347
355
|
// ── Normal tool permission gate ───────────────────────────────────────────
|
|
348
|
-
const check = deps
|
|
349
|
-
|
|
350
|
-
|
|
356
|
+
const check = deps.runtime.permissionManager.checkPermission(
|
|
357
|
+
toolName,
|
|
358
|
+
input,
|
|
359
|
+
agentName ?? undefined,
|
|
360
|
+
);
|
|
351
361
|
const permissionLogContext = getPermissionLogContext(
|
|
352
362
|
check,
|
|
353
363
|
input,
|
|
@@ -375,7 +385,7 @@ export async function handleToolCall(
|
|
|
375
385
|
toolName,
|
|
376
386
|
...permissionLogContext,
|
|
377
387
|
}),
|
|
378
|
-
writeLog: deps.writeReviewLog,
|
|
388
|
+
writeLog: deps.runtime.writeReviewLog,
|
|
379
389
|
logContext: {
|
|
380
390
|
source: "tool_call",
|
|
381
391
|
toolCallId: (event as { toolCallId: string }).toolCallId,
|
package/src/handlers/types.ts
CHANGED
|
@@ -2,8 +2,7 @@ import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
|
|
|
2
2
|
|
|
3
3
|
import type { PermissionPromptDecision } from "../permission-dialog";
|
|
4
4
|
import type { PermissionManager } from "../permission-manager";
|
|
5
|
-
import type {
|
|
6
|
-
import type { SkillPromptEntry } from "../skill-prompt-sanitizer";
|
|
5
|
+
import type { ExtensionRuntime } from "../runtime";
|
|
7
6
|
|
|
8
7
|
export type PermissionReviewSource = "tool_call" | "skill_input" | "skill_read";
|
|
9
8
|
|
|
@@ -25,31 +24,13 @@ export interface PromptPermissionDetails {
|
|
|
25
24
|
/**
|
|
26
25
|
* Explicit dependency bag passed to each extracted event handler.
|
|
27
26
|
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* isolation without importing src/index.ts.
|
|
31
|
-
* - Issue #43 can replace this interface with ExtensionRuntime without
|
|
32
|
-
* changing handler signatures — only the deps object construction in
|
|
33
|
-
* index.ts needs to change.
|
|
27
|
+
* Mutable state lives in `runtime`; handlers read and write `deps.runtime.*`
|
|
28
|
+
* directly instead of going through getter/setter pairs.
|
|
34
29
|
*/
|
|
35
30
|
export interface HandlerDeps {
|
|
36
|
-
// ──
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
getRuntimeContext(): ExtensionContext | null;
|
|
40
|
-
setRuntimeContext(ctx: ExtensionContext | null): void;
|
|
41
|
-
getActiveSkillEntries(): SkillPromptEntry[];
|
|
42
|
-
setActiveSkillEntries(entries: SkillPromptEntry[]): void;
|
|
43
|
-
getLastKnownActiveAgentName(): string | null;
|
|
44
|
-
setLastKnownActiveAgentName(name: string | null): void;
|
|
45
|
-
/** Cache key for the last set of active tools passed to setActiveTools(). */
|
|
46
|
-
getLastActiveToolsCacheKey(): string | null;
|
|
47
|
-
setLastActiveToolsCacheKey(key: string | null): void;
|
|
48
|
-
/** Cache key for the last before_agent_start prompt state. */
|
|
49
|
-
getLastPromptStateCacheKey(): string | null;
|
|
50
|
-
setLastPromptStateCacheKey(key: string | null): void;
|
|
51
|
-
/** Session-scoped approval cache (passed by reference; mutations are visible). */
|
|
52
|
-
sessionApprovalCache: SessionApprovalCache;
|
|
31
|
+
// ── Runtime context ────────────────────────────────────────────────────
|
|
32
|
+
/** All mutable extension state and log-writing methods. */
|
|
33
|
+
readonly runtime: ExtensionRuntime;
|
|
53
34
|
|
|
54
35
|
// ── Factories ──────────────────────────────────────────────────────────
|
|
55
36
|
/** Create a new PermissionManager scoped to cwd's config hierarchy. */
|
|
@@ -68,7 +49,7 @@ export interface HandlerDeps {
|
|
|
68
49
|
// ── Permission helpers ─────────────────────────────────────────────────
|
|
69
50
|
/**
|
|
70
51
|
* Resolve the active agent name from the session context or system prompt.
|
|
71
|
-
* Updates
|
|
52
|
+
* Updates runtime.lastKnownActiveAgentName as a side effect.
|
|
72
53
|
*/
|
|
73
54
|
resolveAgentName(ctx: ExtensionContext, systemPrompt?: string): string | null;
|
|
74
55
|
/** Whether the current context can show an interactive permission prompt. */
|
|
@@ -85,10 +66,6 @@ export interface HandlerDeps {
|
|
|
85
66
|
startForwardedPermissionPolling(ctx: ExtensionContext): void;
|
|
86
67
|
stopForwardedPermissionPolling(): void;
|
|
87
68
|
|
|
88
|
-
// ── Logging ────────────────────────────────────────────────────────────
|
|
89
|
-
writeReviewLog(event: string, details?: Record<string, unknown>): void;
|
|
90
|
-
writeDebugLog(event: string, details?: Record<string, unknown>): void;
|
|
91
|
-
|
|
92
69
|
// ── Pi API subset ──────────────────────────────────────────────────────
|
|
93
70
|
getAllTools(): unknown[];
|
|
94
71
|
setActiveTools(names: string[]): void;
|