@omercnet/paseo-omp 0.4.0-next.124.1 → 0.4.0-next.125.1

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.
@@ -1,12 +1,10 @@
1
- import { createHash, randomUUID } from "node:crypto";
1
+ import { randomUUID } from "node:crypto";
2
2
  import type {
3
3
  ProviderConfigState,
4
4
  ProviderError,
5
5
  ProviderEvent,
6
6
  ProviderInput,
7
- ProviderPermissionResponse,
8
7
  ProviderSessionConfig,
9
- ProviderToolCallDetail,
10
8
  } from "@getpaseo/plugin/server/provider";
11
9
  import type {
12
10
  OmpOperationalFailure,
@@ -34,13 +32,10 @@ import type {
34
32
  OmpAvailableCommand,
35
33
  OmpBranchResult,
36
34
  OmpCompactionResult,
37
- OmpExtensionUiResponse,
38
35
  OmpMessage,
39
36
  OmpModel,
40
37
  OmpRpcEvent,
41
38
  OmpSessionState,
42
- OmpToolApprovalCancel,
43
- OmpToolApprovalRequest,
44
39
  } from "./omp-rpc-protocol";
45
40
  import { OmpRpcRequestRejectedError } from "./omp-rpc-transport";
46
41
  import {
@@ -66,6 +61,7 @@ import { OmpSessionCompaction } from "./session-compaction";
66
61
  import type { OmpSessionDescriptor } from "./session-descriptors";
67
62
  import { validateNativeSessionId } from "./session-descriptors";
68
63
  import { isNativeTurnActivity, isPassiveUiMethod, isRuntimeConfigEvent } from "./session-events";
64
+ import { OmpSessionPermissions } from "./session-permissions";
69
65
  import {
70
66
  type ActiveTurn,
71
67
  assistantTerminalOutcome,
@@ -96,10 +92,6 @@ type NativeSessionTransition = (previousSessionId: string, nextSessionId: string
96
92
  type RewindCleanupQuarantine = (cleanup: Promise<void>) => void;
97
93
  type RewindSessionRetirement = () => void;
98
94
  type SessionPermissionInput = Extract<ProviderInput, { type: "session.permission" }>;
99
- type OmpQuestionRequest = Extract<
100
- Extract<OmpRpcEvent, { type: "extension_ui_request" }>,
101
- { method: "select" | "confirm" | "input" | "editor" }
102
- >;
103
95
  type Emit = (event: ProviderEvent) => void;
104
96
  const LOCAL_ONLY_SETTLE_MS = 5_000;
105
97
  const AGENT_END_STATE_TIMEOUT_MS = 2_000;
@@ -115,8 +107,8 @@ const MAX_AGENT_END_CORRELATION_MESSAGES = 512;
115
107
  const MAX_TRACKED_ENTRY_IDS = 1_024;
116
108
  const MAX_UNCLAIMED_BRANCH_ENTRIES = 1_024;
117
109
  const MAX_PENDING_USERS = 256;
118
- const MAX_PENDING_PERMISSIONS = 32;
119
- const MAX_PENDING_PERMISSION_BYTES = 2 * 1024 * 1024;
110
+ const _MAX_PENDING_PERMISSIONS = 32;
111
+ const _MAX_PENDING_PERMISSION_BYTES = 2 * 1024 * 1024;
120
112
  const MAX_USER_ECHOES = 512;
121
113
  const MAX_BUFFERED_TURN_EVENTS = 512;
122
114
  const MAX_BUFFERED_VALUE_ITEMS = 1_024;
@@ -128,8 +120,8 @@ const MAX_UNCLAIMED_BRANCH_BYTES = 4 * 1024 * 1024;
128
120
  class OmpCatalogEscape extends OmpPublicError {}
129
121
  const MAX_REPLAY_MESSAGES = 100_000;
130
122
  const REPLAY_TIMEOUT_MS = 20_000;
131
- const OMP_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
132
- const MAX_FREEFORM_RESPONSE_BYTES = 64 * 1024;
123
+ const _OMP_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
124
+ const _MAX_FREEFORM_RESPONSE_BYTES = 64 * 1024;
133
125
  const OMP_BUILTIN_COMMANDS: readonly OmpAvailableCommand[] = [
134
126
  {
135
127
  name: "compact",
@@ -255,46 +247,6 @@ type PendingAbort = {
255
247
  forceTerminal: boolean;
256
248
  promise: Promise<void>;
257
249
  };
258
- type PendingPermission = {
259
- nativeId: string;
260
- header: string;
261
- fingerprint: string;
262
- optionValues: ReadonlyMap<string, string>;
263
- actionBehaviors: ReadonlyMap<string, "allow" | "deny">;
264
- retainedBytes: number;
265
- displayValues: ReadonlyMap<string, string>;
266
- generation: number;
267
- runtime: OmpRuntimeSession;
268
- expiresAt?: number;
269
- timer?: unknown;
270
- turnId?: string;
271
- request: OmpQuestionRequest;
272
- freeformSentinel?: string;
273
- };
274
- type PendingToolPermission = {
275
- nativeId: string;
276
- toolCallId: string;
277
- fingerprint: string;
278
- retainedBytes: number;
279
- generation: number;
280
- runtime: OmpRuntimeSession;
281
- expiresAt?: number;
282
- timer?: unknown;
283
- turnId?: string;
284
- };
285
-
286
- type PendingFreeformSelection = {
287
- value: string;
288
- nativeSelectId: string;
289
- generation: number;
290
- runtime: OmpRuntimeSession;
291
- turnId?: string;
292
- };
293
-
294
- function permissionFingerprint(request: OmpQuestionRequest): string {
295
- return createHash("sha256").update(JSON.stringify(request)).digest("base64url");
296
- }
297
-
298
250
  function providerError(error: unknown, fallback: string): { message: string } {
299
251
  return { message: isOmpPublicError(error) ? error.message : fallback };
300
252
  }
@@ -359,14 +311,7 @@ export class OmpProviderSession {
359
311
  private revertInFlight = false;
360
312
  private runtimeTurnCompleted = false;
361
313
  private commandCatalog: OmpAvailableCommand[];
362
- private permissionSequence = 0;
363
- private readonly permissionNamespace = randomUUID();
364
- private readonly pendingPermissions = new Map<string, PendingPermission>();
365
- private readonly inFlightPermissions = new Map<string, PendingPermission>();
366
- private pendingFreeformSelection: PendingFreeformSelection | null = null;
367
- private readonly pendingToolPermissions = new Map<string, PendingToolPermission>();
368
- private readonly inFlightToolPermissions = new Map<string, PendingToolPermission>();
369
- private readonly resolvedToolApprovalIds = new BoundedStringSet(MAX_TRACKED_ENTRY_IDS);
314
+ private readonly permissions: OmpSessionPermissions;
370
315
  private unsupportedThinkingNoticePending = false;
371
316
  private unsupportedThinkingNoticePublished = false;
372
317
 
@@ -439,6 +384,24 @@ export class OmpProviderSession {
439
384
  this.dataFilter,
440
385
  this.usage,
441
386
  );
387
+ this.permissions = new OmpSessionPermissions({
388
+ sessionId: id,
389
+ emit,
390
+ scheduler,
391
+ dataFilter: this.dataFilter,
392
+ projector: this.projector,
393
+ readContext: () => ({
394
+ runtime: this.runtime,
395
+ generation: this.generation,
396
+ activeTurn: this.activeTurn,
397
+ closed: this.closed,
398
+ runtimeDead: this.runtimeDead !== null,
399
+ }),
400
+ handleRuntimeFailure: (message) => this.handleRuntimeFailure(message),
401
+ invalidateRuntime: (message) => this.invalidateRuntime(message),
402
+ markAgentEvidence: (turn) => this.markAgentEvidence(turn),
403
+ reevaluateDeferredTerminal: () => this.reevaluateDeferredPermissionTerminal(),
404
+ });
442
405
  this.subsessions = capabilities.includes("session.subsession")
443
406
  ? new OmpSubsessionProjector(
444
407
  id,
@@ -1017,7 +980,7 @@ export class OmpProviderSession {
1017
980
  const configRefresh = this.configRefreshInFlight;
1018
981
  this.cancelConfigRefreshRetry();
1019
982
  this.lifetime.abort(new Error(message));
1020
- this.resolveAllPermissions(true);
983
+ this.permissions.resolveAllPermissions(true);
1021
984
  this.subsessions?.close();
1022
985
  this.projector.close();
1023
986
  this.unsubscribe();
@@ -1265,7 +1228,7 @@ export class OmpProviderSession {
1265
1228
  turn.steerReady.resolve();
1266
1229
  turn.terminal = true;
1267
1230
  this.compaction.finish("failed", { message: "OMP prompt failed" });
1268
- this.resolveTurnPermissions(turn.turnId);
1231
+ this.permissions.resolveTurnPermissions(turn.turnId);
1269
1232
  this.projector.finishTurn(turn.turnId);
1270
1233
  if (this.activeTurn === turn) this.activeTurn = null;
1271
1234
  }
@@ -1466,7 +1429,7 @@ export class OmpProviderSession {
1466
1429
  }
1467
1430
  const forceTerminal = turn.awaitingPermissionEvidence;
1468
1431
  turn.awaitingPermissionEvidence = false;
1469
- if (forceTerminal) this.resolveTurnPermissions(turn.turnId);
1432
+ if (forceTerminal) this.permissions.resolveTurnPermissions(turn.turnId);
1470
1433
  const pending = this.activeAbort;
1471
1434
  if (turn.interrupted) {
1472
1435
  if (pending?.turn === turn) await this.settleInterrupt(input.requestId, pending);
@@ -1877,118 +1840,7 @@ export class OmpProviderSession {
1877
1840
  }
1878
1841
 
1879
1842
  async permission(input: SessionPermissionInput): Promise<void> {
1880
- const typed = this.pendingToolPermissions.get(input.permissionId);
1881
- if (typed) {
1882
- await this.respondToToolPermission(input, typed);
1883
- return;
1884
- }
1885
- const pending = this.pendingPermissions.get(input.permissionId);
1886
- if (!pending) throw new OmpPublicError("Unknown OMP permission request");
1887
- if (!this.permissionOwnerIsCurrent(pending)) {
1888
- this.pendingPermissions.delete(input.permissionId);
1889
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
1890
- this.emit({
1891
- type: "session.permission_resolved",
1892
- sessionId: this.id,
1893
- permissionId: input.permissionId,
1894
- });
1895
- throw new OmpPublicError("OMP permission request is no longer active");
1896
- }
1897
- const { generation, runtime } = pending;
1898
- this.pendingPermissions.delete(input.permissionId);
1899
- this.inFlightPermissions.set(input.permissionId, pending);
1900
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
1901
- try {
1902
- const freeformValue = this.freeformSelection(pending, input.response);
1903
- if (freeformValue !== undefined) {
1904
- this.pendingFreeformSelection = {
1905
- value: freeformValue,
1906
- nativeSelectId: pending.nativeId,
1907
- generation,
1908
- runtime,
1909
- ...(pending.turnId ? { turnId: pending.turnId } : {}),
1910
- };
1911
- }
1912
- const response = this.extensionUiResponse(pending, input.response);
1913
- await runtime.respondToExtensionUi(response);
1914
- } catch (error) {
1915
- if (this.pendingFreeformSelection?.nativeSelectId === pending.nativeId) {
1916
- this.pendingFreeformSelection = null;
1917
- }
1918
- if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
1919
- this.inFlightPermissions.delete(input.permissionId);
1920
- if (
1921
- this.permissionOwnerIsCurrent(pending) &&
1922
- generation === this.generation &&
1923
- runtime === this.runtime
1924
- ) {
1925
- this.pendingPermissions.set(input.permissionId, pending);
1926
- this.armPermissionTimeout(input.permissionId, pending);
1927
- throw error;
1928
- }
1929
- return;
1930
- }
1931
- if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
1932
- this.inFlightPermissions.delete(input.permissionId);
1933
- this.emit({
1934
- type: "session.permission_resolved",
1935
- sessionId: this.id,
1936
- permissionId: input.permissionId,
1937
- });
1938
- this.reevaluateDeferredPermissionTerminal();
1939
- }
1940
- private async respondToToolPermission(
1941
- input: SessionPermissionInput,
1942
- pending: PendingToolPermission,
1943
- ): Promise<void> {
1944
- if (!this.permissionOwnerIsCurrent(pending)) {
1945
- this.pendingToolPermissions.delete(input.permissionId);
1946
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
1947
- this.resolvedToolApprovalIds.add(pending.nativeId);
1948
- this.emit({
1949
- type: "session.permission_resolved",
1950
- sessionId: this.id,
1951
- permissionId: input.permissionId,
1952
- });
1953
- throw new OmpPublicError("OMP permission request is no longer active");
1954
- }
1955
- if (
1956
- input.response.selectedActionId !== undefined &&
1957
- input.response.selectedActionId !== input.response.behavior
1958
- ) {
1959
- throw new OmpPublicError("OMP permission action is invalid");
1960
- }
1961
- this.pendingToolPermissions.delete(input.permissionId);
1962
- this.inFlightToolPermissions.set(input.permissionId, pending);
1963
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
1964
- try {
1965
- await pending.runtime.respondToToolApproval({
1966
- type: "tool_approval_response",
1967
- id: pending.nativeId,
1968
- toolCallId: pending.toolCallId,
1969
- approved: input.response.behavior === "allow",
1970
- });
1971
- } catch (error) {
1972
- if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
1973
- this.inFlightToolPermissions.delete(input.permissionId);
1974
- this.resolvedToolApprovalIds.add(pending.nativeId);
1975
- this.emit({
1976
- type: "session.permission_resolved",
1977
- sessionId: this.id,
1978
- permissionId: input.permissionId,
1979
- });
1980
- this.reevaluateDeferredPermissionTerminal();
1981
- throw error;
1982
- }
1983
- if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
1984
- this.inFlightToolPermissions.delete(input.permissionId);
1985
- this.resolvedToolApprovalIds.add(pending.nativeId);
1986
- this.emit({
1987
- type: "session.permission_resolved",
1988
- sessionId: this.id,
1989
- permissionId: input.permissionId,
1990
- });
1991
- this.reevaluateDeferredPermissionTerminal();
1843
+ await this.permissions.respond(input);
1992
1844
  }
1993
1845
 
1994
1846
  close(input?: SessionCloseInput): Promise<void> {
@@ -2027,7 +1879,7 @@ export class OmpProviderSession {
2027
1879
  }
2028
1880
  this.imageMaterializer.clear();
2029
1881
  this.compaction.finish("canceled");
2030
- this.resolveAllPermissions(true);
1882
+ this.permissions.resolveAllPermissions(true);
2031
1883
  this.closed = true;
2032
1884
  this.configRefreshAttempts = 0;
2033
1885
  this.configRefreshDirty = false;
@@ -2352,7 +2204,7 @@ export class OmpProviderSession {
2352
2204
  this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval request");
2353
2205
  return;
2354
2206
  }
2355
- this.publishToolPermission(event);
2207
+ this.permissions.publishToolPermission(event);
2356
2208
  return;
2357
2209
  }
2358
2210
  if (event.type === "tool_approval_cancel") {
@@ -2360,18 +2212,17 @@ export class OmpProviderSession {
2360
2212
  this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval cancellation");
2361
2213
  return;
2362
2214
  }
2363
- this.cancelToolPermission(event);
2215
+ this.permissions.cancelToolPermission(event);
2364
2216
  return;
2365
2217
  }
2366
2218
  if (event.type === "extension_ui_request") {
2367
2219
  if (event.method === "cancel") {
2368
- if (this.pendingFreeformSelection?.nativeSelectId === event.targetId) {
2369
- this.pendingFreeformSelection = null;
2370
- }
2371
- this.resolvePermissionByNativeId(event.targetId ?? "");
2220
+ this.permissions.clearPendingFreeformSelection(event.targetId);
2221
+ this.permissions.resolvePermissionByNativeId(event.targetId ?? "");
2372
2222
  return;
2373
2223
  }
2374
- if (event.method === "input" && this.submitPendingFreeformSelection(event)) return;
2224
+ if (event.method === "input" && this.permissions.submitPendingFreeformSelection(event))
2225
+ return;
2375
2226
  if (
2376
2227
  event.method === "select" ||
2377
2228
  event.method === "confirm" ||
@@ -2382,7 +2233,7 @@ export class OmpProviderSession {
2382
2233
  this.handleRuntimeFailure();
2383
2234
  return;
2384
2235
  }
2385
- this.publishPermission(event);
2236
+ this.permissions.publishPermission(event);
2386
2237
  return;
2387
2238
  }
2388
2239
 
@@ -2546,7 +2397,7 @@ export class OmpProviderSession {
2546
2397
  if (event.type === "tool_execution_start") turn.activeToolCallIds.add(event.toolCallId);
2547
2398
  if (event.type === "tool_execution_end") {
2548
2399
  turn.activeToolCallIds.delete(event.toolCallId);
2549
- if (event.toolName === "ask_user") this.pendingFreeformSelection = null;
2400
+ if (event.toolName === "ask_user") this.permissions.clearPendingFreeformSelection();
2550
2401
  }
2551
2402
  if (event.type === "tool_execution_start" || event.type === "tool_execution_end") {
2552
2403
  try {
@@ -2821,24 +2672,6 @@ export class OmpProviderSession {
2821
2672
  });
2822
2673
  }
2823
2674
 
2824
- private submitPendingFreeformSelection(
2825
- request: Extract<OmpQuestionRequest, { method: "input" }>,
2826
- ): boolean {
2827
- const pending = this.pendingFreeformSelection;
2828
- if (!pending) return false;
2829
- this.pendingFreeformSelection = null;
2830
- if (
2831
- pending.generation !== this.generation ||
2832
- pending.runtime !== this.runtime ||
2833
- (pending.turnId !== undefined && this.activeTurn?.turnId !== pending.turnId)
2834
- ) {
2835
- return false;
2836
- }
2837
- void pending.runtime
2838
- .respondToExtensionUi({ type: "extension_ui_response", id: request.id, value: pending.value })
2839
- .catch(() => this.handleRuntimeFailure("OMP freeform response failed"));
2840
- return true;
2841
- }
2842
2675
  private replaceSlashCommands(commands: OmpAvailableCommand[]): void {
2843
2676
  this.slashCommands.clear();
2844
2677
  for (const command of [...OMP_BUILTIN_COMMANDS, ...commands]) {
@@ -2870,687 +2703,11 @@ export class OmpProviderSession {
2870
2703
  }),
2871
2704
  });
2872
2705
  }
2873
- private toolPermissionDetail(
2874
- request: OmpToolApprovalRequest,
2875
- ): ProviderToolCallDetail | undefined {
2876
- switch (request.identity.kind) {
2877
- case "shell":
2878
- return {
2879
- type: "shell",
2880
- command: this.dataFilter.text(request.identity.command, 24 * 1024),
2881
- };
2882
- case "edit":
2883
- return {
2884
- type: "edit",
2885
- filePath: this.dataFilter.text(request.identity.paths[0] ?? "", 4_096),
2886
- newString: this.dataFilter.text(request.identity.content, 20 * 1024),
2887
- };
2888
- case "write":
2889
- return {
2890
- type: "write",
2891
- filePath: this.dataFilter.text(request.identity.path, 4_096),
2892
- content: this.dataFilter.text(request.identity.content, 20 * 1024),
2893
- };
2894
- case "other":
2895
- return;
2896
- }
2897
- }
2898
-
2899
- private publishToolPermission(request: OmpToolApprovalRequest): void {
2900
- const fingerprint = createHash("sha256").update(JSON.stringify(request)).digest("base64url");
2901
- if (this.resolvedToolApprovalIds.has(request.id)) {
2902
- this.handleRuntimeFailure("OMP reused a resolved tool approval identifier");
2903
- return;
2904
- }
2905
- for (const pending of [
2906
- ...this.pendingToolPermissions.values(),
2907
- ...this.inFlightToolPermissions.values(),
2908
- ]) {
2909
- if (pending.nativeId !== request.id) continue;
2910
- if (pending.fingerprint !== fingerprint || pending.toolCallId !== request.toolCallId) {
2911
- this.handleRuntimeFailure("OMP changed a pending tool approval request");
2912
- }
2913
- return;
2914
- }
2915
- const pendingCount =
2916
- this.pendingPermissions.size +
2917
- this.inFlightPermissions.size +
2918
- this.pendingToolPermissions.size +
2919
- this.inFlightToolPermissions.size;
2920
- if (pendingCount >= MAX_PENDING_PERMISSIONS) {
2921
- this.resolvedToolApprovalIds.add(request.id);
2922
- void this.runtime
2923
- .respondToToolApproval({
2924
- type: "tool_approval_response",
2925
- id: request.id,
2926
- toolCallId: request.toolCallId,
2927
- cancelled: true,
2928
- })
2929
- .catch(() => this.handleRuntimeFailure());
2930
- return;
2931
- }
2932
-
2933
- this.permissionSequence += 1;
2934
- const permissionId = `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
2935
- const detail = this.toolPermissionDetail(request);
2936
- const filteredInput = this.dataFilter.json(request.input, 8 * 1024, 32 * 1024);
2937
- const input =
2938
- filteredInput && typeof filteredInput === "object" && !Array.isArray(filteredInput)
2939
- ? filteredInput
2940
- : {};
2941
- const descriptionParts = [
2942
- request.detail.reason,
2943
- ...request.detail.lines,
2944
- ...(request.detail.providerSafetyChecks ?? []),
2945
- ].filter((part): part is string => Boolean(part));
2946
- const publicRequest = {
2947
- id: permissionId,
2948
- name: `omp.${this.dataFilter.text(request.toolName, 256)}`,
2949
- kind: "tool" as const,
2950
- title: `Allow ${this.dataFilter.text(request.toolName, 256)}?`,
2951
- ...(descriptionParts.length > 0
2952
- ? { description: this.dataFilter.text(descriptionParts.join("\n"), 16 * 1024) }
2953
- : {}),
2954
- input: {
2955
- ...input,
2956
- identity: this.dataFilter.json(request.identity, 20 * 1024, 32 * 1024),
2957
- tier: request.tier,
2958
- toolCallId: request.toolCallId,
2959
- },
2960
- ...(detail ? { detail } : {}),
2961
- actions: [
2962
- { id: "allow", label: "Allow", behavior: "allow" as const, variant: "primary" as const },
2963
- { id: "deny", label: "Deny", behavior: "deny" as const, variant: "danger" as const },
2964
- ],
2965
- metadata: {
2966
- tier: request.tier,
2967
- redacted: request.detail.redacted,
2968
- truncated: request.detail.truncated,
2969
- redactedFields: request.detail.redactedFields,
2970
- truncatedFields: request.detail.truncatedFields,
2971
- },
2972
- };
2973
- const pending: PendingToolPermission = {
2974
- nativeId: request.id,
2975
- toolCallId: request.toolCallId,
2976
- fingerprint,
2977
- retainedBytes: boundedJsonBytes(
2978
- publicRequest,
2979
- MAX_PENDING_PERMISSION_BYTES,
2980
- 512,
2981
- MAX_PENDING_PERMISSION_BYTES,
2982
- 4_096,
2983
- ),
2984
- generation: this.generation,
2985
- runtime: this.runtime,
2986
- ...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
2987
- ...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
2988
- };
2989
- let retainedBytes = pending.retainedBytes;
2990
- for (const item of this.pendingPermissions.values()) retainedBytes += item.retainedBytes;
2991
- for (const item of this.inFlightPermissions.values()) retainedBytes += item.retainedBytes;
2992
- for (const item of this.pendingToolPermissions.values()) retainedBytes += item.retainedBytes;
2993
- for (const item of this.inFlightToolPermissions.values()) retainedBytes += item.retainedBytes;
2994
- if (
2995
- pending.retainedBytes === Number.POSITIVE_INFINITY ||
2996
- retainedBytes > MAX_PENDING_PERMISSION_BYTES
2997
- ) {
2998
- void this.runtime
2999
- .respondToToolApproval({
3000
- type: "tool_approval_response",
3001
- id: request.id,
3002
- toolCallId: request.toolCallId,
3003
- cancelled: true,
3004
- })
3005
- .catch(() => this.handleRuntimeFailure());
3006
- this.resolvedToolApprovalIds.add(request.id);
3007
- return;
3008
- }
3009
- this.pendingToolPermissions.set(permissionId, pending);
3010
- this.armToolPermissionTimeout(permissionId, pending);
3011
- this.projector.markAskPermissionRendered();
3012
- if (this.activeTurn) {
3013
- this.activeTurn.awaitingPermissionEvidence = true;
3014
- this.markAgentEvidence(this.activeTurn);
3015
- }
3016
- this.emit({ type: "session.permission", sessionId: this.id, request: publicRequest });
3017
- }
3018
-
3019
- private cancelToolPermission(request: OmpToolApprovalCancel): void {
3020
- for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
3021
- for (const [permissionId, pending] of permissions) {
3022
- if (pending.nativeId !== request.targetId) continue;
3023
- if (pending.toolCallId !== request.toolCallId) {
3024
- this.handleRuntimeFailure("OMP tool approval cancellation did not match its tool call");
3025
- return;
3026
- }
3027
- permissions.delete(permissionId);
3028
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
3029
- this.resolvedToolApprovalIds.add(pending.nativeId);
3030
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3031
- this.reevaluateDeferredPermissionTerminal();
3032
- return;
3033
- }
3034
- }
3035
- if (!this.resolvedToolApprovalIds.has(request.targetId)) {
3036
- this.handleRuntimeFailure("OMP canceled an unknown tool approval request");
3037
- }
3038
- }
3039
-
3040
- private publishPermission(request: OmpQuestionRequest): void {
3041
- if (request.method === "select" && !request.options?.length) {
3042
- this.handleRuntimeFailure();
3043
- return;
3044
- }
3045
- const fingerprint = permissionFingerprint(request);
3046
- let existingId: string | undefined;
3047
- let existingPending: PendingPermission | undefined;
3048
- for (const [permissionId, pending] of this.pendingPermissions) {
3049
- if (pending.nativeId !== request.id) continue;
3050
- if (pending.fingerprint === fingerprint) {
3051
- existingId = permissionId;
3052
- existingPending = pending;
3053
- } else {
3054
- this.pendingPermissions.delete(permissionId);
3055
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
3056
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3057
- }
3058
- break;
3059
- }
3060
- if (!existingId) {
3061
- for (const pending of this.inFlightPermissions.values()) {
3062
- if (pending.nativeId !== request.id) continue;
3063
- if (pending.fingerprint !== fingerprint) this.handleRuntimeFailure();
3064
- return;
3065
- }
3066
- }
3067
- if (
3068
- !existingId &&
3069
- this.pendingPermissions.size +
3070
- this.inFlightPermissions.size +
3071
- this.pendingToolPermissions.size +
3072
- this.inFlightToolPermissions.size >=
3073
- MAX_PENDING_PERMISSIONS
3074
- ) {
3075
- this.rejectPermissionRequest(request, "Too many OMP questions are already pending");
3076
- return;
3077
- }
3078
- if (existingPending?.timer !== undefined) this.scheduler.clear(existingPending.timer);
3079
- if (!existingId) this.permissionSequence += 1;
3080
- const id =
3081
- existingId ?? `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
3082
- const header = this.dataFilter.text(request.title ?? "OMP question", 4_096);
3083
- const freeformSentinel =
3084
- request.method === "select" && request.options.includes(OMP_ASK_USER_FREEFORM_SENTINEL)
3085
- ? OMP_ASK_USER_FREEFORM_SENTINEL
3086
- : undefined;
3087
- const optionValues = new Map<string, string>();
3088
- const displayValues = new Map<string, string>();
3089
- const usedOptionLabels = new Set<string>();
3090
- const optionDetails = request.method === "select" ? request.optionDetails : undefined;
3091
- const selectableOptions =
3092
- request.method === "select"
3093
- ? request.options.flatMap((nativeValue, index) =>
3094
- nativeValue === freeformSentinel ? [] : [{ nativeValue, index }],
3095
- )
3096
- : [];
3097
- const options =
3098
- request.method === "select"
3099
- ? selectableOptions.map(({ nativeValue, index }) => {
3100
- const baseLabel = this.dataFilter.text(nativeValue, 4_096);
3101
- let label = baseLabel;
3102
- let suffix = 2;
3103
- while (usedOptionLabels.has(label)) {
3104
- label = `${baseLabel} (${suffix})`;
3105
- suffix += 1;
3106
- }
3107
- usedOptionLabels.add(label);
3108
- const value = `${id}:option:${index}`;
3109
- optionValues.set(value, nativeValue);
3110
- displayValues.set(label, nativeValue);
3111
- return {
3112
- label,
3113
- value,
3114
- ...(optionDetails?.[index]?.description
3115
- ? {
3116
- description: this.dataFilter.text(
3117
- optionDetails[index]?.description ?? "",
3118
- 16_384,
3119
- ),
3120
- }
3121
- : {}),
3122
- };
3123
- })
3124
- : undefined;
3125
- const questionOptions =
3126
- request.method === "confirm" ? [{ label: "Yes" }, { label: "No" }] : (options ?? []);
3127
- const actions =
3128
- request.method === "select"
3129
- ? [
3130
- ...(options ?? []).map((option) => ({
3131
- id: option.value,
3132
- label: option.label,
3133
- behavior: "allow" as const,
3134
- variant: "secondary" as const,
3135
- })),
3136
- {
3137
- id: "cancel",
3138
- label: "Cancel",
3139
- behavior: "deny" as const,
3140
- variant: "secondary" as const,
3141
- },
3142
- ]
3143
- : [
3144
- {
3145
- id: "submit",
3146
- label: request.method === "confirm" ? "Confirm" : "Submit",
3147
- behavior: "allow" as const,
3148
- variant: "primary" as const,
3149
- },
3150
- {
3151
- id: "cancel",
3152
- label: "Cancel",
3153
- behavior: "deny" as const,
3154
- variant: "secondary" as const,
3155
- },
3156
- ];
3157
- const pending: PendingPermission = {
3158
- nativeId: request.id,
3159
- header,
3160
- fingerprint,
3161
- optionValues,
3162
- actionBehaviors: new Map(actions.map((action) => [action.id, action.behavior])),
3163
- displayValues,
3164
- generation: this.generation,
3165
- runtime: this.runtime,
3166
- request,
3167
- retainedBytes: boundedJsonBytes(
3168
- { request, header, options: questionOptions, actions },
3169
- MAX_PENDING_PERMISSION_BYTES,
3170
- 512,
3171
- MAX_PENDING_PERMISSION_BYTES,
3172
- 4_096,
3173
- ),
3174
- ...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
3175
- ...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
3176
- ...(freeformSentinel ? { freeformSentinel } : {}),
3177
- };
3178
- let retainedPermissionBytes = pending.retainedBytes;
3179
- for (const [permissionId, retained] of this.pendingPermissions) {
3180
- if (permissionId !== existingId) retainedPermissionBytes += retained.retainedBytes;
3181
- }
3182
- for (const retained of this.inFlightPermissions.values()) {
3183
- retainedPermissionBytes += retained.retainedBytes;
3184
- }
3185
- for (const retained of this.pendingToolPermissions.values()) {
3186
- retainedPermissionBytes += retained.retainedBytes;
3187
- }
3188
- for (const retained of this.inFlightToolPermissions.values()) {
3189
- retainedPermissionBytes += retained.retainedBytes;
3190
- }
3191
- if (
3192
- pending.retainedBytes === Number.POSITIVE_INFINITY ||
3193
- retainedPermissionBytes > MAX_PENDING_PERMISSION_BYTES
3194
- ) {
3195
- if (existingId) {
3196
- this.pendingPermissions.delete(existingId);
3197
- this.emit({
3198
- type: "session.permission_resolved",
3199
- sessionId: this.id,
3200
- permissionId: existingId,
3201
- });
3202
- }
3203
- this.rejectPermissionRequest(request, "OMP question data exceeded the pending input budget");
3204
- return;
3205
- }
3206
- this.pendingPermissions.set(id, pending);
3207
- this.armPermissionTimeout(id, pending);
3208
- this.projector.markAskPermissionRendered();
3209
- if (this.activeTurn) {
3210
- this.activeTurn.awaitingPermissionEvidence = true;
3211
- this.markAgentEvidence(this.activeTurn);
3212
- }
3213
- this.emit({
3214
- type: "session.permission",
3215
- sessionId: this.id,
3216
- request: {
3217
- id,
3218
- name: `omp.${request.method}`,
3219
- kind: "question",
3220
- title: header,
3221
- ...(request.method === "confirm"
3222
- ? { description: this.dataFilter.text(request.message, 64 * 1024) }
3223
- : {}),
3224
- input: {
3225
- questions: [
3226
- {
3227
- header,
3228
- question: this.dataFilter.text(
3229
- request.method === "confirm" ? request.message : request.title,
3230
- 64 * 1024,
3231
- ),
3232
- options: questionOptions,
3233
- multiSelect: false,
3234
- ...(freeformSentinel ? { allowOther: true } : {}),
3235
- ...(request.method === "input" && request.placeholder
3236
- ? { placeholder: this.dataFilter.text(request.placeholder, 4_096) }
3237
- : {}),
3238
- ...((request.method === "input" || request.method === "editor") && request.prefill
3239
- ? { prefill: this.dataFilter.text(request.prefill) }
3240
- : {}),
3241
- },
3242
- ],
3243
- },
3244
- actions,
3245
- },
3246
- });
3247
- }
3248
-
3249
- private rejectPermissionRequest(request: OmpQuestionRequest, description: string): void {
3250
- this.emit({
3251
- type: "session.notice",
3252
- sessionId: this.id,
3253
- notice: {
3254
- id: `omp:permission-rejected:${this.permissionSequence + 1}`,
3255
- severity: "warning",
3256
- title: "OMP question canceled",
3257
- description,
3258
- },
3259
- });
3260
- void this.runtime
3261
- .respondToExtensionUi({ type: "extension_ui_response", id: request.id, cancelled: true })
3262
- .catch(() => this.handleRuntimeFailure());
3263
- }
3264
-
3265
- private freeformSelection(
3266
- pending: PendingPermission,
3267
- response: ProviderPermissionResponse,
3268
- ): string | undefined {
3269
- if (
3270
- pending.request.method !== "select" ||
3271
- !pending.freeformSentinel ||
3272
- response.behavior !== "allow" ||
3273
- response.selectedActionId !== undefined
3274
- ) {
3275
- return;
3276
- }
3277
- const answers = response.updatedInput?.answers;
3278
- const answer =
3279
- answers && typeof answers === "object" && !Array.isArray(answers)
3280
- ? answers[pending.header]
3281
- : undefined;
3282
- const value = Array.isArray(answer) ? answer[0] : answer;
3283
- if (
3284
- typeof value !== "string" ||
3285
- pending.optionValues.has(value) ||
3286
- pending.displayValues.has(value)
3287
- ) {
3288
- return;
3289
- }
3290
- if (
3291
- value === pending.freeformSentinel ||
3292
- value.trim().length === 0 ||
3293
- value.includes("\0") ||
3294
- utf8Bytes(value) > MAX_FREEFORM_RESPONSE_BYTES
3295
- ) {
3296
- throw new OmpPublicError("OMP freeform response is invalid");
3297
- }
3298
- return value;
3299
- }
3300
-
3301
- private extensionUiResponse(
3302
- pending: PendingPermission,
3303
- response: ProviderPermissionResponse,
3304
- ): OmpExtensionUiResponse {
3305
- if (response.selectedActionId !== undefined) {
3306
- const expectedBehavior = pending.actionBehaviors.get(response.selectedActionId);
3307
- if (expectedBehavior === undefined || expectedBehavior !== response.behavior) {
3308
- throw new OmpPublicError("OMP permission action is invalid");
3309
- }
3310
- }
3311
- const { nativeId, request, header } = pending;
3312
- if (request.method === "confirm") {
3313
- if (response.behavior === "deny") {
3314
- return { type: "extension_ui_response", id: nativeId, confirmed: false };
3315
- }
3316
- const answers = response.updatedInput?.answers;
3317
- const answer =
3318
- answers && typeof answers === "object" && !Array.isArray(answers)
3319
- ? answers[header]
3320
- : undefined;
3321
- return {
3322
- type: "extension_ui_response",
3323
- id: nativeId,
3324
- confirmed: typeof answer === "string" ? /^yes$/iu.test(answer.trim()) : true,
3325
- };
3326
- }
3327
- this.reevaluateDeferredPermissionTerminal();
3328
- if (response.behavior === "deny") {
3329
- return { type: "extension_ui_response", id: nativeId, cancelled: true };
3330
- }
3331
- const selectedValue = response.selectedActionId
3332
- ? pending.optionValues.get(response.selectedActionId)
3333
- : undefined;
3334
- if (selectedValue !== undefined) {
3335
- return { type: "extension_ui_response", id: nativeId, value: selectedValue };
3336
- }
3337
- const answers = response.updatedInput?.answers;
3338
- if (!answers || typeof answers !== "object" || Array.isArray(answers)) {
3339
- throw new OmpPublicError("OMP question response requires answers");
3340
- }
3341
- const answer = answers[header];
3342
- const publicValue = Array.isArray(answer) ? answer[0] : answer;
3343
- if (typeof publicValue !== "string") {
3344
- throw new OmpPublicError("OMP question response is invalid");
3345
- }
3346
- if (
3347
- request.method === "select" &&
3348
- pending.freeformSentinel &&
3349
- publicValue !== pending.freeformSentinel &&
3350
- !pending.displayValues.has(publicValue) &&
3351
- !pending.optionValues.has(publicValue)
3352
- ) {
3353
- if (
3354
- publicValue.trim().length === 0 ||
3355
- publicValue.includes("\0") ||
3356
- utf8Bytes(publicValue) > MAX_FREEFORM_RESPONSE_BYTES
3357
- ) {
3358
- throw new OmpPublicError("OMP freeform response is invalid");
3359
- }
3360
- this.pendingFreeformSelection = {
3361
- value: publicValue,
3362
- nativeSelectId: pending.nativeId,
3363
- generation: pending.generation,
3364
- runtime: pending.runtime,
3365
- ...(pending.turnId ? { turnId: pending.turnId } : {}),
3366
- };
3367
- return {
3368
- type: "extension_ui_response",
3369
- id: nativeId,
3370
- value: pending.freeformSentinel,
3371
- };
3372
- }
3373
- if (request.method === "select") {
3374
- const mapped =
3375
- pending.optionValues.get(publicValue) ?? pending.displayValues.get(publicValue);
3376
- if (mapped === undefined) {
3377
- throw new OmpPublicError("OMP selection response is invalid");
3378
- }
3379
- return { type: "extension_ui_response", id: nativeId, value: mapped };
3380
- }
3381
- return { type: "extension_ui_response", id: nativeId, value: publicValue };
3382
- }
3383
-
3384
- private resolvePermissionByNativeId(nativeId: string): void {
3385
- for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
3386
- for (const [permissionId, pending] of permissions) {
3387
- if (pending.nativeId !== nativeId) continue;
3388
- permissions.delete(permissionId);
3389
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
3390
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3391
- this.reevaluateDeferredPermissionTerminal();
3392
- return;
3393
- }
3394
- }
3395
- }
3396
-
3397
- private resolveTurnPermissions(turnId: string): void {
3398
- if (this.pendingFreeformSelection?.turnId === turnId) this.pendingFreeformSelection = null;
3399
- this.resolvePermissions((pending) => pending.turnId === turnId, true);
3400
- this.resolveToolPermissions((pending) => pending.turnId === turnId, true);
3401
- }
3402
- private resolveAllPermissions(cancelNative = false): void {
3403
- this.pendingFreeformSelection = null;
3404
- this.resolvePermissions(() => true, cancelNative);
3405
- this.resolveToolPermissions(() => true, cancelNative);
3406
- }
3407
-
3408
- private resolvePermissions(
3409
- matches: (pending: PendingPermission) => boolean,
3410
- cancelNative: boolean,
3411
- ): void {
3412
- const permissionIds = new Set<string>();
3413
- for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
3414
- for (const [permissionId, pending] of permissions) {
3415
- if (!matches(pending)) continue;
3416
- permissions.delete(permissionId);
3417
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
3418
- permissionIds.add(permissionId);
3419
- if (cancelNative && permissions === this.pendingPermissions) {
3420
- void pending.runtime
3421
- .respondToExtensionUi({
3422
- type: "extension_ui_response",
3423
- id: pending.nativeId,
3424
- cancelled: true,
3425
- })
3426
- .catch(() => {
3427
- if (!this.closed && !this.runtimeDead) {
3428
- this.invalidateRuntime("OMP permission cancellation failed");
3429
- }
3430
- });
3431
- }
3432
- }
3433
- }
3434
- for (const permissionId of permissionIds) {
3435
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3436
- }
3437
- this.reevaluateDeferredPermissionTerminal();
3438
- }
3439
- private resolveToolPermissions(
3440
- matches: (pending: PendingToolPermission) => boolean,
3441
- cancelNative: boolean,
3442
- ): void {
3443
- const permissionIds = new Set<string>();
3444
- for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
3445
- for (const [permissionId, pending] of permissions) {
3446
- if (!matches(pending)) continue;
3447
- permissions.delete(permissionId);
3448
- if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
3449
- this.resolvedToolApprovalIds.add(pending.nativeId);
3450
- permissionIds.add(permissionId);
3451
- if (cancelNative && permissions === this.pendingToolPermissions) {
3452
- void pending.runtime
3453
- .respondToToolApproval({
3454
- type: "tool_approval_response",
3455
- id: pending.nativeId,
3456
- toolCallId: pending.toolCallId,
3457
- cancelled: true,
3458
- })
3459
- .catch(() => {
3460
- if (!this.closed && !this.runtimeDead) {
3461
- this.invalidateRuntime("OMP tool permission cancellation failed");
3462
- }
3463
- });
3464
- }
3465
- }
3466
- }
3467
- for (const permissionId of permissionIds) {
3468
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3469
- }
3470
- this.reevaluateDeferredPermissionTerminal();
3471
- }
3472
-
3473
- private armToolPermissionTimeout(permissionId: string, pending: PendingToolPermission): void {
3474
- if (pending.expiresAt === undefined) return;
3475
- const remainingMs = Math.max(0, pending.expiresAt - Date.now());
3476
- pending.timer = this.scheduler.set(() => {
3477
- if (this.pendingToolPermissions.get(permissionId) !== pending) return;
3478
- this.pendingToolPermissions.delete(permissionId);
3479
- this.inFlightToolPermissions.set(permissionId, pending);
3480
- void pending.runtime
3481
- .respondToToolApproval({
3482
- type: "tool_approval_response",
3483
- id: pending.nativeId,
3484
- toolCallId: pending.toolCallId,
3485
- cancelled: true,
3486
- timedOut: true,
3487
- })
3488
- .then(
3489
- () => {
3490
- if (this.inFlightToolPermissions.get(permissionId) !== pending) return;
3491
- this.inFlightToolPermissions.delete(permissionId);
3492
- this.resolvedToolApprovalIds.add(pending.nativeId);
3493
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3494
- this.reevaluateDeferredPermissionTerminal();
3495
- },
3496
- () => this.handleRuntimeFailure(),
3497
- );
3498
- }, remainingMs);
3499
- }
3500
-
3501
- private armPermissionTimeout(permissionId: string, pending: PendingPermission): void {
3502
- if (pending.expiresAt === undefined) return;
3503
- const remainingMs = Math.max(0, pending.expiresAt - Date.now());
3504
- pending.timer = this.scheduler.set(() => {
3505
- if (this.pendingPermissions.get(permissionId) !== pending) return;
3506
- this.pendingPermissions.delete(permissionId);
3507
- if (!this.permissionOwnerIsCurrent(pending)) {
3508
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3509
- this.reevaluateDeferredPermissionTerminal();
3510
- return;
3511
- }
3512
- this.inFlightPermissions.set(permissionId, pending);
3513
- void pending.runtime
3514
- .respondToExtensionUi({
3515
- type: "extension_ui_response",
3516
- id: pending.nativeId,
3517
- cancelled: true,
3518
- timedOut: true,
3519
- })
3520
- .then(
3521
- () => {
3522
- if (this.inFlightPermissions.get(permissionId) !== pending) return;
3523
- this.inFlightPermissions.delete(permissionId);
3524
- this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
3525
- this.reevaluateDeferredPermissionTerminal();
3526
- },
3527
- () => this.handleRuntimeFailure(),
3528
- );
3529
- }, remainingMs);
3530
- }
3531
-
3532
- private permissionOwnerIsCurrent(pending: PendingPermission | PendingToolPermission): boolean {
3533
- if (
3534
- this.closed ||
3535
- this.runtimeDead ||
3536
- pending.generation !== this.generation ||
3537
- pending.runtime !== this.runtime
3538
- ) {
3539
- return false;
3540
- }
3541
- if (pending.turnId === undefined) return true;
3542
- return this.activeTurn?.turnId === pending.turnId && !this.activeTurn.terminal;
3543
- }
3544
2706
 
3545
2707
  private hasTerminalConflict(turn: ActiveTurn, includeChildren = true): boolean {
3546
- const ownsTurn = (pending: PendingPermission | PendingToolPermission) =>
3547
- pending.turnId === turn.turnId;
3548
2708
  return (
3549
2709
  turn.awaitingPermissionEvidence ||
3550
- [...this.pendingPermissions.values()].some(ownsTurn) ||
3551
- [...this.inFlightPermissions.values()].some(ownsTurn) ||
3552
- [...this.pendingToolPermissions.values()].some(ownsTurn) ||
3553
- [...this.inFlightToolPermissions.values()].some(ownsTurn) ||
2710
+ this.permissions.hasForTurn(turn.turnId) ||
3554
2711
  turn.activeToolCallIds.size > 0 ||
3555
2712
  turn.steersInFlight > 0 ||
3556
2713
  (includeChildren && Boolean(this.subsessions?.hasActiveChildren()))
@@ -3994,7 +3151,7 @@ export class OmpProviderSession {
3994
3151
  turn.terminal = true;
3995
3152
  this.usage.stopPoll(turn);
3996
3153
  this.cancelAmbiguousTerminal(turn);
3997
- this.resolveTurnPermissions(turn.turnId);
3154
+ this.permissions.resolveTurnPermissions(turn.turnId);
3998
3155
  this.projector.finishTurn(turn.turnId);
3999
3156
  if (this.activeTurn === turn) this.activeTurn = null;
4000
3157
  }
@@ -4105,7 +3262,7 @@ export class OmpProviderSession {
4105
3262
  }
4106
3263
  }
4107
3264
  this.publishPendingUsers(turn);
4108
- this.resolveTurnPermissions(turn.turnId);
3265
+ this.permissions.resolveTurnPermissions(turn.turnId);
4109
3266
  try {
4110
3267
  this.projector.finishTurn(turn.turnId, preserveCompactions);
4111
3268
  } catch (error) {
@@ -4137,7 +3294,7 @@ export class OmpProviderSession {
4137
3294
  this.imageMaterializer.clear();
4138
3295
  if (this.closed || this.runtimeDead) return;
4139
3296
  this.compaction.resetDiscardedEnds();
4140
- this.resolveAllPermissions();
3297
+ this.permissions.resolveAllPermissions();
4141
3298
  this.recoveryUsesNativeConfig ||=
4142
3299
  this.configRefreshInFlight !== null || this.configRefreshDirty || this.configMutationInFlight;
4143
3300
  const turn = this.activeTurn;