@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
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [3.8.0](https://github.com/gotgenes/pi-permission-system/compare/v3.7.0...v3.8.0) (2026-05-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* define ExtensionRuntime and createExtensionRuntime factory ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([6ad3db6](https://github.com/gotgenes/pi-permission-system/commit/6ad3db6671629f6480a49e6be890dbaff211ad69))
|
|
14
|
+
* eliminate module-scope state in src/index.ts ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([45b2bc1](https://github.com/gotgenes/pi-permission-system/commit/45b2bc1f4bff3693f295892c942328cc6a53f5e0))
|
|
15
|
+
* relocate factory helpers into src/runtime.ts ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([88c1acd](https://github.com/gotgenes/pi-permission-system/commit/88c1acd4e99f24e808b60bbeee2cbed69c2a67ef))
|
|
16
|
+
* simplify HandlerDeps to use ExtensionRuntime ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([2ff5971](https://github.com/gotgenes/pi-permission-system/commit/2ff59712f88c8bde2095df7e475ecb0c19cb3335))
|
|
17
|
+
* thread logger through forwarded-permissions IO ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([66db158](https://github.com/gotgenes/pi-permission-system/commit/66db158cfe423cf503fc08fc472f31f595527381))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
* plan eliminate module-scope mutable state ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([6a782d7](https://github.com/gotgenes/pi-permission-system/commit/6a782d7e8df871c15cf9d5c4b27c5e90f9e12d4d))
|
|
23
|
+
* **retro:** add retro notes for issue [#42](https://github.com/gotgenes/pi-permission-system/issues/42) ([9b91110](https://github.com/gotgenes/pi-permission-system/commit/9b91110832e562440c3a881bb16e5f0a7989b33a))
|
|
24
|
+
* update plan with implementation notes ([#43](https://github.com/gotgenes/pi-permission-system/issues/43)) ([d29a7c0](https://github.com/gotgenes/pi-permission-system/commit/d29a7c0d037f3e44791c713474190a1873a5d294))
|
|
25
|
+
|
|
8
26
|
## [3.7.0](https://github.com/gotgenes/pi-permission-system/compare/v3.6.0...v3.7.0) (2026-05-03)
|
|
9
27
|
|
|
10
28
|
|
package/package.json
CHANGED
|
@@ -24,14 +24,6 @@ export interface ForwardedPermissionLogger {
|
|
|
24
24
|
writeDebugLog: LogFn;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
let logger: ForwardedPermissionLogger | null = null;
|
|
28
|
-
|
|
29
|
-
export function setForwardedPermissionLogger(
|
|
30
|
-
l: ForwardedPermissionLogger,
|
|
31
|
-
): void {
|
|
32
|
-
logger = l;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
27
|
export function formatUnknownErrorMessage(error: unknown): string {
|
|
36
28
|
if (error instanceof Error && error.message) {
|
|
37
29
|
return error.message;
|
|
@@ -48,7 +40,12 @@ export function isErrnoCode(error: unknown, code: string): boolean {
|
|
|
48
40
|
);
|
|
49
41
|
}
|
|
50
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Log a warning to both the review and debug logs.
|
|
45
|
+
* Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
|
|
46
|
+
*/
|
|
51
47
|
export function logPermissionForwardingWarning(
|
|
48
|
+
logger: ForwardedPermissionLogger | null,
|
|
52
49
|
message: string,
|
|
53
50
|
error?: unknown,
|
|
54
51
|
): void {
|
|
@@ -61,7 +58,12 @@ export function logPermissionForwardingWarning(
|
|
|
61
58
|
logger?.writeDebugLog("permission_forwarding.warning", details);
|
|
62
59
|
}
|
|
63
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Log an error to both the review and debug logs.
|
|
63
|
+
* Pass `null` for `logger` to silently no-op (e.g. in unit tests without IO).
|
|
64
|
+
*/
|
|
64
65
|
export function logPermissionForwardingError(
|
|
66
|
+
logger: ForwardedPermissionLogger | null,
|
|
65
67
|
message: string,
|
|
66
68
|
error?: unknown,
|
|
67
69
|
): void {
|
|
@@ -75,6 +77,7 @@ export function logPermissionForwardingError(
|
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
export function ensureDirectoryExists(
|
|
80
|
+
logger: ForwardedPermissionLogger | null,
|
|
78
81
|
path: string,
|
|
79
82
|
description: string,
|
|
80
83
|
): boolean {
|
|
@@ -83,6 +86,7 @@ export function ensureDirectoryExists(
|
|
|
83
86
|
return true;
|
|
84
87
|
} catch (error) {
|
|
85
88
|
logPermissionForwardingError(
|
|
89
|
+
logger,
|
|
86
90
|
`Failed to create ${description} directory '${path}'`,
|
|
87
91
|
error,
|
|
88
92
|
);
|
|
@@ -98,6 +102,7 @@ export function getPermissionForwardingLocationForSession(
|
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
export function ensurePermissionForwardingLocation(
|
|
105
|
+
logger: ForwardedPermissionLogger | null,
|
|
101
106
|
forwardingDir: string,
|
|
102
107
|
sessionId: string,
|
|
103
108
|
): PermissionForwardingLocation | null {
|
|
@@ -109,6 +114,7 @@ export function ensurePermissionForwardingLocation(
|
|
|
109
114
|
);
|
|
110
115
|
} catch (error) {
|
|
111
116
|
logPermissionForwardingError(
|
|
117
|
+
logger,
|
|
112
118
|
"Failed to resolve permission forwarding location",
|
|
113
119
|
error,
|
|
114
120
|
);
|
|
@@ -116,14 +122,17 @@ export function ensurePermissionForwardingLocation(
|
|
|
116
122
|
}
|
|
117
123
|
|
|
118
124
|
const sessionRootReady = ensureDirectoryExists(
|
|
125
|
+
logger,
|
|
119
126
|
location.sessionRootDir,
|
|
120
127
|
"permission forwarding session root",
|
|
121
128
|
);
|
|
122
129
|
const requestsReady = ensureDirectoryExists(
|
|
130
|
+
logger,
|
|
123
131
|
location.requestsDir,
|
|
124
132
|
"permission forwarding requests",
|
|
125
133
|
);
|
|
126
134
|
const responsesReady = ensureDirectoryExists(
|
|
135
|
+
logger,
|
|
127
136
|
location.responsesDir,
|
|
128
137
|
"permission forwarding responses",
|
|
129
138
|
);
|
|
@@ -149,6 +158,7 @@ export function getExistingPermissionForwardingLocation(
|
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
export function tryRemoveDirectoryIfEmpty(
|
|
161
|
+
logger: ForwardedPermissionLogger | null,
|
|
152
162
|
path: string,
|
|
153
163
|
description: string,
|
|
154
164
|
): void {
|
|
@@ -161,6 +171,7 @@ export function tryRemoveDirectoryIfEmpty(
|
|
|
161
171
|
entries = readdirSync(path);
|
|
162
172
|
} catch (error) {
|
|
163
173
|
logPermissionForwardingWarning(
|
|
174
|
+
logger,
|
|
164
175
|
`Failed to inspect ${description} directory '${path}'`,
|
|
165
176
|
error,
|
|
166
177
|
);
|
|
@@ -179,6 +190,7 @@ export function tryRemoveDirectoryIfEmpty(
|
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
logPermissionForwardingWarning(
|
|
193
|
+
logger,
|
|
182
194
|
`Failed to remove empty ${description} directory '${path}'`,
|
|
183
195
|
error,
|
|
184
196
|
);
|
|
@@ -186,23 +198,31 @@ export function tryRemoveDirectoryIfEmpty(
|
|
|
186
198
|
}
|
|
187
199
|
|
|
188
200
|
export function cleanupPermissionForwardingLocationIfEmpty(
|
|
201
|
+
logger: ForwardedPermissionLogger | null,
|
|
189
202
|
location: PermissionForwardingLocation,
|
|
190
203
|
): void {
|
|
191
204
|
tryRemoveDirectoryIfEmpty(
|
|
205
|
+
logger,
|
|
192
206
|
location.requestsDir,
|
|
193
207
|
`${location.label} permission forwarding requests`,
|
|
194
208
|
);
|
|
195
209
|
tryRemoveDirectoryIfEmpty(
|
|
210
|
+
logger,
|
|
196
211
|
location.responsesDir,
|
|
197
212
|
`${location.label} permission forwarding responses`,
|
|
198
213
|
);
|
|
199
214
|
tryRemoveDirectoryIfEmpty(
|
|
215
|
+
logger,
|
|
200
216
|
location.sessionRootDir,
|
|
201
217
|
`${location.label} permission forwarding session root`,
|
|
202
218
|
);
|
|
203
219
|
}
|
|
204
220
|
|
|
205
|
-
export function safeDeleteFile(
|
|
221
|
+
export function safeDeleteFile(
|
|
222
|
+
logger: ForwardedPermissionLogger | null,
|
|
223
|
+
filePath: string,
|
|
224
|
+
description: string,
|
|
225
|
+
): void {
|
|
206
226
|
try {
|
|
207
227
|
unlinkSync(filePath);
|
|
208
228
|
} catch (error) {
|
|
@@ -211,25 +231,31 @@ export function safeDeleteFile(filePath: string, description: string): void {
|
|
|
211
231
|
}
|
|
212
232
|
|
|
213
233
|
logPermissionForwardingWarning(
|
|
234
|
+
logger,
|
|
214
235
|
`Failed to delete ${description} file '${filePath}'`,
|
|
215
236
|
error,
|
|
216
237
|
);
|
|
217
238
|
}
|
|
218
239
|
}
|
|
219
240
|
|
|
220
|
-
export function writeJsonFileAtomic(
|
|
241
|
+
export function writeJsonFileAtomic(
|
|
242
|
+
logger: ForwardedPermissionLogger | null,
|
|
243
|
+
filePath: string,
|
|
244
|
+
value: unknown,
|
|
245
|
+
): void {
|
|
221
246
|
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
222
247
|
|
|
223
248
|
try {
|
|
224
249
|
writeFileSync(tempPath, JSON.stringify(value), "utf-8");
|
|
225
250
|
renameSync(tempPath, filePath);
|
|
226
251
|
} catch (error) {
|
|
227
|
-
safeDeleteFile(tempPath, "temporary permission-forwarding");
|
|
252
|
+
safeDeleteFile(logger, tempPath, "temporary permission-forwarding");
|
|
228
253
|
throw error;
|
|
229
254
|
}
|
|
230
255
|
}
|
|
231
256
|
|
|
232
257
|
export function readForwardedPermissionRequest(
|
|
258
|
+
logger: ForwardedPermissionLogger | null,
|
|
233
259
|
filePath: string,
|
|
234
260
|
): ForwardedPermissionRequest | null {
|
|
235
261
|
try {
|
|
@@ -245,6 +271,7 @@ export function readForwardedPermissionRequest(
|
|
|
245
271
|
typeof parsed.message !== "string"
|
|
246
272
|
) {
|
|
247
273
|
logPermissionForwardingWarning(
|
|
274
|
+
logger,
|
|
248
275
|
`Ignoring invalid forwarded permission request format in '${filePath}'`,
|
|
249
276
|
);
|
|
250
277
|
return null;
|
|
@@ -260,6 +287,7 @@ export function readForwardedPermissionRequest(
|
|
|
260
287
|
};
|
|
261
288
|
} catch (error) {
|
|
262
289
|
logPermissionForwardingWarning(
|
|
290
|
+
logger,
|
|
263
291
|
`Failed to read forwarded permission request '${filePath}'`,
|
|
264
292
|
error,
|
|
265
293
|
);
|
|
@@ -268,6 +296,7 @@ export function readForwardedPermissionRequest(
|
|
|
268
296
|
}
|
|
269
297
|
|
|
270
298
|
export function readForwardedPermissionResponse(
|
|
299
|
+
logger: ForwardedPermissionLogger | null,
|
|
271
300
|
filePath: string,
|
|
272
301
|
): ForwardedPermissionResponse | null {
|
|
273
302
|
try {
|
|
@@ -280,6 +309,7 @@ export function readForwardedPermissionResponse(
|
|
|
280
309
|
typeof parsed.responderSessionId !== "string"
|
|
281
310
|
) {
|
|
282
311
|
logPermissionForwardingWarning(
|
|
312
|
+
logger,
|
|
283
313
|
`Ignoring invalid forwarded permission response format in '${filePath}'`,
|
|
284
314
|
);
|
|
285
315
|
return null;
|
|
@@ -300,6 +330,7 @@ export function readForwardedPermissionResponse(
|
|
|
300
330
|
};
|
|
301
331
|
} catch (error) {
|
|
302
332
|
logPermissionForwardingWarning(
|
|
333
|
+
logger,
|
|
303
334
|
`Failed to read forwarded permission response '${filePath}'`,
|
|
304
335
|
error,
|
|
305
336
|
);
|
|
@@ -307,13 +338,17 @@ export function readForwardedPermissionResponse(
|
|
|
307
338
|
}
|
|
308
339
|
}
|
|
309
340
|
|
|
310
|
-
export function listRequestFiles(
|
|
341
|
+
export function listRequestFiles(
|
|
342
|
+
logger: ForwardedPermissionLogger | null,
|
|
343
|
+
requestsDir: string,
|
|
344
|
+
): string[] {
|
|
311
345
|
try {
|
|
312
346
|
return readdirSync(requestsDir)
|
|
313
347
|
.filter((name) => name.endsWith(".json"))
|
|
314
348
|
.sort();
|
|
315
349
|
} catch (error) {
|
|
316
350
|
logPermissionForwardingWarning(
|
|
351
|
+
logger,
|
|
317
352
|
`Failed to read permission forwarding requests from '${requestsDir}'`,
|
|
318
353
|
error,
|
|
319
354
|
);
|
|
@@ -21,6 +21,7 @@ import { isSubagentExecutionContext } from "../subagent-context";
|
|
|
21
21
|
import {
|
|
22
22
|
cleanupPermissionForwardingLocationIfEmpty,
|
|
23
23
|
ensurePermissionForwardingLocation,
|
|
24
|
+
type ForwardedPermissionLogger,
|
|
24
25
|
getExistingPermissionForwardingLocation,
|
|
25
26
|
listRequestFiles,
|
|
26
27
|
logPermissionForwardingError,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
export interface PermissionForwardingDeps {
|
|
36
37
|
forwardingDir: string;
|
|
37
38
|
subagentSessionsDir: string;
|
|
39
|
+
logger: ForwardedPermissionLogger;
|
|
38
40
|
writeReviewLog: (event: string, details: Record<string, unknown>) => void;
|
|
39
41
|
requestPermissionDecisionFromUi: (
|
|
40
42
|
ui: ExtensionContext["ui"],
|
|
@@ -65,7 +67,9 @@ function getContextSystemPrompt(ctx: ExtensionContext): string | undefined {
|
|
|
65
67
|
const systemPrompt = getSystemPrompt.call(ctx);
|
|
66
68
|
return typeof systemPrompt === "string" ? systemPrompt : undefined;
|
|
67
69
|
} catch (error) {
|
|
70
|
+
// No deps available in this helper — warning silently dropped.
|
|
68
71
|
logPermissionForwardingWarning(
|
|
72
|
+
null,
|
|
69
73
|
"Failed to read context system prompt for forwarded permission metadata",
|
|
70
74
|
error,
|
|
71
75
|
);
|
|
@@ -101,17 +105,20 @@ export async function waitForForwardedPermissionApproval(
|
|
|
101
105
|
|
|
102
106
|
if (!targetSessionId) {
|
|
103
107
|
logPermissionForwardingError(
|
|
108
|
+
deps.logger,
|
|
104
109
|
"Permission forwarding target session could not be resolved from subagent runtime metadata (expected PI_AGENT_ROUTER_PARENT_SESSION_ID)",
|
|
105
110
|
);
|
|
106
111
|
return { approved: false, state: "denied" };
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
const location = ensurePermissionForwardingLocation(
|
|
115
|
+
deps.logger,
|
|
110
116
|
deps.forwardingDir,
|
|
111
117
|
targetSessionId,
|
|
112
118
|
);
|
|
113
119
|
if (!location) {
|
|
114
120
|
logPermissionForwardingError(
|
|
121
|
+
deps.logger,
|
|
115
122
|
`Permission forwarding is unavailable because session-scoped directories could not be prepared for '${targetSessionId}'`,
|
|
116
123
|
);
|
|
117
124
|
return { approved: false, state: "denied" };
|
|
@@ -144,9 +151,10 @@ export async function waitForForwardedPermissionApproval(
|
|
|
144
151
|
});
|
|
145
152
|
|
|
146
153
|
try {
|
|
147
|
-
writeJsonFileAtomic(requestPath, request);
|
|
154
|
+
writeJsonFileAtomic(deps.logger, requestPath, request);
|
|
148
155
|
} catch (error) {
|
|
149
156
|
logPermissionForwardingError(
|
|
157
|
+
deps.logger,
|
|
150
158
|
`Failed to write forwarded permission request '${requestPath}'`,
|
|
151
159
|
error,
|
|
152
160
|
);
|
|
@@ -156,7 +164,10 @@ export async function waitForForwardedPermissionApproval(
|
|
|
156
164
|
const deadline = Date.now() + PERMISSION_FORWARDING_TIMEOUT_MS;
|
|
157
165
|
while (Date.now() < deadline) {
|
|
158
166
|
if (existsSync(responsePath)) {
|
|
159
|
-
const response = readForwardedPermissionResponse(
|
|
167
|
+
const response = readForwardedPermissionResponse(
|
|
168
|
+
deps.logger,
|
|
169
|
+
responsePath,
|
|
170
|
+
);
|
|
160
171
|
deps.writeReviewLog("forwarded_permission.response_received", {
|
|
161
172
|
requestId,
|
|
162
173
|
approved: response?.approved ?? null,
|
|
@@ -166,9 +177,13 @@ export async function waitForForwardedPermissionApproval(
|
|
|
166
177
|
targetSessionId,
|
|
167
178
|
responsePath,
|
|
168
179
|
});
|
|
169
|
-
safeDeleteFile(
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
safeDeleteFile(
|
|
181
|
+
deps.logger,
|
|
182
|
+
responsePath,
|
|
183
|
+
"forwarded permission response",
|
|
184
|
+
);
|
|
185
|
+
safeDeleteFile(deps.logger, requestPath, "forwarded permission request");
|
|
186
|
+
cleanupPermissionForwardingLocationIfEmpty(deps.logger, location);
|
|
172
187
|
return response ?? { approved: false, state: "denied" };
|
|
173
188
|
}
|
|
174
189
|
|
|
@@ -176,6 +191,7 @@ export async function waitForForwardedPermissionApproval(
|
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
logPermissionForwardingWarning(
|
|
194
|
+
deps.logger,
|
|
179
195
|
`Timed out waiting for forwarded permission response '${responsePath}'`,
|
|
180
196
|
);
|
|
181
197
|
deps.writeReviewLog("forwarded_permission.response_timed_out", {
|
|
@@ -184,8 +200,8 @@ export async function waitForForwardedPermissionApproval(
|
|
|
184
200
|
targetSessionId,
|
|
185
201
|
responsePath,
|
|
186
202
|
});
|
|
187
|
-
safeDeleteFile(requestPath, "forwarded permission request");
|
|
188
|
-
cleanupPermissionForwardingLocationIfEmpty(location);
|
|
203
|
+
safeDeleteFile(deps.logger, requestPath, "forwarded permission request");
|
|
204
|
+
cleanupPermissionForwardingLocationIfEmpty(deps.logger, location);
|
|
189
205
|
return { approved: false, state: "denied" };
|
|
190
206
|
}
|
|
191
207
|
|
|
@@ -206,16 +222,17 @@ export async function processForwardedPermissionRequests(
|
|
|
206
222
|
return;
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
const requestFiles = listRequestFiles(location.requestsDir);
|
|
225
|
+
const requestFiles = listRequestFiles(deps.logger, location.requestsDir);
|
|
210
226
|
if (requestFiles.length === 0) {
|
|
211
227
|
return;
|
|
212
228
|
}
|
|
213
229
|
|
|
214
230
|
for (const fileName of requestFiles) {
|
|
215
231
|
const requestPath = join(location.requestsDir, fileName);
|
|
216
|
-
const request = readForwardedPermissionRequest(requestPath);
|
|
232
|
+
const request = readForwardedPermissionRequest(deps.logger, requestPath);
|
|
217
233
|
if (!request) {
|
|
218
234
|
safeDeleteFile(
|
|
235
|
+
deps.logger,
|
|
219
236
|
requestPath,
|
|
220
237
|
`${location.label} forwarded permission request`,
|
|
221
238
|
);
|
|
@@ -224,9 +241,11 @@ export async function processForwardedPermissionRequests(
|
|
|
224
241
|
|
|
225
242
|
if (!isForwardedPermissionRequestForSession(request, currentSessionId)) {
|
|
226
243
|
logPermissionForwardingWarning(
|
|
244
|
+
deps.logger,
|
|
227
245
|
`Ignoring forwarded permission request '${request.id}' because it targets session '${request.targetSessionId}' instead of '${currentSessionId}'`,
|
|
228
246
|
);
|
|
229
247
|
safeDeleteFile(
|
|
248
|
+
deps.logger,
|
|
230
249
|
requestPath,
|
|
231
250
|
`${location.label} forwarded permission request`,
|
|
232
251
|
);
|
|
@@ -265,6 +284,7 @@ export async function processForwardedPermissionRequests(
|
|
|
265
284
|
);
|
|
266
285
|
} catch (error) {
|
|
267
286
|
logPermissionForwardingError(
|
|
287
|
+
deps.logger,
|
|
268
288
|
"Failed to show forwarded permission confirmation dialog",
|
|
269
289
|
error,
|
|
270
290
|
);
|
|
@@ -289,7 +309,7 @@ export async function processForwardedPermissionRequests(
|
|
|
289
309
|
},
|
|
290
310
|
);
|
|
291
311
|
try {
|
|
292
|
-
writeJsonFileAtomic(responsePath, {
|
|
312
|
+
writeJsonFileAtomic(deps.logger, responsePath, {
|
|
293
313
|
approved: decision.approved,
|
|
294
314
|
state: decision.state,
|
|
295
315
|
denialReason: decision.denialReason,
|
|
@@ -298,6 +318,7 @@ export async function processForwardedPermissionRequests(
|
|
|
298
318
|
} satisfies ForwardedPermissionResponse);
|
|
299
319
|
} catch (error) {
|
|
300
320
|
logPermissionForwardingError(
|
|
321
|
+
deps.logger,
|
|
301
322
|
`Failed to write ${location.label} forwarded permission response '${responsePath}'`,
|
|
302
323
|
error,
|
|
303
324
|
);
|
|
@@ -305,12 +326,13 @@ export async function processForwardedPermissionRequests(
|
|
|
305
326
|
}
|
|
306
327
|
|
|
307
328
|
safeDeleteFile(
|
|
329
|
+
deps.logger,
|
|
308
330
|
requestPath,
|
|
309
331
|
`${location.label} forwarded permission request`,
|
|
310
332
|
);
|
|
311
333
|
}
|
|
312
334
|
|
|
313
|
-
cleanupPermissionForwardingLocationIfEmpty(location);
|
|
335
|
+
cleanupPermissionForwardingLocationIfEmpty(deps.logger, location);
|
|
314
336
|
}
|
|
315
337
|
|
|
316
338
|
export async function confirmPermission(
|
|
@@ -41,12 +41,12 @@ export async function handleBeforeAgentStart(
|
|
|
41
41
|
event: BeforeAgentStartPayload,
|
|
42
42
|
ctx: ExtensionContext,
|
|
43
43
|
): Promise<BeforeAgentStartEventResult> {
|
|
44
|
-
deps.
|
|
44
|
+
deps.runtime.runtimeContext = ctx;
|
|
45
45
|
deps.refreshExtensionConfig(ctx);
|
|
46
46
|
deps.startForwardedPermissionPolling(ctx);
|
|
47
47
|
|
|
48
48
|
const agentName = deps.resolveAgentName(ctx, event.systemPrompt);
|
|
49
|
-
const permissionManager = deps.
|
|
49
|
+
const { permissionManager } = deps.runtime;
|
|
50
50
|
const allTools = deps.getAllTools();
|
|
51
51
|
const allowedTools: string[] = [];
|
|
52
52
|
|
|
@@ -63,12 +63,12 @@ export async function handleBeforeAgentStart(
|
|
|
63
63
|
const activeToolsCacheKey = createActiveToolsCacheKey(allowedTools);
|
|
64
64
|
if (
|
|
65
65
|
shouldApplyCachedAgentStartState(
|
|
66
|
-
deps.
|
|
66
|
+
deps.runtime.lastActiveToolsCacheKey,
|
|
67
67
|
activeToolsCacheKey,
|
|
68
68
|
)
|
|
69
69
|
) {
|
|
70
70
|
deps.setActiveTools(allowedTools);
|
|
71
|
-
deps.
|
|
71
|
+
deps.runtime.lastActiveToolsCacheKey = activeToolsCacheKey;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
const promptStateCacheKey = createBeforeAgentStartPromptStateKey({
|
|
@@ -83,14 +83,14 @@ export async function handleBeforeAgentStart(
|
|
|
83
83
|
|
|
84
84
|
if (
|
|
85
85
|
!shouldApplyCachedAgentStartState(
|
|
86
|
-
deps.
|
|
86
|
+
deps.runtime.lastPromptStateCacheKey,
|
|
87
87
|
promptStateCacheKey,
|
|
88
88
|
)
|
|
89
89
|
) {
|
|
90
90
|
return {};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
deps.
|
|
93
|
+
deps.runtime.lastPromptStateCacheKey = promptStateCacheKey;
|
|
94
94
|
|
|
95
95
|
const toolPromptResult = sanitizeAvailableToolsSection(
|
|
96
96
|
event.systemPrompt,
|
|
@@ -102,7 +102,7 @@ export async function handleBeforeAgentStart(
|
|
|
102
102
|
agentName,
|
|
103
103
|
ctx.cwd,
|
|
104
104
|
);
|
|
105
|
-
deps.
|
|
105
|
+
deps.runtime.activeSkillEntries = skillPromptResult.entries;
|
|
106
106
|
|
|
107
107
|
if (skillPromptResult.prompt !== event.systemPrompt) {
|
|
108
108
|
return { systemPrompt: skillPromptResult.prompt };
|
package/src/handlers/input.ts
CHANGED
|
@@ -39,7 +39,7 @@ export async function handleInput(
|
|
|
39
39
|
event: InputPayload,
|
|
40
40
|
ctx: ExtensionContext,
|
|
41
41
|
): Promise<InputEventResult> {
|
|
42
|
-
deps.
|
|
42
|
+
deps.runtime.runtimeContext = ctx;
|
|
43
43
|
deps.startForwardedPermissionPolling(ctx);
|
|
44
44
|
|
|
45
45
|
const skillName = extractSkillNameFromInput(event.text);
|
|
@@ -48,9 +48,11 @@ export async function handleInput(
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
const agentName = deps.resolveAgentName(ctx);
|
|
51
|
-
const check = deps
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const check = deps.runtime.permissionManager.checkPermission(
|
|
52
|
+
"skill",
|
|
53
|
+
{ name: skillName },
|
|
54
|
+
agentName ?? undefined,
|
|
55
|
+
);
|
|
54
56
|
|
|
55
57
|
if (check.state === "deny" && ctx.hasUI) {
|
|
56
58
|
const notifyMessage = agentName
|
|
@@ -74,7 +76,7 @@ export async function handleInput(
|
|
|
74
76
|
message: skillInputMessage,
|
|
75
77
|
skillName,
|
|
76
78
|
}),
|
|
77
|
-
writeLog: deps.writeReviewLog,
|
|
79
|
+
writeLog: deps.runtime.writeReviewLog,
|
|
78
80
|
logContext: {
|
|
79
81
|
source: "skill_input",
|
|
80
82
|
skillName,
|
|
@@ -19,24 +19,25 @@ export async function handleSessionStart(
|
|
|
19
19
|
event: SessionStartPayload,
|
|
20
20
|
ctx: ExtensionContext,
|
|
21
21
|
): Promise<void> {
|
|
22
|
-
deps.
|
|
22
|
+
deps.runtime.runtimeContext = ctx;
|
|
23
23
|
deps.refreshExtensionConfig(ctx);
|
|
24
|
-
deps.
|
|
25
|
-
deps.
|
|
26
|
-
deps.
|
|
27
|
-
deps.
|
|
28
|
-
deps.
|
|
24
|
+
deps.runtime.permissionManager = deps.createPermissionManagerForCwd(ctx.cwd);
|
|
25
|
+
deps.runtime.activeSkillEntries = [];
|
|
26
|
+
deps.runtime.lastActiveToolsCacheKey = null;
|
|
27
|
+
deps.runtime.lastPromptStateCacheKey = null;
|
|
28
|
+
deps.runtime.lastKnownActiveAgentName = getActiveAgentName(ctx);
|
|
29
29
|
deps.startForwardedPermissionPolling(ctx);
|
|
30
30
|
deps.logResolvedConfigPaths();
|
|
31
31
|
|
|
32
|
-
const agentName = deps.
|
|
33
|
-
const policyIssues =
|
|
32
|
+
const agentName = deps.runtime.lastKnownActiveAgentName;
|
|
33
|
+
const policyIssues =
|
|
34
|
+
deps.runtime.permissionManager.getConfigIssues(agentName);
|
|
34
35
|
for (const issue of policyIssues) {
|
|
35
36
|
deps.notifyWarning(issue);
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
if (event.reason === "reload") {
|
|
39
|
-
deps.writeDebugLog("lifecycle.reload", {
|
|
40
|
+
deps.runtime.writeDebugLog("lifecycle.reload", {
|
|
40
41
|
triggeredBy: "session_start",
|
|
41
42
|
reason: event.reason,
|
|
42
43
|
cwd: ctx.cwd,
|
|
@@ -52,14 +53,14 @@ export async function handleResourcesDiscover(
|
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
const runtimeContext = deps.
|
|
56
|
-
deps.
|
|
57
|
-
|
|
56
|
+
const { runtimeContext } = deps.runtime;
|
|
57
|
+
deps.runtime.permissionManager = deps.createPermissionManagerForCwd(
|
|
58
|
+
runtimeContext?.cwd,
|
|
58
59
|
);
|
|
59
|
-
deps.
|
|
60
|
-
deps.
|
|
61
|
-
deps.
|
|
62
|
-
deps.writeDebugLog("lifecycle.reload", {
|
|
60
|
+
deps.runtime.activeSkillEntries = [];
|
|
61
|
+
deps.runtime.lastActiveToolsCacheKey = null;
|
|
62
|
+
deps.runtime.lastPromptStateCacheKey = null;
|
|
63
|
+
deps.runtime.writeDebugLog("lifecycle.reload", {
|
|
63
64
|
triggeredBy: "resources_discover",
|
|
64
65
|
reason: event.reason,
|
|
65
66
|
cwd: runtimeContext?.cwd ?? null,
|
|
@@ -67,14 +68,14 @@ export async function handleResourcesDiscover(
|
|
|
67
68
|
}
|
|
68
69
|
|
|
69
70
|
export async function handleSessionShutdown(deps: HandlerDeps): Promise<void> {
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
|
|
71
|
+
const { runtimeContext } = deps.runtime;
|
|
72
|
+
if (runtimeContext) {
|
|
73
|
+
runtimeContext.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
|
|
73
74
|
}
|
|
74
|
-
deps.
|
|
75
|
-
deps.
|
|
76
|
-
deps.
|
|
77
|
-
deps.
|
|
78
|
-
deps.sessionApprovalCache.clear();
|
|
75
|
+
deps.runtime.runtimeContext = null;
|
|
76
|
+
deps.runtime.activeSkillEntries = [];
|
|
77
|
+
deps.runtime.lastActiveToolsCacheKey = null;
|
|
78
|
+
deps.runtime.lastPromptStateCacheKey = null;
|
|
79
|
+
deps.runtime.sessionApprovalCache.clear();
|
|
79
80
|
deps.stopForwardedPermissionPolling();
|
|
80
81
|
}
|