@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "files": [
@@ -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(filePath: string, description: string): void {
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(filePath: string, value: unknown): void {
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(requestsDir: string): string[] {
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(responsePath);
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(responsePath, "forwarded permission response");
170
- safeDeleteFile(requestPath, "forwarded permission request");
171
- cleanupPermissionForwardingLocationIfEmpty(location);
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.setRuntimeContext(ctx);
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.getPermissionManager();
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.getLastActiveToolsCacheKey(),
66
+ deps.runtime.lastActiveToolsCacheKey,
67
67
  activeToolsCacheKey,
68
68
  )
69
69
  ) {
70
70
  deps.setActiveTools(allowedTools);
71
- deps.setLastActiveToolsCacheKey(activeToolsCacheKey);
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.getLastPromptStateCacheKey(),
86
+ deps.runtime.lastPromptStateCacheKey,
87
87
  promptStateCacheKey,
88
88
  )
89
89
  ) {
90
90
  return {};
91
91
  }
92
92
 
93
- deps.setLastPromptStateCacheKey(promptStateCacheKey);
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.setActiveSkillEntries(skillPromptResult.entries);
105
+ deps.runtime.activeSkillEntries = skillPromptResult.entries;
106
106
 
107
107
  if (skillPromptResult.prompt !== event.systemPrompt) {
108
108
  return { systemPrompt: skillPromptResult.prompt };
@@ -39,7 +39,7 @@ export async function handleInput(
39
39
  event: InputPayload,
40
40
  ctx: ExtensionContext,
41
41
  ): Promise<InputEventResult> {
42
- deps.setRuntimeContext(ctx);
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
- .getPermissionManager()
53
- .checkPermission("skill", { name: skillName }, agentName ?? undefined);
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.setRuntimeContext(ctx);
22
+ deps.runtime.runtimeContext = ctx;
23
23
  deps.refreshExtensionConfig(ctx);
24
- deps.setPermissionManager(deps.createPermissionManagerForCwd(ctx.cwd));
25
- deps.setActiveSkillEntries([]);
26
- deps.setLastActiveToolsCacheKey(null);
27
- deps.setLastPromptStateCacheKey(null);
28
- deps.setLastKnownActiveAgentName(getActiveAgentName(ctx));
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.getLastKnownActiveAgentName();
33
- const policyIssues = deps.getPermissionManager().getConfigIssues(agentName);
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.getRuntimeContext();
56
- deps.setPermissionManager(
57
- deps.createPermissionManagerForCwd(runtimeContext?.cwd),
56
+ const { runtimeContext } = deps.runtime;
57
+ deps.runtime.permissionManager = deps.createPermissionManagerForCwd(
58
+ runtimeContext?.cwd,
58
59
  );
59
- deps.setActiveSkillEntries([]);
60
- deps.setLastActiveToolsCacheKey(null);
61
- deps.setLastPromptStateCacheKey(null);
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 ctx = deps.getRuntimeContext();
71
- if (ctx) {
72
- ctx.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
71
+ const { runtimeContext } = deps.runtime;
72
+ if (runtimeContext) {
73
+ runtimeContext.ui.setStatus(PERMISSION_SYSTEM_STATUS_KEY, undefined);
73
74
  }
74
- deps.setRuntimeContext(null);
75
- deps.setActiveSkillEntries([]);
76
- deps.setLastActiveToolsCacheKey(null);
77
- deps.setLastPromptStateCacheKey(null);
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
  }