@omercnet/paseo-omp 0.4.0-next.123.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,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
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
|
-
ProviderUsage,
|
|
11
8
|
} from "@getpaseo/plugin/server/provider";
|
|
12
9
|
import type {
|
|
13
10
|
OmpOperationalFailure,
|
|
@@ -35,14 +32,10 @@ import type {
|
|
|
35
32
|
OmpAvailableCommand,
|
|
36
33
|
OmpBranchResult,
|
|
37
34
|
OmpCompactionResult,
|
|
38
|
-
OmpExtensionUiResponse,
|
|
39
35
|
OmpMessage,
|
|
40
36
|
OmpModel,
|
|
41
37
|
OmpRpcEvent,
|
|
42
38
|
OmpSessionState,
|
|
43
|
-
OmpSessionStats,
|
|
44
|
-
OmpToolApprovalCancel,
|
|
45
|
-
OmpToolApprovalRequest,
|
|
46
39
|
} from "./omp-rpc-protocol";
|
|
47
40
|
import { OmpRpcRequestRejectedError } from "./omp-rpc-transport";
|
|
48
41
|
import {
|
|
@@ -64,9 +57,11 @@ import {
|
|
|
64
57
|
OmpPublicError,
|
|
65
58
|
utf8Bytes,
|
|
66
59
|
} from "./security";
|
|
60
|
+
import { OmpSessionCompaction } from "./session-compaction";
|
|
67
61
|
import type { OmpSessionDescriptor } from "./session-descriptors";
|
|
68
62
|
import { validateNativeSessionId } from "./session-descriptors";
|
|
69
63
|
import { isNativeTurnActivity, isPassiveUiMethod, isRuntimeConfigEvent } from "./session-events";
|
|
64
|
+
import { OmpSessionPermissions } from "./session-permissions";
|
|
70
65
|
import {
|
|
71
66
|
type ActiveTurn,
|
|
72
67
|
assistantTerminalOutcome,
|
|
@@ -79,6 +74,7 @@ import {
|
|
|
79
74
|
terminalOutcome,
|
|
80
75
|
unknownTerminalOutcomeError,
|
|
81
76
|
} from "./session-terminal";
|
|
77
|
+
import { OmpSessionUsage } from "./session-usage";
|
|
82
78
|
import { OmpSubsessionProjector } from "./subsessions";
|
|
83
79
|
import {
|
|
84
80
|
defaultOmpTimelineScheduler,
|
|
@@ -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
|
|
119
|
-
const
|
|
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
|
|
132
|
-
const
|
|
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,56 +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
|
-
type ActiveCompaction = {
|
|
299
|
-
id: string;
|
|
300
|
-
trigger: "auto" | "manual";
|
|
301
|
-
turnId: string;
|
|
302
|
-
generation: number;
|
|
303
|
-
retrying: boolean;
|
|
304
|
-
action?: string;
|
|
305
|
-
preTokens?: number;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
250
|
function providerError(error: unknown, fallback: string): { message: string } {
|
|
309
251
|
return { message: isOmpPublicError(error) ? error.message : fallback };
|
|
310
252
|
}
|
|
@@ -364,30 +306,12 @@ export class OmpProviderSession {
|
|
|
364
306
|
private configRevision = 0;
|
|
365
307
|
private recoveryUsesNativeConfig = false;
|
|
366
308
|
private activeAbort: PendingAbort | null = null;
|
|
367
|
-
private
|
|
368
|
-
private
|
|
369
|
-
private usageSample: {
|
|
370
|
-
turn: ActiveTurn;
|
|
371
|
-
generation: number;
|
|
372
|
-
runtime: OmpRuntimeSession;
|
|
373
|
-
epoch: number;
|
|
374
|
-
sequence: number;
|
|
375
|
-
promise: Promise<OmpSessionState | undefined>;
|
|
376
|
-
} | null = null;
|
|
377
|
-
private activeCompaction: ActiveCompaction | null = null;
|
|
378
|
-
private discardedCompactionEnds = 0;
|
|
379
|
-
private lastUsage: ProviderUsage | null = null;
|
|
309
|
+
private readonly usage: OmpSessionUsage;
|
|
310
|
+
private readonly compaction: OmpSessionCompaction;
|
|
380
311
|
private revertInFlight = false;
|
|
381
312
|
private runtimeTurnCompleted = false;
|
|
382
313
|
private commandCatalog: OmpAvailableCommand[];
|
|
383
|
-
private
|
|
384
|
-
private readonly permissionNamespace = randomUUID();
|
|
385
|
-
private readonly pendingPermissions = new Map<string, PendingPermission>();
|
|
386
|
-
private readonly inFlightPermissions = new Map<string, PendingPermission>();
|
|
387
|
-
private pendingFreeformSelection: PendingFreeformSelection | null = null;
|
|
388
|
-
private readonly pendingToolPermissions = new Map<string, PendingToolPermission>();
|
|
389
|
-
private readonly inFlightToolPermissions = new Map<string, PendingToolPermission>();
|
|
390
|
-
private readonly resolvedToolApprovalIds = new BoundedStringSet(MAX_TRACKED_ENTRY_IDS);
|
|
314
|
+
private readonly permissions: OmpSessionPermissions;
|
|
391
315
|
private unsupportedThinkingNoticePending = false;
|
|
392
316
|
private unsupportedThinkingNoticePublished = false;
|
|
393
317
|
|
|
@@ -420,6 +344,26 @@ export class OmpProviderSession {
|
|
|
420
344
|
this.id = id;
|
|
421
345
|
this.cwd = config.cwd;
|
|
422
346
|
this.scheduler = scheduler;
|
|
347
|
+
this.usage = new OmpSessionUsage(
|
|
348
|
+
id,
|
|
349
|
+
emit,
|
|
350
|
+
scheduler,
|
|
351
|
+
() => ({
|
|
352
|
+
closed: this.closed,
|
|
353
|
+
runtimeDead: this.runtimeDead !== null,
|
|
354
|
+
generation: this.generation,
|
|
355
|
+
runtime: this.runtime,
|
|
356
|
+
activeTurn: this.activeTurn,
|
|
357
|
+
}),
|
|
358
|
+
(turn) =>
|
|
359
|
+
!this.closed &&
|
|
360
|
+
!this.runtimeDead &&
|
|
361
|
+
!turn.terminal &&
|
|
362
|
+
!turn.terminalizing &&
|
|
363
|
+
!turn.agentEndPending &&
|
|
364
|
+
turn.generation === this.generation &&
|
|
365
|
+
this.activeTurn === turn,
|
|
366
|
+
);
|
|
423
367
|
this.dataFilter = new OmpPublicDataSerializer(outputRedactionValues);
|
|
424
368
|
this.nativeModelsByPublicId = nativeModelsByPublicId;
|
|
425
369
|
this.commandCatalog = commandCatalog;
|
|
@@ -433,6 +377,31 @@ export class OmpProviderSession {
|
|
|
433
377
|
capabilities.includes("timeline.plugin"),
|
|
434
378
|
hostTools.labels,
|
|
435
379
|
);
|
|
380
|
+
this.compaction = new OmpSessionCompaction(
|
|
381
|
+
id,
|
|
382
|
+
emit,
|
|
383
|
+
this.projector,
|
|
384
|
+
this.dataFilter,
|
|
385
|
+
this.usage,
|
|
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
|
+
});
|
|
436
405
|
this.subsessions = capabilities.includes("session.subsession")
|
|
437
406
|
? new OmpSubsessionProjector(
|
|
438
407
|
id,
|
|
@@ -774,289 +743,6 @@ export class OmpProviderSession {
|
|
|
774
743
|
});
|
|
775
744
|
}
|
|
776
745
|
|
|
777
|
-
private usageFrom(
|
|
778
|
-
state: OmpSessionState | undefined,
|
|
779
|
-
stats: OmpSessionStats | undefined,
|
|
780
|
-
): ProviderUsage | undefined {
|
|
781
|
-
if (!state?.contextUsage && !stats) return undefined;
|
|
782
|
-
const stateContext = state?.contextUsage;
|
|
783
|
-
const statsContext = stats?.contextUsage;
|
|
784
|
-
const modelCapacity = state?.model?.contextWindow;
|
|
785
|
-
const inputTokens = stats?.tokens?.input;
|
|
786
|
-
const cachedInputTokens = stats?.tokens?.cacheRead;
|
|
787
|
-
const outputTokens = stats?.tokens?.output;
|
|
788
|
-
const totalCostUsd = stats?.cost;
|
|
789
|
-
const contextTokens =
|
|
790
|
-
typeof stateContext?.tokens === "number"
|
|
791
|
-
? stateContext.tokens
|
|
792
|
-
: typeof statsContext?.tokens === "number"
|
|
793
|
-
? statsContext.tokens
|
|
794
|
-
: undefined;
|
|
795
|
-
const contextWindow =
|
|
796
|
-
typeof stateContext?.contextWindow === "number" && stateContext.contextWindow > 0
|
|
797
|
-
? stateContext.contextWindow
|
|
798
|
-
: typeof statsContext?.contextWindow === "number" && statsContext.contextWindow > 0
|
|
799
|
-
? statsContext.contextWindow
|
|
800
|
-
: typeof modelCapacity === "number" && modelCapacity > 0
|
|
801
|
-
? modelCapacity
|
|
802
|
-
: undefined;
|
|
803
|
-
const usage: ProviderUsage = {
|
|
804
|
-
...(typeof inputTokens === "number" ? { inputTokens } : {}),
|
|
805
|
-
...(typeof cachedInputTokens === "number" ? { cachedInputTokens } : {}),
|
|
806
|
-
...(typeof outputTokens === "number" ? { outputTokens } : {}),
|
|
807
|
-
...(typeof totalCostUsd === "number" ? { totalCostUsd } : {}),
|
|
808
|
-
...(contextTokens !== undefined ? { contextWindowUsedTokens: contextTokens } : {}),
|
|
809
|
-
...(contextWindow !== undefined ? { contextWindowMaxTokens: contextWindow } : {}),
|
|
810
|
-
};
|
|
811
|
-
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
private ownsUsageSample(
|
|
815
|
-
turn: ActiveTurn,
|
|
816
|
-
generation: number,
|
|
817
|
-
runtime: OmpRuntimeSession,
|
|
818
|
-
): boolean {
|
|
819
|
-
return (
|
|
820
|
-
!this.closed &&
|
|
821
|
-
!this.runtimeDead &&
|
|
822
|
-
!turn.terminal &&
|
|
823
|
-
generation === this.generation &&
|
|
824
|
-
runtime === this.runtime &&
|
|
825
|
-
this.activeTurn === turn
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
private publishUsageSnapshot(
|
|
830
|
-
turn: ActiveTurn,
|
|
831
|
-
minimumEpoch = this.usageEpoch,
|
|
832
|
-
minimumSequence = turn.usageSampleFloor,
|
|
833
|
-
): Promise<OmpSessionState | undefined> {
|
|
834
|
-
const generation = turn.generation;
|
|
835
|
-
const runtime = this.runtime;
|
|
836
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return Promise.resolve(undefined);
|
|
837
|
-
const current = this.usageSample;
|
|
838
|
-
if (current) {
|
|
839
|
-
if (
|
|
840
|
-
current.turn === turn &&
|
|
841
|
-
current.generation === generation &&
|
|
842
|
-
current.runtime === runtime &&
|
|
843
|
-
current.epoch >= minimumEpoch &&
|
|
844
|
-
current.sequence >= minimumSequence
|
|
845
|
-
) {
|
|
846
|
-
return current.promise;
|
|
847
|
-
}
|
|
848
|
-
return current.promise.then(() => {
|
|
849
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return undefined;
|
|
850
|
-
return this.publishUsageSnapshot(turn, minimumEpoch, minimumSequence);
|
|
851
|
-
});
|
|
852
|
-
}
|
|
853
|
-
const epoch = this.usageEpoch;
|
|
854
|
-
const sequence = ++this.usageSequence;
|
|
855
|
-
const promise = Promise.allSettled([runtime.getState(), runtime.getSessionStats()]).then(
|
|
856
|
-
([stateResult, statsResult]) => {
|
|
857
|
-
if (
|
|
858
|
-
!this.ownsUsageSample(turn, generation, runtime) ||
|
|
859
|
-
epoch !== this.usageEpoch ||
|
|
860
|
-
epoch < minimumEpoch ||
|
|
861
|
-
sequence < turn.usageSampleFloor ||
|
|
862
|
-
sequence < minimumSequence
|
|
863
|
-
) {
|
|
864
|
-
return undefined;
|
|
865
|
-
}
|
|
866
|
-
const state = stateResult.status === "fulfilled" ? stateResult.value : undefined;
|
|
867
|
-
const stats = statsResult.status === "fulfilled" ? statsResult.value : undefined;
|
|
868
|
-
const usage = this.usageFrom(state, stats);
|
|
869
|
-
if (usage) {
|
|
870
|
-
this.lastUsage = usage;
|
|
871
|
-
this.emit({ type: "session.usage", sessionId: this.id, turnId: turn.turnId, usage });
|
|
872
|
-
}
|
|
873
|
-
return state;
|
|
874
|
-
},
|
|
875
|
-
);
|
|
876
|
-
this.usageSample = { turn, generation, runtime, epoch, sequence, promise };
|
|
877
|
-
void promise.finally(() => {
|
|
878
|
-
if (this.usageSample?.promise === promise) this.usageSample = null;
|
|
879
|
-
});
|
|
880
|
-
return promise;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
private async boundedUsageSnapshot(
|
|
884
|
-
turn: ActiveTurn,
|
|
885
|
-
timeoutMs: number,
|
|
886
|
-
): Promise<OmpSessionState | undefined> {
|
|
887
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
888
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
889
|
-
try {
|
|
890
|
-
return await Promise.race([this.publishUsageSnapshot(turn), timeout.promise]);
|
|
891
|
-
} finally {
|
|
892
|
-
this.scheduler.clear(timer);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
private async boundedTerminalState(
|
|
897
|
-
turn: ActiveTurn,
|
|
898
|
-
timeoutMs: number,
|
|
899
|
-
): Promise<OmpSessionState | undefined> {
|
|
900
|
-
const generation = turn.generation;
|
|
901
|
-
const runtime = this.runtime;
|
|
902
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
903
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
904
|
-
try {
|
|
905
|
-
const state = await Promise.race([runtime.getState(), timeout.promise]);
|
|
906
|
-
return this.ownsUsageSample(turn, generation, runtime) ? state : undefined;
|
|
907
|
-
} catch {
|
|
908
|
-
return undefined;
|
|
909
|
-
} finally {
|
|
910
|
-
this.scheduler.clear(timer);
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
private isActiveTurn(turn: ActiveTurn): boolean {
|
|
915
|
-
return (
|
|
916
|
-
!this.closed &&
|
|
917
|
-
!this.runtimeDead &&
|
|
918
|
-
!turn.terminal &&
|
|
919
|
-
!turn.terminalizing &&
|
|
920
|
-
!turn.agentEndPending &&
|
|
921
|
-
turn.generation === this.generation &&
|
|
922
|
-
this.activeTurn === turn
|
|
923
|
-
);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
private scheduleUsagePoll(turn: ActiveTurn, delayMs = USAGE_POLL_MS): void {
|
|
927
|
-
if (!this.isActiveTurn(turn) || turn.usagePollTimer !== undefined) return;
|
|
928
|
-
turn.usagePollTimer = this.scheduler.set(() => {
|
|
929
|
-
turn.usagePollTimer = undefined;
|
|
930
|
-
this.pollUsage(turn);
|
|
931
|
-
}, delayMs);
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
private pollUsage(turn: ActiveTurn): void {
|
|
935
|
-
if (!this.isActiveTurn(turn) || turn.usagePoll) return;
|
|
936
|
-
const poll = this.publishUsageSnapshot(turn).then(() => undefined);
|
|
937
|
-
turn.usagePoll = poll;
|
|
938
|
-
void poll.finally(() => {
|
|
939
|
-
if (turn.usagePoll === poll) turn.usagePoll = undefined;
|
|
940
|
-
this.scheduleUsagePoll(turn);
|
|
941
|
-
});
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
private stopUsagePoll(turn: ActiveTurn): void {
|
|
945
|
-
if (turn.usagePollTimer === undefined) return;
|
|
946
|
-
this.scheduler.clear(turn.usagePollTimer);
|
|
947
|
-
turn.usagePollTimer = undefined;
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
private startCompaction(turn: ActiveTurn, trigger: "auto" | "manual", action?: string): void {
|
|
951
|
-
if (this.discardedCompactionEnds > 0) {
|
|
952
|
-
this.discardedCompactionEnds += 1;
|
|
953
|
-
return;
|
|
954
|
-
}
|
|
955
|
-
const active = this.activeCompaction;
|
|
956
|
-
if (active && active.trigger === trigger && active.action === action) {
|
|
957
|
-
active.retrying = false;
|
|
958
|
-
return;
|
|
959
|
-
}
|
|
960
|
-
if (active) {
|
|
961
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
962
|
-
this.discardedCompactionEnds = 2;
|
|
963
|
-
return;
|
|
964
|
-
}
|
|
965
|
-
const operation: ActiveCompaction = {
|
|
966
|
-
id: randomUUID(),
|
|
967
|
-
trigger,
|
|
968
|
-
turnId: turn.turnId,
|
|
969
|
-
generation: turn.generation,
|
|
970
|
-
action,
|
|
971
|
-
retrying: false,
|
|
972
|
-
preTokens: this.lastUsage?.contextWindowUsedTokens,
|
|
973
|
-
};
|
|
974
|
-
this.activeCompaction = operation;
|
|
975
|
-
this.projector.flush(true);
|
|
976
|
-
this.emit({
|
|
977
|
-
type: "timeline.item",
|
|
978
|
-
sessionId: this.id,
|
|
979
|
-
item: {
|
|
980
|
-
id: operation.id,
|
|
981
|
-
type: "compaction",
|
|
982
|
-
status: "loading",
|
|
983
|
-
trigger,
|
|
984
|
-
...(operation.preTokens !== undefined ? { preTokens: operation.preTokens } : {}),
|
|
985
|
-
},
|
|
986
|
-
});
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
private finishCompaction(
|
|
990
|
-
state: "completed" | "failed" | "canceled" | "skipped",
|
|
991
|
-
options: { tokensBefore?: number | null; message?: string } = {},
|
|
992
|
-
): void {
|
|
993
|
-
const operation = this.activeCompaction;
|
|
994
|
-
if (!operation) return;
|
|
995
|
-
this.activeCompaction = null;
|
|
996
|
-
this.projector.flush(true);
|
|
997
|
-
if (state === "completed") {
|
|
998
|
-
this.usageEpoch += 1;
|
|
999
|
-
const staleSample = this.usageSample;
|
|
1000
|
-
if (staleSample && staleSample.epoch < this.usageEpoch) {
|
|
1001
|
-
this.usageSample = null;
|
|
1002
|
-
staleSample.turn.usagePoll = undefined;
|
|
1003
|
-
}
|
|
1004
|
-
this.lastUsage = null;
|
|
1005
|
-
}
|
|
1006
|
-
if (state !== "completed") {
|
|
1007
|
-
const defaultMessage =
|
|
1008
|
-
state === "failed"
|
|
1009
|
-
? "OMP compaction failed"
|
|
1010
|
-
: state === "canceled"
|
|
1011
|
-
? "OMP compaction canceled"
|
|
1012
|
-
: "OMP compaction skipped";
|
|
1013
|
-
this.emit({
|
|
1014
|
-
type: "timeline.item",
|
|
1015
|
-
sessionId: this.id,
|
|
1016
|
-
item: {
|
|
1017
|
-
id: operation.id,
|
|
1018
|
-
type: "notification",
|
|
1019
|
-
level: state === "failed" ? "error" : "info",
|
|
1020
|
-
message: this.dataFilter.text(options.message ?? defaultMessage, 4_096),
|
|
1021
|
-
},
|
|
1022
|
-
});
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
const tokensBefore = options.tokensBefore ?? operation.preTokens;
|
|
1026
|
-
this.emit({
|
|
1027
|
-
type: "timeline.item",
|
|
1028
|
-
sessionId: this.id,
|
|
1029
|
-
item: {
|
|
1030
|
-
id: operation.id,
|
|
1031
|
-
type: "compaction",
|
|
1032
|
-
status: "completed",
|
|
1033
|
-
trigger: operation.trigger,
|
|
1034
|
-
...(tokensBefore !== undefined ? { preTokens: tokensBefore } : {}),
|
|
1035
|
-
},
|
|
1036
|
-
});
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
private retireCompaction(message: string): void {
|
|
1040
|
-
const operation = this.activeCompaction;
|
|
1041
|
-
if (!operation) return;
|
|
1042
|
-
this.activeCompaction = null;
|
|
1043
|
-
this.emit({
|
|
1044
|
-
type: "timeline.item",
|
|
1045
|
-
sessionId: this.id,
|
|
1046
|
-
item: {
|
|
1047
|
-
id: operation.id,
|
|
1048
|
-
type: "compaction",
|
|
1049
|
-
status: "completed",
|
|
1050
|
-
trigger: operation.trigger,
|
|
1051
|
-
},
|
|
1052
|
-
});
|
|
1053
|
-
this.emit({
|
|
1054
|
-
type: "timeline.item",
|
|
1055
|
-
sessionId: this.id,
|
|
1056
|
-
item: { id: `${operation.id}:error`, type: "error", message },
|
|
1057
|
-
});
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
746
|
private async replayHistory(preferPersistedTranscript = false): Promise<void> {
|
|
1061
747
|
if (!this.runtime.canReplayHistory) {
|
|
1062
748
|
throw new OmpPublicError("OMP session history cannot be replayed safely");
|
|
@@ -1294,7 +980,7 @@ export class OmpProviderSession {
|
|
|
1294
980
|
const configRefresh = this.configRefreshInFlight;
|
|
1295
981
|
this.cancelConfigRefreshRetry();
|
|
1296
982
|
this.lifetime.abort(new Error(message));
|
|
1297
|
-
this.resolveAllPermissions(true);
|
|
983
|
+
this.permissions.resolveAllPermissions(true);
|
|
1298
984
|
this.subsessions?.close();
|
|
1299
985
|
this.projector.close();
|
|
1300
986
|
this.unsubscribe();
|
|
@@ -1449,8 +1135,8 @@ export class OmpProviderSession {
|
|
|
1449
1135
|
turn.starting = false;
|
|
1450
1136
|
turn.acknowledged = true;
|
|
1451
1137
|
this.startTurn(turn, false);
|
|
1452
|
-
this.
|
|
1453
|
-
this.
|
|
1138
|
+
this.compaction.start(turn, "manual");
|
|
1139
|
+
this.usage.poll(turn);
|
|
1454
1140
|
const bufferedEvents = turn.bufferedEvents.splice(0);
|
|
1455
1141
|
turn.replayingBufferedEvents = true;
|
|
1456
1142
|
for (const event of bufferedEvents) this.handleTurnEvent(turn, event);
|
|
@@ -1541,8 +1227,8 @@ export class OmpProviderSession {
|
|
|
1541
1227
|
else {
|
|
1542
1228
|
turn.steerReady.resolve();
|
|
1543
1229
|
turn.terminal = true;
|
|
1544
|
-
this.
|
|
1545
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
1230
|
+
this.compaction.finish("failed", { message: "OMP prompt failed" });
|
|
1231
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
1546
1232
|
this.projector.finishTurn(turn.turnId);
|
|
1547
1233
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
1548
1234
|
}
|
|
@@ -1714,7 +1400,7 @@ export class OmpProviderSession {
|
|
|
1714
1400
|
const result = await compaction;
|
|
1715
1401
|
turn.manualCompactionPending = false;
|
|
1716
1402
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
1717
|
-
this.
|
|
1403
|
+
this.compaction.finish("completed", { tokensBefore: result.tokensBefore });
|
|
1718
1404
|
await this.finishTurn(turn, turn.interrupted ? "canceled" : "completed");
|
|
1719
1405
|
} catch (error) {
|
|
1720
1406
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
@@ -1722,7 +1408,7 @@ export class OmpProviderSession {
|
|
|
1722
1408
|
const raw = providerError(error, "OMP compaction failed");
|
|
1723
1409
|
const failure = { message: this.dataFilter.text(raw.message, 4_096) };
|
|
1724
1410
|
const state = turn.interrupted ? "canceled" : "failed";
|
|
1725
|
-
this.
|
|
1411
|
+
this.compaction.finish(state, { message: failure.message });
|
|
1726
1412
|
await this.finishTurn(turn, state, state === "failed" ? failure : undefined, true, true);
|
|
1727
1413
|
}
|
|
1728
1414
|
}
|
|
@@ -1743,7 +1429,7 @@ export class OmpProviderSession {
|
|
|
1743
1429
|
}
|
|
1744
1430
|
const forceTerminal = turn.awaitingPermissionEvidence;
|
|
1745
1431
|
turn.awaitingPermissionEvidence = false;
|
|
1746
|
-
if (forceTerminal) this.resolveTurnPermissions(turn.turnId);
|
|
1432
|
+
if (forceTerminal) this.permissions.resolveTurnPermissions(turn.turnId);
|
|
1747
1433
|
const pending = this.activeAbort;
|
|
1748
1434
|
if (turn.interrupted) {
|
|
1749
1435
|
if (pending?.turn === turn) await this.settleInterrupt(input.requestId, pending);
|
|
@@ -2154,118 +1840,7 @@ export class OmpProviderSession {
|
|
|
2154
1840
|
}
|
|
2155
1841
|
|
|
2156
1842
|
async permission(input: SessionPermissionInput): Promise<void> {
|
|
2157
|
-
|
|
2158
|
-
if (typed) {
|
|
2159
|
-
await this.respondToToolPermission(input, typed);
|
|
2160
|
-
return;
|
|
2161
|
-
}
|
|
2162
|
-
const pending = this.pendingPermissions.get(input.permissionId);
|
|
2163
|
-
if (!pending) throw new OmpPublicError("Unknown OMP permission request");
|
|
2164
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
2165
|
-
this.pendingPermissions.delete(input.permissionId);
|
|
2166
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2167
|
-
this.emit({
|
|
2168
|
-
type: "session.permission_resolved",
|
|
2169
|
-
sessionId: this.id,
|
|
2170
|
-
permissionId: input.permissionId,
|
|
2171
|
-
});
|
|
2172
|
-
throw new OmpPublicError("OMP permission request is no longer active");
|
|
2173
|
-
}
|
|
2174
|
-
const { generation, runtime } = pending;
|
|
2175
|
-
this.pendingPermissions.delete(input.permissionId);
|
|
2176
|
-
this.inFlightPermissions.set(input.permissionId, pending);
|
|
2177
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2178
|
-
try {
|
|
2179
|
-
const freeformValue = this.freeformSelection(pending, input.response);
|
|
2180
|
-
if (freeformValue !== undefined) {
|
|
2181
|
-
this.pendingFreeformSelection = {
|
|
2182
|
-
value: freeformValue,
|
|
2183
|
-
nativeSelectId: pending.nativeId,
|
|
2184
|
-
generation,
|
|
2185
|
-
runtime,
|
|
2186
|
-
...(pending.turnId ? { turnId: pending.turnId } : {}),
|
|
2187
|
-
};
|
|
2188
|
-
}
|
|
2189
|
-
const response = this.extensionUiResponse(pending, input.response);
|
|
2190
|
-
await runtime.respondToExtensionUi(response);
|
|
2191
|
-
} catch (error) {
|
|
2192
|
-
if (this.pendingFreeformSelection?.nativeSelectId === pending.nativeId) {
|
|
2193
|
-
this.pendingFreeformSelection = null;
|
|
2194
|
-
}
|
|
2195
|
-
if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
|
|
2196
|
-
this.inFlightPermissions.delete(input.permissionId);
|
|
2197
|
-
if (
|
|
2198
|
-
this.permissionOwnerIsCurrent(pending) &&
|
|
2199
|
-
generation === this.generation &&
|
|
2200
|
-
runtime === this.runtime
|
|
2201
|
-
) {
|
|
2202
|
-
this.pendingPermissions.set(input.permissionId, pending);
|
|
2203
|
-
this.armPermissionTimeout(input.permissionId, pending);
|
|
2204
|
-
throw error;
|
|
2205
|
-
}
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
|
|
2209
|
-
this.inFlightPermissions.delete(input.permissionId);
|
|
2210
|
-
this.emit({
|
|
2211
|
-
type: "session.permission_resolved",
|
|
2212
|
-
sessionId: this.id,
|
|
2213
|
-
permissionId: input.permissionId,
|
|
2214
|
-
});
|
|
2215
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
2216
|
-
}
|
|
2217
|
-
private async respondToToolPermission(
|
|
2218
|
-
input: SessionPermissionInput,
|
|
2219
|
-
pending: PendingToolPermission,
|
|
2220
|
-
): Promise<void> {
|
|
2221
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
2222
|
-
this.pendingToolPermissions.delete(input.permissionId);
|
|
2223
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2224
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2225
|
-
this.emit({
|
|
2226
|
-
type: "session.permission_resolved",
|
|
2227
|
-
sessionId: this.id,
|
|
2228
|
-
permissionId: input.permissionId,
|
|
2229
|
-
});
|
|
2230
|
-
throw new OmpPublicError("OMP permission request is no longer active");
|
|
2231
|
-
}
|
|
2232
|
-
if (
|
|
2233
|
-
input.response.selectedActionId !== undefined &&
|
|
2234
|
-
input.response.selectedActionId !== input.response.behavior
|
|
2235
|
-
) {
|
|
2236
|
-
throw new OmpPublicError("OMP permission action is invalid");
|
|
2237
|
-
}
|
|
2238
|
-
this.pendingToolPermissions.delete(input.permissionId);
|
|
2239
|
-
this.inFlightToolPermissions.set(input.permissionId, pending);
|
|
2240
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2241
|
-
try {
|
|
2242
|
-
await pending.runtime.respondToToolApproval({
|
|
2243
|
-
type: "tool_approval_response",
|
|
2244
|
-
id: pending.nativeId,
|
|
2245
|
-
toolCallId: pending.toolCallId,
|
|
2246
|
-
approved: input.response.behavior === "allow",
|
|
2247
|
-
});
|
|
2248
|
-
} catch (error) {
|
|
2249
|
-
if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
|
|
2250
|
-
this.inFlightToolPermissions.delete(input.permissionId);
|
|
2251
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2252
|
-
this.emit({
|
|
2253
|
-
type: "session.permission_resolved",
|
|
2254
|
-
sessionId: this.id,
|
|
2255
|
-
permissionId: input.permissionId,
|
|
2256
|
-
});
|
|
2257
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
2258
|
-
throw error;
|
|
2259
|
-
}
|
|
2260
|
-
if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
|
|
2261
|
-
this.inFlightToolPermissions.delete(input.permissionId);
|
|
2262
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2263
|
-
this.emit({
|
|
2264
|
-
type: "session.permission_resolved",
|
|
2265
|
-
sessionId: this.id,
|
|
2266
|
-
permissionId: input.permissionId,
|
|
2267
|
-
});
|
|
2268
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
1843
|
+
await this.permissions.respond(input);
|
|
2269
1844
|
}
|
|
2270
1845
|
|
|
2271
1846
|
close(input?: SessionCloseInput): Promise<void> {
|
|
@@ -2299,12 +1874,12 @@ export class OmpProviderSession {
|
|
|
2299
1874
|
if (turn.started) await this.finishTurn(turn, "canceled", undefined, true, true);
|
|
2300
1875
|
else {
|
|
2301
1876
|
this.settleUnstartedTurn(turn);
|
|
2302
|
-
this.
|
|
1877
|
+
this.compaction.finish("canceled");
|
|
2303
1878
|
}
|
|
2304
1879
|
}
|
|
2305
1880
|
this.imageMaterializer.clear();
|
|
2306
|
-
this.
|
|
2307
|
-
this.resolveAllPermissions(true);
|
|
1881
|
+
this.compaction.finish("canceled");
|
|
1882
|
+
this.permissions.resolveAllPermissions(true);
|
|
2308
1883
|
this.closed = true;
|
|
2309
1884
|
this.configRefreshAttempts = 0;
|
|
2310
1885
|
this.configRefreshDirty = false;
|
|
@@ -2463,7 +2038,7 @@ export class OmpProviderSession {
|
|
|
2463
2038
|
}
|
|
2464
2039
|
if (this.subsessions) await recovered.setSubagentSubscription("events");
|
|
2465
2040
|
this.generation += 1;
|
|
2466
|
-
this.
|
|
2041
|
+
this.usage.clearLatest();
|
|
2467
2042
|
this.runtimeDead = null;
|
|
2468
2043
|
this.runtimeDisposal = null;
|
|
2469
2044
|
unsubscribeBootstrap();
|
|
@@ -2629,7 +2204,7 @@ export class OmpProviderSession {
|
|
|
2629
2204
|
this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval request");
|
|
2630
2205
|
return;
|
|
2631
2206
|
}
|
|
2632
|
-
this.publishToolPermission(event);
|
|
2207
|
+
this.permissions.publishToolPermission(event);
|
|
2633
2208
|
return;
|
|
2634
2209
|
}
|
|
2635
2210
|
if (event.type === "tool_approval_cancel") {
|
|
@@ -2637,18 +2212,17 @@ export class OmpProviderSession {
|
|
|
2637
2212
|
this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval cancellation");
|
|
2638
2213
|
return;
|
|
2639
2214
|
}
|
|
2640
|
-
this.cancelToolPermission(event);
|
|
2215
|
+
this.permissions.cancelToolPermission(event);
|
|
2641
2216
|
return;
|
|
2642
2217
|
}
|
|
2643
2218
|
if (event.type === "extension_ui_request") {
|
|
2644
2219
|
if (event.method === "cancel") {
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
}
|
|
2648
|
-
this.resolvePermissionByNativeId(event.targetId ?? "");
|
|
2220
|
+
this.permissions.clearPendingFreeformSelection(event.targetId);
|
|
2221
|
+
this.permissions.resolvePermissionByNativeId(event.targetId ?? "");
|
|
2649
2222
|
return;
|
|
2650
2223
|
}
|
|
2651
|
-
if (event.method === "input" && this.submitPendingFreeformSelection(event))
|
|
2224
|
+
if (event.method === "input" && this.permissions.submitPendingFreeformSelection(event))
|
|
2225
|
+
return;
|
|
2652
2226
|
if (
|
|
2653
2227
|
event.method === "select" ||
|
|
2654
2228
|
event.method === "confirm" ||
|
|
@@ -2659,7 +2233,7 @@ export class OmpProviderSession {
|
|
|
2659
2233
|
this.handleRuntimeFailure();
|
|
2660
2234
|
return;
|
|
2661
2235
|
}
|
|
2662
|
-
this.publishPermission(event);
|
|
2236
|
+
this.permissions.publishPermission(event);
|
|
2663
2237
|
return;
|
|
2664
2238
|
}
|
|
2665
2239
|
|
|
@@ -2810,50 +2384,20 @@ export class OmpProviderSession {
|
|
|
2810
2384
|
if (event.type === "auto_compaction_start") {
|
|
2811
2385
|
turn.nativeActivity = true;
|
|
2812
2386
|
this.cancelLocalOnlyCompletion(turn);
|
|
2813
|
-
this.
|
|
2387
|
+
this.compaction.start(turn, "auto", event.action);
|
|
2814
2388
|
return;
|
|
2815
2389
|
}
|
|
2816
2390
|
if (event.type === "auto_compaction_end") {
|
|
2817
|
-
if (this.
|
|
2818
|
-
this.
|
|
2819
|
-
return;
|
|
2391
|
+
if (this.compaction.handleAutoEnd(turn, event)) {
|
|
2392
|
+
this.usage.schedulePoll(turn, USAGE_REFRESH_MS);
|
|
2820
2393
|
}
|
|
2821
|
-
const operation = this.activeCompaction;
|
|
2822
|
-
if (
|
|
2823
|
-
operation?.trigger !== "auto" ||
|
|
2824
|
-
operation.turnId !== turn.turnId ||
|
|
2825
|
-
operation.generation !== turn.generation
|
|
2826
|
-
) {
|
|
2827
|
-
return;
|
|
2828
|
-
}
|
|
2829
|
-
if (event.action !== undefined && operation.action !== event.action) {
|
|
2830
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
2831
|
-
this.discardedCompactionEnds = 1;
|
|
2832
|
-
return;
|
|
2833
|
-
}
|
|
2834
|
-
if (event.willRetry) {
|
|
2835
|
-
operation.retrying = true;
|
|
2836
|
-
return;
|
|
2837
|
-
}
|
|
2838
|
-
const state = event.aborted
|
|
2839
|
-
? "canceled"
|
|
2840
|
-
: event.errorMessage
|
|
2841
|
-
? "failed"
|
|
2842
|
-
: event.skipped
|
|
2843
|
-
? "skipped"
|
|
2844
|
-
: "completed";
|
|
2845
|
-
this.finishCompaction(state, {
|
|
2846
|
-
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
2847
|
-
message: event.errorMessage,
|
|
2848
|
-
});
|
|
2849
|
-
this.scheduleUsagePoll(turn, USAGE_REFRESH_MS);
|
|
2850
2394
|
return;
|
|
2851
2395
|
}
|
|
2852
2396
|
if (event.type === "agent_end" && turn.manualCompactionPending) return;
|
|
2853
2397
|
if (event.type === "tool_execution_start") turn.activeToolCallIds.add(event.toolCallId);
|
|
2854
2398
|
if (event.type === "tool_execution_end") {
|
|
2855
2399
|
turn.activeToolCallIds.delete(event.toolCallId);
|
|
2856
|
-
if (event.toolName === "ask_user") this.
|
|
2400
|
+
if (event.toolName === "ask_user") this.permissions.clearPendingFreeformSelection();
|
|
2857
2401
|
}
|
|
2858
2402
|
if (event.type === "tool_execution_start" || event.type === "tool_execution_end") {
|
|
2859
2403
|
try {
|
|
@@ -3128,24 +2672,6 @@ export class OmpProviderSession {
|
|
|
3128
2672
|
});
|
|
3129
2673
|
}
|
|
3130
2674
|
|
|
3131
|
-
private submitPendingFreeformSelection(
|
|
3132
|
-
request: Extract<OmpQuestionRequest, { method: "input" }>,
|
|
3133
|
-
): boolean {
|
|
3134
|
-
const pending = this.pendingFreeformSelection;
|
|
3135
|
-
if (!pending) return false;
|
|
3136
|
-
this.pendingFreeformSelection = null;
|
|
3137
|
-
if (
|
|
3138
|
-
pending.generation !== this.generation ||
|
|
3139
|
-
pending.runtime !== this.runtime ||
|
|
3140
|
-
(pending.turnId !== undefined && this.activeTurn?.turnId !== pending.turnId)
|
|
3141
|
-
) {
|
|
3142
|
-
return false;
|
|
3143
|
-
}
|
|
3144
|
-
void pending.runtime
|
|
3145
|
-
.respondToExtensionUi({ type: "extension_ui_response", id: request.id, value: pending.value })
|
|
3146
|
-
.catch(() => this.handleRuntimeFailure("OMP freeform response failed"));
|
|
3147
|
-
return true;
|
|
3148
|
-
}
|
|
3149
2675
|
private replaceSlashCommands(commands: OmpAvailableCommand[]): void {
|
|
3150
2676
|
this.slashCommands.clear();
|
|
3151
2677
|
for (const command of [...OMP_BUILTIN_COMMANDS, ...commands]) {
|
|
@@ -3177,687 +2703,11 @@ export class OmpProviderSession {
|
|
|
3177
2703
|
}),
|
|
3178
2704
|
});
|
|
3179
2705
|
}
|
|
3180
|
-
private toolPermissionDetail(
|
|
3181
|
-
request: OmpToolApprovalRequest,
|
|
3182
|
-
): ProviderToolCallDetail | undefined {
|
|
3183
|
-
switch (request.identity.kind) {
|
|
3184
|
-
case "shell":
|
|
3185
|
-
return {
|
|
3186
|
-
type: "shell",
|
|
3187
|
-
command: this.dataFilter.text(request.identity.command, 24 * 1024),
|
|
3188
|
-
};
|
|
3189
|
-
case "edit":
|
|
3190
|
-
return {
|
|
3191
|
-
type: "edit",
|
|
3192
|
-
filePath: this.dataFilter.text(request.identity.paths[0] ?? "", 4_096),
|
|
3193
|
-
newString: this.dataFilter.text(request.identity.content, 20 * 1024),
|
|
3194
|
-
};
|
|
3195
|
-
case "write":
|
|
3196
|
-
return {
|
|
3197
|
-
type: "write",
|
|
3198
|
-
filePath: this.dataFilter.text(request.identity.path, 4_096),
|
|
3199
|
-
content: this.dataFilter.text(request.identity.content, 20 * 1024),
|
|
3200
|
-
};
|
|
3201
|
-
case "other":
|
|
3202
|
-
return;
|
|
3203
|
-
}
|
|
3204
|
-
}
|
|
3205
|
-
|
|
3206
|
-
private publishToolPermission(request: OmpToolApprovalRequest): void {
|
|
3207
|
-
const fingerprint = createHash("sha256").update(JSON.stringify(request)).digest("base64url");
|
|
3208
|
-
if (this.resolvedToolApprovalIds.has(request.id)) {
|
|
3209
|
-
this.handleRuntimeFailure("OMP reused a resolved tool approval identifier");
|
|
3210
|
-
return;
|
|
3211
|
-
}
|
|
3212
|
-
for (const pending of [
|
|
3213
|
-
...this.pendingToolPermissions.values(),
|
|
3214
|
-
...this.inFlightToolPermissions.values(),
|
|
3215
|
-
]) {
|
|
3216
|
-
if (pending.nativeId !== request.id) continue;
|
|
3217
|
-
if (pending.fingerprint !== fingerprint || pending.toolCallId !== request.toolCallId) {
|
|
3218
|
-
this.handleRuntimeFailure("OMP changed a pending tool approval request");
|
|
3219
|
-
}
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
const pendingCount =
|
|
3223
|
-
this.pendingPermissions.size +
|
|
3224
|
-
this.inFlightPermissions.size +
|
|
3225
|
-
this.pendingToolPermissions.size +
|
|
3226
|
-
this.inFlightToolPermissions.size;
|
|
3227
|
-
if (pendingCount >= MAX_PENDING_PERMISSIONS) {
|
|
3228
|
-
this.resolvedToolApprovalIds.add(request.id);
|
|
3229
|
-
void this.runtime
|
|
3230
|
-
.respondToToolApproval({
|
|
3231
|
-
type: "tool_approval_response",
|
|
3232
|
-
id: request.id,
|
|
3233
|
-
toolCallId: request.toolCallId,
|
|
3234
|
-
cancelled: true,
|
|
3235
|
-
})
|
|
3236
|
-
.catch(() => this.handleRuntimeFailure());
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
|
|
3240
|
-
this.permissionSequence += 1;
|
|
3241
|
-
const permissionId = `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
|
|
3242
|
-
const detail = this.toolPermissionDetail(request);
|
|
3243
|
-
const filteredInput = this.dataFilter.json(request.input, 8 * 1024, 32 * 1024);
|
|
3244
|
-
const input =
|
|
3245
|
-
filteredInput && typeof filteredInput === "object" && !Array.isArray(filteredInput)
|
|
3246
|
-
? filteredInput
|
|
3247
|
-
: {};
|
|
3248
|
-
const descriptionParts = [
|
|
3249
|
-
request.detail.reason,
|
|
3250
|
-
...request.detail.lines,
|
|
3251
|
-
...(request.detail.providerSafetyChecks ?? []),
|
|
3252
|
-
].filter((part): part is string => Boolean(part));
|
|
3253
|
-
const publicRequest = {
|
|
3254
|
-
id: permissionId,
|
|
3255
|
-
name: `omp.${this.dataFilter.text(request.toolName, 256)}`,
|
|
3256
|
-
kind: "tool" as const,
|
|
3257
|
-
title: `Allow ${this.dataFilter.text(request.toolName, 256)}?`,
|
|
3258
|
-
...(descriptionParts.length > 0
|
|
3259
|
-
? { description: this.dataFilter.text(descriptionParts.join("\n"), 16 * 1024) }
|
|
3260
|
-
: {}),
|
|
3261
|
-
input: {
|
|
3262
|
-
...input,
|
|
3263
|
-
identity: this.dataFilter.json(request.identity, 20 * 1024, 32 * 1024),
|
|
3264
|
-
tier: request.tier,
|
|
3265
|
-
toolCallId: request.toolCallId,
|
|
3266
|
-
},
|
|
3267
|
-
...(detail ? { detail } : {}),
|
|
3268
|
-
actions: [
|
|
3269
|
-
{ id: "allow", label: "Allow", behavior: "allow" as const, variant: "primary" as const },
|
|
3270
|
-
{ id: "deny", label: "Deny", behavior: "deny" as const, variant: "danger" as const },
|
|
3271
|
-
],
|
|
3272
|
-
metadata: {
|
|
3273
|
-
tier: request.tier,
|
|
3274
|
-
redacted: request.detail.redacted,
|
|
3275
|
-
truncated: request.detail.truncated,
|
|
3276
|
-
redactedFields: request.detail.redactedFields,
|
|
3277
|
-
truncatedFields: request.detail.truncatedFields,
|
|
3278
|
-
},
|
|
3279
|
-
};
|
|
3280
|
-
const pending: PendingToolPermission = {
|
|
3281
|
-
nativeId: request.id,
|
|
3282
|
-
toolCallId: request.toolCallId,
|
|
3283
|
-
fingerprint,
|
|
3284
|
-
retainedBytes: boundedJsonBytes(
|
|
3285
|
-
publicRequest,
|
|
3286
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3287
|
-
512,
|
|
3288
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3289
|
-
4_096,
|
|
3290
|
-
),
|
|
3291
|
-
generation: this.generation,
|
|
3292
|
-
runtime: this.runtime,
|
|
3293
|
-
...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
|
|
3294
|
-
...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
|
|
3295
|
-
};
|
|
3296
|
-
let retainedBytes = pending.retainedBytes;
|
|
3297
|
-
for (const item of this.pendingPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3298
|
-
for (const item of this.inFlightPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3299
|
-
for (const item of this.pendingToolPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3300
|
-
for (const item of this.inFlightToolPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3301
|
-
if (
|
|
3302
|
-
pending.retainedBytes === Number.POSITIVE_INFINITY ||
|
|
3303
|
-
retainedBytes > MAX_PENDING_PERMISSION_BYTES
|
|
3304
|
-
) {
|
|
3305
|
-
void this.runtime
|
|
3306
|
-
.respondToToolApproval({
|
|
3307
|
-
type: "tool_approval_response",
|
|
3308
|
-
id: request.id,
|
|
3309
|
-
toolCallId: request.toolCallId,
|
|
3310
|
-
cancelled: true,
|
|
3311
|
-
})
|
|
3312
|
-
.catch(() => this.handleRuntimeFailure());
|
|
3313
|
-
this.resolvedToolApprovalIds.add(request.id);
|
|
3314
|
-
return;
|
|
3315
|
-
}
|
|
3316
|
-
this.pendingToolPermissions.set(permissionId, pending);
|
|
3317
|
-
this.armToolPermissionTimeout(permissionId, pending);
|
|
3318
|
-
this.projector.markAskPermissionRendered();
|
|
3319
|
-
if (this.activeTurn) {
|
|
3320
|
-
this.activeTurn.awaitingPermissionEvidence = true;
|
|
3321
|
-
this.markAgentEvidence(this.activeTurn);
|
|
3322
|
-
}
|
|
3323
|
-
this.emit({ type: "session.permission", sessionId: this.id, request: publicRequest });
|
|
3324
|
-
}
|
|
3325
|
-
|
|
3326
|
-
private cancelToolPermission(request: OmpToolApprovalCancel): void {
|
|
3327
|
-
for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
|
|
3328
|
-
for (const [permissionId, pending] of permissions) {
|
|
3329
|
-
if (pending.nativeId !== request.targetId) continue;
|
|
3330
|
-
if (pending.toolCallId !== request.toolCallId) {
|
|
3331
|
-
this.handleRuntimeFailure("OMP tool approval cancellation did not match its tool call");
|
|
3332
|
-
return;
|
|
3333
|
-
}
|
|
3334
|
-
permissions.delete(permissionId);
|
|
3335
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3336
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
3337
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3338
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3339
|
-
return;
|
|
3340
|
-
}
|
|
3341
|
-
}
|
|
3342
|
-
if (!this.resolvedToolApprovalIds.has(request.targetId)) {
|
|
3343
|
-
this.handleRuntimeFailure("OMP canceled an unknown tool approval request");
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
|
|
3347
|
-
private publishPermission(request: OmpQuestionRequest): void {
|
|
3348
|
-
if (request.method === "select" && !request.options?.length) {
|
|
3349
|
-
this.handleRuntimeFailure();
|
|
3350
|
-
return;
|
|
3351
|
-
}
|
|
3352
|
-
const fingerprint = permissionFingerprint(request);
|
|
3353
|
-
let existingId: string | undefined;
|
|
3354
|
-
let existingPending: PendingPermission | undefined;
|
|
3355
|
-
for (const [permissionId, pending] of this.pendingPermissions) {
|
|
3356
|
-
if (pending.nativeId !== request.id) continue;
|
|
3357
|
-
if (pending.fingerprint === fingerprint) {
|
|
3358
|
-
existingId = permissionId;
|
|
3359
|
-
existingPending = pending;
|
|
3360
|
-
} else {
|
|
3361
|
-
this.pendingPermissions.delete(permissionId);
|
|
3362
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3363
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3364
|
-
}
|
|
3365
|
-
break;
|
|
3366
|
-
}
|
|
3367
|
-
if (!existingId) {
|
|
3368
|
-
for (const pending of this.inFlightPermissions.values()) {
|
|
3369
|
-
if (pending.nativeId !== request.id) continue;
|
|
3370
|
-
if (pending.fingerprint !== fingerprint) this.handleRuntimeFailure();
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
if (
|
|
3375
|
-
!existingId &&
|
|
3376
|
-
this.pendingPermissions.size +
|
|
3377
|
-
this.inFlightPermissions.size +
|
|
3378
|
-
this.pendingToolPermissions.size +
|
|
3379
|
-
this.inFlightToolPermissions.size >=
|
|
3380
|
-
MAX_PENDING_PERMISSIONS
|
|
3381
|
-
) {
|
|
3382
|
-
this.rejectPermissionRequest(request, "Too many OMP questions are already pending");
|
|
3383
|
-
return;
|
|
3384
|
-
}
|
|
3385
|
-
if (existingPending?.timer !== undefined) this.scheduler.clear(existingPending.timer);
|
|
3386
|
-
if (!existingId) this.permissionSequence += 1;
|
|
3387
|
-
const id =
|
|
3388
|
-
existingId ?? `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
|
|
3389
|
-
const header = this.dataFilter.text(request.title ?? "OMP question", 4_096);
|
|
3390
|
-
const freeformSentinel =
|
|
3391
|
-
request.method === "select" && request.options.includes(OMP_ASK_USER_FREEFORM_SENTINEL)
|
|
3392
|
-
? OMP_ASK_USER_FREEFORM_SENTINEL
|
|
3393
|
-
: undefined;
|
|
3394
|
-
const optionValues = new Map<string, string>();
|
|
3395
|
-
const displayValues = new Map<string, string>();
|
|
3396
|
-
const usedOptionLabels = new Set<string>();
|
|
3397
|
-
const optionDetails = request.method === "select" ? request.optionDetails : undefined;
|
|
3398
|
-
const selectableOptions =
|
|
3399
|
-
request.method === "select"
|
|
3400
|
-
? request.options.flatMap((nativeValue, index) =>
|
|
3401
|
-
nativeValue === freeformSentinel ? [] : [{ nativeValue, index }],
|
|
3402
|
-
)
|
|
3403
|
-
: [];
|
|
3404
|
-
const options =
|
|
3405
|
-
request.method === "select"
|
|
3406
|
-
? selectableOptions.map(({ nativeValue, index }) => {
|
|
3407
|
-
const baseLabel = this.dataFilter.text(nativeValue, 4_096);
|
|
3408
|
-
let label = baseLabel;
|
|
3409
|
-
let suffix = 2;
|
|
3410
|
-
while (usedOptionLabels.has(label)) {
|
|
3411
|
-
label = `${baseLabel} (${suffix})`;
|
|
3412
|
-
suffix += 1;
|
|
3413
|
-
}
|
|
3414
|
-
usedOptionLabels.add(label);
|
|
3415
|
-
const value = `${id}:option:${index}`;
|
|
3416
|
-
optionValues.set(value, nativeValue);
|
|
3417
|
-
displayValues.set(label, nativeValue);
|
|
3418
|
-
return {
|
|
3419
|
-
label,
|
|
3420
|
-
value,
|
|
3421
|
-
...(optionDetails?.[index]?.description
|
|
3422
|
-
? {
|
|
3423
|
-
description: this.dataFilter.text(
|
|
3424
|
-
optionDetails[index]?.description ?? "",
|
|
3425
|
-
16_384,
|
|
3426
|
-
),
|
|
3427
|
-
}
|
|
3428
|
-
: {}),
|
|
3429
|
-
};
|
|
3430
|
-
})
|
|
3431
|
-
: undefined;
|
|
3432
|
-
const questionOptions =
|
|
3433
|
-
request.method === "confirm" ? [{ label: "Yes" }, { label: "No" }] : (options ?? []);
|
|
3434
|
-
const actions =
|
|
3435
|
-
request.method === "select"
|
|
3436
|
-
? [
|
|
3437
|
-
...(options ?? []).map((option) => ({
|
|
3438
|
-
id: option.value,
|
|
3439
|
-
label: option.label,
|
|
3440
|
-
behavior: "allow" as const,
|
|
3441
|
-
variant: "secondary" as const,
|
|
3442
|
-
})),
|
|
3443
|
-
{
|
|
3444
|
-
id: "cancel",
|
|
3445
|
-
label: "Cancel",
|
|
3446
|
-
behavior: "deny" as const,
|
|
3447
|
-
variant: "secondary" as const,
|
|
3448
|
-
},
|
|
3449
|
-
]
|
|
3450
|
-
: [
|
|
3451
|
-
{
|
|
3452
|
-
id: "submit",
|
|
3453
|
-
label: request.method === "confirm" ? "Confirm" : "Submit",
|
|
3454
|
-
behavior: "allow" as const,
|
|
3455
|
-
variant: "primary" as const,
|
|
3456
|
-
},
|
|
3457
|
-
{
|
|
3458
|
-
id: "cancel",
|
|
3459
|
-
label: "Cancel",
|
|
3460
|
-
behavior: "deny" as const,
|
|
3461
|
-
variant: "secondary" as const,
|
|
3462
|
-
},
|
|
3463
|
-
];
|
|
3464
|
-
const pending: PendingPermission = {
|
|
3465
|
-
nativeId: request.id,
|
|
3466
|
-
header,
|
|
3467
|
-
fingerprint,
|
|
3468
|
-
optionValues,
|
|
3469
|
-
actionBehaviors: new Map(actions.map((action) => [action.id, action.behavior])),
|
|
3470
|
-
displayValues,
|
|
3471
|
-
generation: this.generation,
|
|
3472
|
-
runtime: this.runtime,
|
|
3473
|
-
request,
|
|
3474
|
-
retainedBytes: boundedJsonBytes(
|
|
3475
|
-
{ request, header, options: questionOptions, actions },
|
|
3476
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3477
|
-
512,
|
|
3478
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3479
|
-
4_096,
|
|
3480
|
-
),
|
|
3481
|
-
...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
|
|
3482
|
-
...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
|
|
3483
|
-
...(freeformSentinel ? { freeformSentinel } : {}),
|
|
3484
|
-
};
|
|
3485
|
-
let retainedPermissionBytes = pending.retainedBytes;
|
|
3486
|
-
for (const [permissionId, retained] of this.pendingPermissions) {
|
|
3487
|
-
if (permissionId !== existingId) retainedPermissionBytes += retained.retainedBytes;
|
|
3488
|
-
}
|
|
3489
|
-
for (const retained of this.inFlightPermissions.values()) {
|
|
3490
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
3491
|
-
}
|
|
3492
|
-
for (const retained of this.pendingToolPermissions.values()) {
|
|
3493
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
3494
|
-
}
|
|
3495
|
-
for (const retained of this.inFlightToolPermissions.values()) {
|
|
3496
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
3497
|
-
}
|
|
3498
|
-
if (
|
|
3499
|
-
pending.retainedBytes === Number.POSITIVE_INFINITY ||
|
|
3500
|
-
retainedPermissionBytes > MAX_PENDING_PERMISSION_BYTES
|
|
3501
|
-
) {
|
|
3502
|
-
if (existingId) {
|
|
3503
|
-
this.pendingPermissions.delete(existingId);
|
|
3504
|
-
this.emit({
|
|
3505
|
-
type: "session.permission_resolved",
|
|
3506
|
-
sessionId: this.id,
|
|
3507
|
-
permissionId: existingId,
|
|
3508
|
-
});
|
|
3509
|
-
}
|
|
3510
|
-
this.rejectPermissionRequest(request, "OMP question data exceeded the pending input budget");
|
|
3511
|
-
return;
|
|
3512
|
-
}
|
|
3513
|
-
this.pendingPermissions.set(id, pending);
|
|
3514
|
-
this.armPermissionTimeout(id, pending);
|
|
3515
|
-
this.projector.markAskPermissionRendered();
|
|
3516
|
-
if (this.activeTurn) {
|
|
3517
|
-
this.activeTurn.awaitingPermissionEvidence = true;
|
|
3518
|
-
this.markAgentEvidence(this.activeTurn);
|
|
3519
|
-
}
|
|
3520
|
-
this.emit({
|
|
3521
|
-
type: "session.permission",
|
|
3522
|
-
sessionId: this.id,
|
|
3523
|
-
request: {
|
|
3524
|
-
id,
|
|
3525
|
-
name: `omp.${request.method}`,
|
|
3526
|
-
kind: "question",
|
|
3527
|
-
title: header,
|
|
3528
|
-
...(request.method === "confirm"
|
|
3529
|
-
? { description: this.dataFilter.text(request.message, 64 * 1024) }
|
|
3530
|
-
: {}),
|
|
3531
|
-
input: {
|
|
3532
|
-
questions: [
|
|
3533
|
-
{
|
|
3534
|
-
header,
|
|
3535
|
-
question: this.dataFilter.text(
|
|
3536
|
-
request.method === "confirm" ? request.message : request.title,
|
|
3537
|
-
64 * 1024,
|
|
3538
|
-
),
|
|
3539
|
-
options: questionOptions,
|
|
3540
|
-
multiSelect: false,
|
|
3541
|
-
...(freeformSentinel ? { allowOther: true } : {}),
|
|
3542
|
-
...(request.method === "input" && request.placeholder
|
|
3543
|
-
? { placeholder: this.dataFilter.text(request.placeholder, 4_096) }
|
|
3544
|
-
: {}),
|
|
3545
|
-
...((request.method === "input" || request.method === "editor") && request.prefill
|
|
3546
|
-
? { prefill: this.dataFilter.text(request.prefill) }
|
|
3547
|
-
: {}),
|
|
3548
|
-
},
|
|
3549
|
-
],
|
|
3550
|
-
},
|
|
3551
|
-
actions,
|
|
3552
|
-
},
|
|
3553
|
-
});
|
|
3554
|
-
}
|
|
3555
|
-
|
|
3556
|
-
private rejectPermissionRequest(request: OmpQuestionRequest, description: string): void {
|
|
3557
|
-
this.emit({
|
|
3558
|
-
type: "session.notice",
|
|
3559
|
-
sessionId: this.id,
|
|
3560
|
-
notice: {
|
|
3561
|
-
id: `omp:permission-rejected:${this.permissionSequence + 1}`,
|
|
3562
|
-
severity: "warning",
|
|
3563
|
-
title: "OMP question canceled",
|
|
3564
|
-
description,
|
|
3565
|
-
},
|
|
3566
|
-
});
|
|
3567
|
-
void this.runtime
|
|
3568
|
-
.respondToExtensionUi({ type: "extension_ui_response", id: request.id, cancelled: true })
|
|
3569
|
-
.catch(() => this.handleRuntimeFailure());
|
|
3570
|
-
}
|
|
3571
|
-
|
|
3572
|
-
private freeformSelection(
|
|
3573
|
-
pending: PendingPermission,
|
|
3574
|
-
response: ProviderPermissionResponse,
|
|
3575
|
-
): string | undefined {
|
|
3576
|
-
if (
|
|
3577
|
-
pending.request.method !== "select" ||
|
|
3578
|
-
!pending.freeformSentinel ||
|
|
3579
|
-
response.behavior !== "allow" ||
|
|
3580
|
-
response.selectedActionId !== undefined
|
|
3581
|
-
) {
|
|
3582
|
-
return;
|
|
3583
|
-
}
|
|
3584
|
-
const answers = response.updatedInput?.answers;
|
|
3585
|
-
const answer =
|
|
3586
|
-
answers && typeof answers === "object" && !Array.isArray(answers)
|
|
3587
|
-
? answers[pending.header]
|
|
3588
|
-
: undefined;
|
|
3589
|
-
const value = Array.isArray(answer) ? answer[0] : answer;
|
|
3590
|
-
if (
|
|
3591
|
-
typeof value !== "string" ||
|
|
3592
|
-
pending.optionValues.has(value) ||
|
|
3593
|
-
pending.displayValues.has(value)
|
|
3594
|
-
) {
|
|
3595
|
-
return;
|
|
3596
|
-
}
|
|
3597
|
-
if (
|
|
3598
|
-
value === pending.freeformSentinel ||
|
|
3599
|
-
value.trim().length === 0 ||
|
|
3600
|
-
value.includes("\0") ||
|
|
3601
|
-
utf8Bytes(value) > MAX_FREEFORM_RESPONSE_BYTES
|
|
3602
|
-
) {
|
|
3603
|
-
throw new OmpPublicError("OMP freeform response is invalid");
|
|
3604
|
-
}
|
|
3605
|
-
return value;
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
private extensionUiResponse(
|
|
3609
|
-
pending: PendingPermission,
|
|
3610
|
-
response: ProviderPermissionResponse,
|
|
3611
|
-
): OmpExtensionUiResponse {
|
|
3612
|
-
if (response.selectedActionId !== undefined) {
|
|
3613
|
-
const expectedBehavior = pending.actionBehaviors.get(response.selectedActionId);
|
|
3614
|
-
if (expectedBehavior === undefined || expectedBehavior !== response.behavior) {
|
|
3615
|
-
throw new OmpPublicError("OMP permission action is invalid");
|
|
3616
|
-
}
|
|
3617
|
-
}
|
|
3618
|
-
const { nativeId, request, header } = pending;
|
|
3619
|
-
if (request.method === "confirm") {
|
|
3620
|
-
if (response.behavior === "deny") {
|
|
3621
|
-
return { type: "extension_ui_response", id: nativeId, confirmed: false };
|
|
3622
|
-
}
|
|
3623
|
-
const answers = response.updatedInput?.answers;
|
|
3624
|
-
const answer =
|
|
3625
|
-
answers && typeof answers === "object" && !Array.isArray(answers)
|
|
3626
|
-
? answers[header]
|
|
3627
|
-
: undefined;
|
|
3628
|
-
return {
|
|
3629
|
-
type: "extension_ui_response",
|
|
3630
|
-
id: nativeId,
|
|
3631
|
-
confirmed: typeof answer === "string" ? /^yes$/iu.test(answer.trim()) : true,
|
|
3632
|
-
};
|
|
3633
|
-
}
|
|
3634
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3635
|
-
if (response.behavior === "deny") {
|
|
3636
|
-
return { type: "extension_ui_response", id: nativeId, cancelled: true };
|
|
3637
|
-
}
|
|
3638
|
-
const selectedValue = response.selectedActionId
|
|
3639
|
-
? pending.optionValues.get(response.selectedActionId)
|
|
3640
|
-
: undefined;
|
|
3641
|
-
if (selectedValue !== undefined) {
|
|
3642
|
-
return { type: "extension_ui_response", id: nativeId, value: selectedValue };
|
|
3643
|
-
}
|
|
3644
|
-
const answers = response.updatedInput?.answers;
|
|
3645
|
-
if (!answers || typeof answers !== "object" || Array.isArray(answers)) {
|
|
3646
|
-
throw new OmpPublicError("OMP question response requires answers");
|
|
3647
|
-
}
|
|
3648
|
-
const answer = answers[header];
|
|
3649
|
-
const publicValue = Array.isArray(answer) ? answer[0] : answer;
|
|
3650
|
-
if (typeof publicValue !== "string") {
|
|
3651
|
-
throw new OmpPublicError("OMP question response is invalid");
|
|
3652
|
-
}
|
|
3653
|
-
if (
|
|
3654
|
-
request.method === "select" &&
|
|
3655
|
-
pending.freeformSentinel &&
|
|
3656
|
-
publicValue !== pending.freeformSentinel &&
|
|
3657
|
-
!pending.displayValues.has(publicValue) &&
|
|
3658
|
-
!pending.optionValues.has(publicValue)
|
|
3659
|
-
) {
|
|
3660
|
-
if (
|
|
3661
|
-
publicValue.trim().length === 0 ||
|
|
3662
|
-
publicValue.includes("\0") ||
|
|
3663
|
-
utf8Bytes(publicValue) > MAX_FREEFORM_RESPONSE_BYTES
|
|
3664
|
-
) {
|
|
3665
|
-
throw new OmpPublicError("OMP freeform response is invalid");
|
|
3666
|
-
}
|
|
3667
|
-
this.pendingFreeformSelection = {
|
|
3668
|
-
value: publicValue,
|
|
3669
|
-
nativeSelectId: pending.nativeId,
|
|
3670
|
-
generation: pending.generation,
|
|
3671
|
-
runtime: pending.runtime,
|
|
3672
|
-
...(pending.turnId ? { turnId: pending.turnId } : {}),
|
|
3673
|
-
};
|
|
3674
|
-
return {
|
|
3675
|
-
type: "extension_ui_response",
|
|
3676
|
-
id: nativeId,
|
|
3677
|
-
value: pending.freeformSentinel,
|
|
3678
|
-
};
|
|
3679
|
-
}
|
|
3680
|
-
if (request.method === "select") {
|
|
3681
|
-
const mapped =
|
|
3682
|
-
pending.optionValues.get(publicValue) ?? pending.displayValues.get(publicValue);
|
|
3683
|
-
if (mapped === undefined) {
|
|
3684
|
-
throw new OmpPublicError("OMP selection response is invalid");
|
|
3685
|
-
}
|
|
3686
|
-
return { type: "extension_ui_response", id: nativeId, value: mapped };
|
|
3687
|
-
}
|
|
3688
|
-
return { type: "extension_ui_response", id: nativeId, value: publicValue };
|
|
3689
|
-
}
|
|
3690
|
-
|
|
3691
|
-
private resolvePermissionByNativeId(nativeId: string): void {
|
|
3692
|
-
for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
|
|
3693
|
-
for (const [permissionId, pending] of permissions) {
|
|
3694
|
-
if (pending.nativeId !== nativeId) continue;
|
|
3695
|
-
permissions.delete(permissionId);
|
|
3696
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3697
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3698
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3699
|
-
return;
|
|
3700
|
-
}
|
|
3701
|
-
}
|
|
3702
|
-
}
|
|
3703
|
-
|
|
3704
|
-
private resolveTurnPermissions(turnId: string): void {
|
|
3705
|
-
if (this.pendingFreeformSelection?.turnId === turnId) this.pendingFreeformSelection = null;
|
|
3706
|
-
this.resolvePermissions((pending) => pending.turnId === turnId, true);
|
|
3707
|
-
this.resolveToolPermissions((pending) => pending.turnId === turnId, true);
|
|
3708
|
-
}
|
|
3709
|
-
private resolveAllPermissions(cancelNative = false): void {
|
|
3710
|
-
this.pendingFreeformSelection = null;
|
|
3711
|
-
this.resolvePermissions(() => true, cancelNative);
|
|
3712
|
-
this.resolveToolPermissions(() => true, cancelNative);
|
|
3713
|
-
}
|
|
3714
|
-
|
|
3715
|
-
private resolvePermissions(
|
|
3716
|
-
matches: (pending: PendingPermission) => boolean,
|
|
3717
|
-
cancelNative: boolean,
|
|
3718
|
-
): void {
|
|
3719
|
-
const permissionIds = new Set<string>();
|
|
3720
|
-
for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
|
|
3721
|
-
for (const [permissionId, pending] of permissions) {
|
|
3722
|
-
if (!matches(pending)) continue;
|
|
3723
|
-
permissions.delete(permissionId);
|
|
3724
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3725
|
-
permissionIds.add(permissionId);
|
|
3726
|
-
if (cancelNative && permissions === this.pendingPermissions) {
|
|
3727
|
-
void pending.runtime
|
|
3728
|
-
.respondToExtensionUi({
|
|
3729
|
-
type: "extension_ui_response",
|
|
3730
|
-
id: pending.nativeId,
|
|
3731
|
-
cancelled: true,
|
|
3732
|
-
})
|
|
3733
|
-
.catch(() => {
|
|
3734
|
-
if (!this.closed && !this.runtimeDead) {
|
|
3735
|
-
this.invalidateRuntime("OMP permission cancellation failed");
|
|
3736
|
-
}
|
|
3737
|
-
});
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
}
|
|
3741
|
-
for (const permissionId of permissionIds) {
|
|
3742
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3743
|
-
}
|
|
3744
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3745
|
-
}
|
|
3746
|
-
private resolveToolPermissions(
|
|
3747
|
-
matches: (pending: PendingToolPermission) => boolean,
|
|
3748
|
-
cancelNative: boolean,
|
|
3749
|
-
): void {
|
|
3750
|
-
const permissionIds = new Set<string>();
|
|
3751
|
-
for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
|
|
3752
|
-
for (const [permissionId, pending] of permissions) {
|
|
3753
|
-
if (!matches(pending)) continue;
|
|
3754
|
-
permissions.delete(permissionId);
|
|
3755
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3756
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
3757
|
-
permissionIds.add(permissionId);
|
|
3758
|
-
if (cancelNative && permissions === this.pendingToolPermissions) {
|
|
3759
|
-
void pending.runtime
|
|
3760
|
-
.respondToToolApproval({
|
|
3761
|
-
type: "tool_approval_response",
|
|
3762
|
-
id: pending.nativeId,
|
|
3763
|
-
toolCallId: pending.toolCallId,
|
|
3764
|
-
cancelled: true,
|
|
3765
|
-
})
|
|
3766
|
-
.catch(() => {
|
|
3767
|
-
if (!this.closed && !this.runtimeDead) {
|
|
3768
|
-
this.invalidateRuntime("OMP tool permission cancellation failed");
|
|
3769
|
-
}
|
|
3770
|
-
});
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
for (const permissionId of permissionIds) {
|
|
3775
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3776
|
-
}
|
|
3777
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3778
|
-
}
|
|
3779
|
-
|
|
3780
|
-
private armToolPermissionTimeout(permissionId: string, pending: PendingToolPermission): void {
|
|
3781
|
-
if (pending.expiresAt === undefined) return;
|
|
3782
|
-
const remainingMs = Math.max(0, pending.expiresAt - Date.now());
|
|
3783
|
-
pending.timer = this.scheduler.set(() => {
|
|
3784
|
-
if (this.pendingToolPermissions.get(permissionId) !== pending) return;
|
|
3785
|
-
this.pendingToolPermissions.delete(permissionId);
|
|
3786
|
-
this.inFlightToolPermissions.set(permissionId, pending);
|
|
3787
|
-
void pending.runtime
|
|
3788
|
-
.respondToToolApproval({
|
|
3789
|
-
type: "tool_approval_response",
|
|
3790
|
-
id: pending.nativeId,
|
|
3791
|
-
toolCallId: pending.toolCallId,
|
|
3792
|
-
cancelled: true,
|
|
3793
|
-
timedOut: true,
|
|
3794
|
-
})
|
|
3795
|
-
.then(
|
|
3796
|
-
() => {
|
|
3797
|
-
if (this.inFlightToolPermissions.get(permissionId) !== pending) return;
|
|
3798
|
-
this.inFlightToolPermissions.delete(permissionId);
|
|
3799
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
3800
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3801
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3802
|
-
},
|
|
3803
|
-
() => this.handleRuntimeFailure(),
|
|
3804
|
-
);
|
|
3805
|
-
}, remainingMs);
|
|
3806
|
-
}
|
|
3807
|
-
|
|
3808
|
-
private armPermissionTimeout(permissionId: string, pending: PendingPermission): void {
|
|
3809
|
-
if (pending.expiresAt === undefined) return;
|
|
3810
|
-
const remainingMs = Math.max(0, pending.expiresAt - Date.now());
|
|
3811
|
-
pending.timer = this.scheduler.set(() => {
|
|
3812
|
-
if (this.pendingPermissions.get(permissionId) !== pending) return;
|
|
3813
|
-
this.pendingPermissions.delete(permissionId);
|
|
3814
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
3815
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3816
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3817
|
-
return;
|
|
3818
|
-
}
|
|
3819
|
-
this.inFlightPermissions.set(permissionId, pending);
|
|
3820
|
-
void pending.runtime
|
|
3821
|
-
.respondToExtensionUi({
|
|
3822
|
-
type: "extension_ui_response",
|
|
3823
|
-
id: pending.nativeId,
|
|
3824
|
-
cancelled: true,
|
|
3825
|
-
timedOut: true,
|
|
3826
|
-
})
|
|
3827
|
-
.then(
|
|
3828
|
-
() => {
|
|
3829
|
-
if (this.inFlightPermissions.get(permissionId) !== pending) return;
|
|
3830
|
-
this.inFlightPermissions.delete(permissionId);
|
|
3831
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3832
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3833
|
-
},
|
|
3834
|
-
() => this.handleRuntimeFailure(),
|
|
3835
|
-
);
|
|
3836
|
-
}, remainingMs);
|
|
3837
|
-
}
|
|
3838
|
-
|
|
3839
|
-
private permissionOwnerIsCurrent(pending: PendingPermission | PendingToolPermission): boolean {
|
|
3840
|
-
if (
|
|
3841
|
-
this.closed ||
|
|
3842
|
-
this.runtimeDead ||
|
|
3843
|
-
pending.generation !== this.generation ||
|
|
3844
|
-
pending.runtime !== this.runtime
|
|
3845
|
-
) {
|
|
3846
|
-
return false;
|
|
3847
|
-
}
|
|
3848
|
-
if (pending.turnId === undefined) return true;
|
|
3849
|
-
return this.activeTurn?.turnId === pending.turnId && !this.activeTurn.terminal;
|
|
3850
|
-
}
|
|
3851
2706
|
|
|
3852
2707
|
private hasTerminalConflict(turn: ActiveTurn, includeChildren = true): boolean {
|
|
3853
|
-
const ownsTurn = (pending: PendingPermission | PendingToolPermission) =>
|
|
3854
|
-
pending.turnId === turn.turnId;
|
|
3855
2708
|
return (
|
|
3856
2709
|
turn.awaitingPermissionEvidence ||
|
|
3857
|
-
|
|
3858
|
-
[...this.inFlightPermissions.values()].some(ownsTurn) ||
|
|
3859
|
-
[...this.pendingToolPermissions.values()].some(ownsTurn) ||
|
|
3860
|
-
[...this.inFlightToolPermissions.values()].some(ownsTurn) ||
|
|
2710
|
+
this.permissions.hasForTurn(turn.turnId) ||
|
|
3861
2711
|
turn.activeToolCallIds.size > 0 ||
|
|
3862
2712
|
turn.steersInFlight > 0 ||
|
|
3863
2713
|
(includeChildren && Boolean(this.subsessions?.hasActiveChildren()))
|
|
@@ -3993,7 +2843,7 @@ export class OmpProviderSession {
|
|
|
3993
2843
|
await Promise.allSettled(turn.userLookups);
|
|
3994
2844
|
this.refreshAmbiguousTerminal(turn);
|
|
3995
2845
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
3996
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
2846
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3997
2847
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
3998
2848
|
if (!state) {
|
|
3999
2849
|
turn.operationalTerminalStage = "unresolved";
|
|
@@ -4023,10 +2873,7 @@ export class OmpProviderSession {
|
|
|
4023
2873
|
) {
|
|
4024
2874
|
return;
|
|
4025
2875
|
}
|
|
4026
|
-
|
|
4027
|
-
if (this.usageSample?.turn === turn && this.usageSample.sequence < turn.usageSampleFloor) {
|
|
4028
|
-
this.usageSample = null;
|
|
4029
|
-
}
|
|
2876
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
4030
2877
|
this.publishPendingUsers(turn);
|
|
4031
2878
|
await this.finishTurn(turn, "completed", undefined, false, false, true);
|
|
4032
2879
|
}
|
|
@@ -4055,7 +2902,7 @@ export class OmpProviderSession {
|
|
|
4055
2902
|
this.beginTerminalization(turn, replacement);
|
|
4056
2903
|
return;
|
|
4057
2904
|
}
|
|
4058
|
-
this.
|
|
2905
|
+
this.usage.poll(turn);
|
|
4059
2906
|
}
|
|
4060
2907
|
|
|
4061
2908
|
private beginTerminalization(turn: ActiveTurn, candidate: TerminalCandidate): void {
|
|
@@ -4069,11 +2916,8 @@ export class OmpProviderSession {
|
|
|
4069
2916
|
}
|
|
4070
2917
|
turn.terminalizing = true;
|
|
4071
2918
|
this.cancelAmbiguousTerminal(turn);
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
this.usageSample = null;
|
|
4075
|
-
}
|
|
4076
|
-
this.stopUsagePoll(turn);
|
|
2919
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
2920
|
+
this.usage.stopPoll(turn);
|
|
4077
2921
|
turn.agentEndDeadlineTimer = this.scheduler.set(() => {
|
|
4078
2922
|
turn.agentEndDeadlineTimer = undefined;
|
|
4079
2923
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
@@ -4165,14 +3009,14 @@ export class OmpProviderSession {
|
|
|
4165
3009
|
) {
|
|
4166
3010
|
this.resetAgentEndProbe(turn);
|
|
4167
3011
|
turn.deferredAgentEnd = candidate;
|
|
4168
|
-
this.
|
|
3012
|
+
this.usage.poll(turn);
|
|
4169
3013
|
return;
|
|
4170
3014
|
}
|
|
4171
3015
|
if (turn.interrupted) {
|
|
4172
3016
|
await this.completeAgentEnd(turn, candidate.event);
|
|
4173
3017
|
return;
|
|
4174
3018
|
}
|
|
4175
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3019
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4176
3020
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
4177
3021
|
if (!turn.interrupted && this.subsessions?.hasActiveChildren()) {
|
|
4178
3022
|
this.resetAgentEndProbe(turn);
|
|
@@ -4185,7 +3029,7 @@ export class OmpProviderSession {
|
|
|
4185
3029
|
) {
|
|
4186
3030
|
this.resetAgentEndProbe(turn);
|
|
4187
3031
|
turn.deferredAgentEnd = candidate;
|
|
4188
|
-
this.
|
|
3032
|
+
this.usage.poll(turn);
|
|
4189
3033
|
return;
|
|
4190
3034
|
}
|
|
4191
3035
|
if (state) {
|
|
@@ -4227,7 +3071,7 @@ export class OmpProviderSession {
|
|
|
4227
3071
|
!turn.interrupted &&
|
|
4228
3072
|
this.activeTurn === turn
|
|
4229
3073
|
) {
|
|
4230
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3074
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4231
3075
|
if (
|
|
4232
3076
|
turn.terminal ||
|
|
4233
3077
|
this.activeTurn !== turn ||
|
|
@@ -4305,9 +3149,9 @@ export class OmpProviderSession {
|
|
|
4305
3149
|
turn.steerReady.resolve();
|
|
4306
3150
|
turn.starting = false;
|
|
4307
3151
|
turn.terminal = true;
|
|
4308
|
-
this.
|
|
3152
|
+
this.usage.stopPoll(turn);
|
|
4309
3153
|
this.cancelAmbiguousTerminal(turn);
|
|
4310
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
3154
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
4311
3155
|
this.projector.finishTurn(turn.turnId);
|
|
4312
3156
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
4313
3157
|
}
|
|
@@ -4332,7 +3176,7 @@ export class OmpProviderSession {
|
|
|
4332
3176
|
turn.starting = false;
|
|
4333
3177
|
turn.steerReady.resolve();
|
|
4334
3178
|
this.emit({ type: "session.turn", sessionId: this.id, turnId: turn.turnId, state: "started" });
|
|
4335
|
-
if (pollUsage) this.
|
|
3179
|
+
if (pollUsage) this.usage.poll(turn);
|
|
4336
3180
|
}
|
|
4337
3181
|
private finishTurn(
|
|
4338
3182
|
turn: ActiveTurn,
|
|
@@ -4362,7 +3206,7 @@ export class OmpProviderSession {
|
|
|
4362
3206
|
turn.agentEndPending = false;
|
|
4363
3207
|
this.cancelLocalOnlyCompletion(turn);
|
|
4364
3208
|
this.cancelAmbiguousTerminal(turn);
|
|
4365
|
-
this.
|
|
3209
|
+
this.usage.stopPoll(turn);
|
|
4366
3210
|
if (turn.manualCompactionDeadlineTimer !== undefined) {
|
|
4367
3211
|
this.scheduler.clear(turn.manualCompactionDeadlineTimer);
|
|
4368
3212
|
turn.manualCompactionDeadlineTimer = undefined;
|
|
@@ -4381,7 +3225,7 @@ export class OmpProviderSession {
|
|
|
4381
3225
|
const terminalization = (async () => {
|
|
4382
3226
|
if (!turn.terminalOutcome?.usageSampled && !this.closed && !this.runtimeDead) {
|
|
4383
3227
|
await Promise.race([
|
|
4384
|
-
this.
|
|
3228
|
+
this.usage.boundedSnapshot(turn, FINAL_USAGE_WAIT_MS),
|
|
4385
3229
|
wake.promise.then(() => undefined),
|
|
4386
3230
|
]);
|
|
4387
3231
|
}
|
|
@@ -4408,17 +3252,17 @@ export class OmpProviderSession {
|
|
|
4408
3252
|
}
|
|
4409
3253
|
this.imageMaterializer.clear();
|
|
4410
3254
|
turn.terminal = true;
|
|
4411
|
-
|
|
4412
|
-
if (!preserveCompactions && this.
|
|
4413
|
-
if (outcome.state === "canceled") this.
|
|
3255
|
+
this.usage.clearSample(turn);
|
|
3256
|
+
if (!preserveCompactions && this.compaction.active) {
|
|
3257
|
+
if (outcome.state === "canceled") this.compaction.finish("canceled");
|
|
4414
3258
|
else {
|
|
4415
|
-
this.
|
|
3259
|
+
this.compaction.finish("failed", {
|
|
4416
3260
|
message: outcome.error?.message ?? "OMP compaction ended without a terminal result",
|
|
4417
3261
|
});
|
|
4418
3262
|
}
|
|
4419
3263
|
}
|
|
4420
3264
|
this.publishPendingUsers(turn);
|
|
4421
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
3265
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
4422
3266
|
try {
|
|
4423
3267
|
this.projector.finishTurn(turn.turnId, preserveCompactions);
|
|
4424
3268
|
} catch (error) {
|
|
@@ -4449,15 +3293,14 @@ export class OmpProviderSession {
|
|
|
4449
3293
|
): void {
|
|
4450
3294
|
this.imageMaterializer.clear();
|
|
4451
3295
|
if (this.closed || this.runtimeDead) return;
|
|
4452
|
-
this.
|
|
4453
|
-
this.resolveAllPermissions();
|
|
3296
|
+
this.compaction.resetDiscardedEnds();
|
|
3297
|
+
this.permissions.resolveAllPermissions();
|
|
4454
3298
|
this.recoveryUsesNativeConfig ||=
|
|
4455
3299
|
this.configRefreshInFlight !== null || this.configRefreshDirty || this.configMutationInFlight;
|
|
4456
3300
|
const turn = this.activeTurn;
|
|
4457
|
-
if (turn) this.
|
|
4458
|
-
this.
|
|
4459
|
-
this.
|
|
4460
|
-
this.lastUsage = null;
|
|
3301
|
+
if (turn) this.usage.stopPoll(turn);
|
|
3302
|
+
this.usage.reset();
|
|
3303
|
+
this.compaction.finish(compactionState, { message });
|
|
4461
3304
|
this.generation += 1;
|
|
4462
3305
|
this.projector.resetRuntimeGeneration("OMP runtime ended during compaction");
|
|
4463
3306
|
this.runtimeDead = message;
|