@omercnet/paseo-omp 0.4.0 → 1.0.0-next.160.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.
- package/README.md +8 -8
- package/client/external-url.ts +2 -11
- package/client/omp-config-styles.ts +284 -0
- package/client/omp-config-surface.tsx +16 -286
- package/client/omp-routing-editor.tsx +1 -3
- package/client/provider-launch-settings.tsx +118 -0
- package/index.client.tsx +21 -2
- package/index.server.ts +10 -0
- package/package.json +14 -9
- package/paseo-plugin.json +2 -1
- package/scripts/prepare-dependencies.mjs +44 -11
- package/server/omp-models.ts +2 -6
- package/server/package-version.ts +1 -1
- package/server/protocol-violation-diagnostics.ts +1 -1
- package/server/provider/catalog.ts +2 -1
- package/server/provider/config-normalization.ts +19 -3
- package/server/provider/connection.ts +30 -0
- package/server/provider/host-tools.ts +2 -2
- package/server/provider/mcp-transport.ts +9 -2
- package/server/provider/omp-rpc-environment.ts +368 -0
- package/server/provider/omp-rpc-process.ts +119 -0
- package/server/provider/omp-rpc-protocol.ts +1949 -0
- package/server/provider/omp-rpc-transport.ts +1373 -0
- package/server/provider/omp-rpc-values.ts +31 -0
- package/server/provider/omp-rpc.ts +267 -3311
- package/server/provider/profile-providers.ts +2 -1
- package/server/provider/prompt-payload.ts +253 -0
- package/server/provider/registration.ts +10 -2
- package/server/provider/session-compaction.ts +187 -0
- package/server/provider/session-descriptors.ts +52 -18
- package/server/provider/session-events.ts +42 -0
- package/server/provider/session-permissions.ts +987 -0
- package/server/provider/session-terminal.ts +307 -0
- package/server/provider/session-usage.ts +234 -0
- package/server/provider/session.ts +145 -1853
- package/server/provider/settings.ts +8 -5
- package/server/provider/subsessions.ts +30 -9
- package/server/provider/timeline-projector.ts +140 -29
- package/shared/external-url.ts +0 -9
- package/shared/provider-launch-settings.ts +18 -0
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type {
|
|
3
3
|
ProviderConfigState,
|
|
4
|
-
ProviderContent,
|
|
5
4
|
ProviderError,
|
|
6
5
|
ProviderEvent,
|
|
7
6
|
ProviderInput,
|
|
8
|
-
ProviderPermissionResponse,
|
|
9
7
|
ProviderSessionConfig,
|
|
10
|
-
ProviderToolCallDetail,
|
|
11
|
-
ProviderUsage,
|
|
12
8
|
} from "@getpaseo/plugin/server/provider";
|
|
13
|
-
import { getForgeDefinitionOrNeutral } from "@getpaseo/protocol/forge-manifest";
|
|
14
9
|
import type {
|
|
15
10
|
OmpOperationalFailure,
|
|
16
11
|
OmpOperationalFailureReporter,
|
|
@@ -30,25 +25,27 @@ import {
|
|
|
30
25
|
withCommittedOmpSelection,
|
|
31
26
|
} from "./config-normalization";
|
|
32
27
|
import { OmpHostToolsBridge, type OmpMcpConnector, validateOmpHostToolConfig } from "./host-tools";
|
|
33
|
-
import { isOmpImageMimeType,
|
|
28
|
+
import { isOmpImageMimeType, OmpImageMaterializer } from "./image";
|
|
29
|
+
import type { OmpRuntime, OmpRuntimeSession } from "./omp-rpc";
|
|
30
|
+
import { buildOmpSpawnRequest, type OmpStartOptions } from "./omp-rpc-environment";
|
|
34
31
|
import type {
|
|
35
32
|
OmpAvailableCommand,
|
|
36
33
|
OmpBranchResult,
|
|
37
34
|
OmpCompactionResult,
|
|
38
|
-
OmpExtensionUiResponse,
|
|
39
|
-
OmpImage,
|
|
40
35
|
OmpMessage,
|
|
41
36
|
OmpModel,
|
|
42
37
|
OmpRpcEvent,
|
|
43
|
-
OmpRuntime,
|
|
44
|
-
OmpRuntimeSession,
|
|
45
38
|
OmpSessionState,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
39
|
+
} from "./omp-rpc-protocol";
|
|
40
|
+
import { OmpRpcRequestRejectedError } from "./omp-rpc-transport";
|
|
41
|
+
import {
|
|
42
|
+
inlinePromptFrameBytes,
|
|
43
|
+
isSafeCommandName,
|
|
44
|
+
MAX_PROMPT_TEXT_LENGTH,
|
|
45
|
+
type OmpPromptPayload,
|
|
46
|
+
promptPayload,
|
|
47
|
+
slashCommandName,
|
|
48
|
+
} from "./prompt-payload";
|
|
52
49
|
import {
|
|
53
50
|
BoundedStringSet,
|
|
54
51
|
boundedJsonBytes,
|
|
@@ -60,8 +57,24 @@ import {
|
|
|
60
57
|
OmpPublicError,
|
|
61
58
|
utf8Bytes,
|
|
62
59
|
} from "./security";
|
|
60
|
+
import { OmpSessionCompaction } from "./session-compaction";
|
|
63
61
|
import type { OmpSessionDescriptor } from "./session-descriptors";
|
|
64
62
|
import { validateNativeSessionId } from "./session-descriptors";
|
|
63
|
+
import { isNativeTurnActivity, isPassiveUiMethod, isRuntimeConfigEvent } from "./session-events";
|
|
64
|
+
import { OmpSessionPermissions } from "./session-permissions";
|
|
65
|
+
import {
|
|
66
|
+
type ActiveTurn,
|
|
67
|
+
assistantTerminalOutcome,
|
|
68
|
+
classifyTerminalCandidate,
|
|
69
|
+
createActiveTurn,
|
|
70
|
+
historyTerminalOutcome,
|
|
71
|
+
nativeEntryId,
|
|
72
|
+
type PendingUser,
|
|
73
|
+
type TerminalCandidate,
|
|
74
|
+
terminalOutcome,
|
|
75
|
+
unknownTerminalOutcomeError,
|
|
76
|
+
} from "./session-terminal";
|
|
77
|
+
import { OmpSessionUsage } from "./session-usage";
|
|
65
78
|
import { OmpSubsessionProjector } from "./subsessions";
|
|
66
79
|
import {
|
|
67
80
|
defaultOmpTimelineScheduler,
|
|
@@ -79,10 +92,6 @@ type NativeSessionTransition = (previousSessionId: string, nextSessionId: string
|
|
|
79
92
|
type RewindCleanupQuarantine = (cleanup: Promise<void>) => void;
|
|
80
93
|
type RewindSessionRetirement = () => void;
|
|
81
94
|
type SessionPermissionInput = Extract<ProviderInput, { type: "session.permission" }>;
|
|
82
|
-
type OmpQuestionRequest = Extract<
|
|
83
|
-
Extract<OmpRpcEvent, { type: "extension_ui_request" }>,
|
|
84
|
-
{ method: "select" | "confirm" | "input" | "editor" }
|
|
85
|
-
>;
|
|
86
95
|
type Emit = (event: ProviderEvent) => void;
|
|
87
96
|
const LOCAL_ONLY_SETTLE_MS = 5_000;
|
|
88
97
|
const AGENT_END_STATE_TIMEOUT_MS = 2_000;
|
|
@@ -94,15 +103,12 @@ const USAGE_REFRESH_MS = 100;
|
|
|
94
103
|
const FINAL_USAGE_WAIT_MS = 250;
|
|
95
104
|
const COMPACTION_MAX_WAIT_MS = 5 * 60_000;
|
|
96
105
|
const AGENT_END_SETTLE_MS = 5_000;
|
|
97
|
-
const MAX_PROMPT_PARTS = 64;
|
|
98
|
-
const MAX_PROMPT_TEXT_LENGTH = 1024 * 1024;
|
|
99
|
-
const RPC_REQUEST_ID_BYTES = 36;
|
|
100
106
|
const MAX_AGENT_END_CORRELATION_MESSAGES = 512;
|
|
101
107
|
const MAX_TRACKED_ENTRY_IDS = 1_024;
|
|
102
108
|
const MAX_UNCLAIMED_BRANCH_ENTRIES = 1_024;
|
|
103
109
|
const MAX_PENDING_USERS = 256;
|
|
104
|
-
const
|
|
105
|
-
const
|
|
110
|
+
const _MAX_PENDING_PERMISSIONS = 32;
|
|
111
|
+
const _MAX_PENDING_PERMISSION_BYTES = 2 * 1024 * 1024;
|
|
106
112
|
const MAX_USER_ECHOES = 512;
|
|
107
113
|
const MAX_BUFFERED_TURN_EVENTS = 512;
|
|
108
114
|
const MAX_BUFFERED_VALUE_ITEMS = 1_024;
|
|
@@ -114,8 +120,8 @@ const MAX_UNCLAIMED_BRANCH_BYTES = 4 * 1024 * 1024;
|
|
|
114
120
|
class OmpCatalogEscape extends OmpPublicError {}
|
|
115
121
|
const MAX_REPLAY_MESSAGES = 100_000;
|
|
116
122
|
const REPLAY_TIMEOUT_MS = 20_000;
|
|
117
|
-
const
|
|
118
|
-
const
|
|
123
|
+
const _OMP_ASK_USER_FREEFORM_SENTINEL = "✏️ Type custom response...";
|
|
124
|
+
const _MAX_FREEFORM_RESPONSE_BYTES = 64 * 1024;
|
|
119
125
|
const OMP_BUILTIN_COMMANDS: readonly OmpAvailableCommand[] = [
|
|
120
126
|
{
|
|
121
127
|
name: "compact",
|
|
@@ -234,86 +240,6 @@ async function waitForReplay<T>(operation: Promise<T>, signal: AbortSignal): Pro
|
|
|
234
240
|
}
|
|
235
241
|
}
|
|
236
242
|
|
|
237
|
-
type PendingUser = {
|
|
238
|
-
clientMessageId: string;
|
|
239
|
-
text: string;
|
|
240
|
-
accepted: boolean;
|
|
241
|
-
fallbackOnFinish: boolean;
|
|
242
|
-
bufferedEchoes: OmpMessage[];
|
|
243
|
-
};
|
|
244
|
-
type TerminalCorrelationEvidence = "fresh-native-user" | "current-assistant";
|
|
245
|
-
|
|
246
|
-
type TerminalCorrelation = {
|
|
247
|
-
policy: "initial-turn" | "ordered-legacy" | "native-command";
|
|
248
|
-
evidence: Map<TerminalCorrelationEvidence, number>;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
type TerminalCandidate = {
|
|
252
|
-
event: Extract<OmpRpcEvent, { type: "agent_end" }>;
|
|
253
|
-
arrivalSequence: number;
|
|
254
|
-
confidence:
|
|
255
|
-
| "keyed"
|
|
256
|
-
| "initial-turn"
|
|
257
|
-
| "ordered-legacy"
|
|
258
|
-
| "native-command"
|
|
259
|
-
| "interrupted"
|
|
260
|
-
| "ambiguous";
|
|
261
|
-
};
|
|
262
|
-
|
|
263
|
-
type ActiveTurn = {
|
|
264
|
-
turnId: string;
|
|
265
|
-
clientMessageId: string;
|
|
266
|
-
generation: number;
|
|
267
|
-
promptResultEmitted: boolean;
|
|
268
|
-
started: boolean;
|
|
269
|
-
terminal: boolean;
|
|
270
|
-
interrupted: boolean;
|
|
271
|
-
starting: boolean;
|
|
272
|
-
nativeActivity: boolean;
|
|
273
|
-
userEchoObserved: boolean;
|
|
274
|
-
localOnlyDisabled: boolean;
|
|
275
|
-
localOnlyEligible: boolean;
|
|
276
|
-
awaitingPermissionEvidence: boolean;
|
|
277
|
-
activitySequence: number;
|
|
278
|
-
acknowledged: boolean;
|
|
279
|
-
terminalCorrelation: TerminalCorrelation;
|
|
280
|
-
replayingBufferedEvents: boolean;
|
|
281
|
-
agentInvoked?: boolean;
|
|
282
|
-
nativeRequestId?: string;
|
|
283
|
-
promptAcceptedEventIndex?: number;
|
|
284
|
-
localOnlyTimer?: unknown;
|
|
285
|
-
usagePollTimer?: unknown;
|
|
286
|
-
usagePoll?: Promise<void>;
|
|
287
|
-
usageSampleFloor: number;
|
|
288
|
-
manualCompaction: boolean;
|
|
289
|
-
manualCompactionPending: boolean;
|
|
290
|
-
manualCompactionDeadlineTimer?: unknown;
|
|
291
|
-
agentEndPending: boolean;
|
|
292
|
-
agentEndRetryTimer?: unknown;
|
|
293
|
-
agentEndDeadlineTimer?: unknown;
|
|
294
|
-
agentEndCheck?: Promise<void>;
|
|
295
|
-
ambiguousTerminalTimer?: unknown;
|
|
296
|
-
terminalizing: boolean;
|
|
297
|
-
terminalization?: Promise<void>;
|
|
298
|
-
terminalOutcome?: TurnOutcome;
|
|
299
|
-
operationalTerminalStage?: "failed" | "unresolved";
|
|
300
|
-
terminalWake?: VoidDeferred;
|
|
301
|
-
steerReady: VoidDeferred;
|
|
302
|
-
steersInFlight: number;
|
|
303
|
-
deferredAgentEnd?: TerminalCandidate;
|
|
304
|
-
activeToolCallIds: Set<string>;
|
|
305
|
-
bufferedEvents: OmpRpcEvent[];
|
|
306
|
-
pendingUsers: PendingUser[];
|
|
307
|
-
userEchoes: OmpMessage[];
|
|
308
|
-
userCorrelationActive: boolean;
|
|
309
|
-
userLookups: Set<Promise<void>>;
|
|
310
|
-
completedMessageCount: number;
|
|
311
|
-
streamedMessageEntryIds: string[];
|
|
312
|
-
streamedMessageIdentityComplete: boolean;
|
|
313
|
-
lastCompletedAssistantOutcome?: AgentEndOutcome;
|
|
314
|
-
lastCompletedAssistantEntryId?: string;
|
|
315
|
-
};
|
|
316
|
-
|
|
317
243
|
type PendingAbort = {
|
|
318
244
|
turn: ActiveTurn;
|
|
319
245
|
generation: number;
|
|
@@ -321,68 +247,6 @@ type PendingAbort = {
|
|
|
321
247
|
forceTerminal: boolean;
|
|
322
248
|
promise: Promise<void>;
|
|
323
249
|
};
|
|
324
|
-
type PendingPermission = {
|
|
325
|
-
nativeId: string;
|
|
326
|
-
header: string;
|
|
327
|
-
fingerprint: string;
|
|
328
|
-
optionValues: ReadonlyMap<string, string>;
|
|
329
|
-
actionBehaviors: ReadonlyMap<string, "allow" | "deny">;
|
|
330
|
-
retainedBytes: number;
|
|
331
|
-
displayValues: ReadonlyMap<string, string>;
|
|
332
|
-
generation: number;
|
|
333
|
-
runtime: OmpRuntimeSession;
|
|
334
|
-
expiresAt?: number;
|
|
335
|
-
timer?: unknown;
|
|
336
|
-
turnId?: string;
|
|
337
|
-
request: OmpQuestionRequest;
|
|
338
|
-
freeformSentinel?: string;
|
|
339
|
-
};
|
|
340
|
-
type PendingToolPermission = {
|
|
341
|
-
nativeId: string;
|
|
342
|
-
toolCallId: string;
|
|
343
|
-
fingerprint: string;
|
|
344
|
-
retainedBytes: number;
|
|
345
|
-
generation: number;
|
|
346
|
-
runtime: OmpRuntimeSession;
|
|
347
|
-
expiresAt?: number;
|
|
348
|
-
timer?: unknown;
|
|
349
|
-
turnId?: string;
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
type PendingFreeformSelection = {
|
|
353
|
-
value: string;
|
|
354
|
-
nativeSelectId: string;
|
|
355
|
-
generation: number;
|
|
356
|
-
runtime: OmpRuntimeSession;
|
|
357
|
-
turnId?: string;
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
function permissionFingerprint(request: OmpQuestionRequest): string {
|
|
361
|
-
return createHash("sha256").update(JSON.stringify(request)).digest("base64url");
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
type VoidDeferred = {
|
|
365
|
-
promise: Promise<void>;
|
|
366
|
-
resolve(value?: void | PromiseLike<void>): void;
|
|
367
|
-
reject(reason?: unknown): void;
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
type TurnOutcome = {
|
|
371
|
-
state: "completed" | "failed" | "canceled";
|
|
372
|
-
error?: ProviderError;
|
|
373
|
-
usageSampled: boolean;
|
|
374
|
-
};
|
|
375
|
-
|
|
376
|
-
type ActiveCompaction = {
|
|
377
|
-
id: string;
|
|
378
|
-
trigger: "auto" | "manual";
|
|
379
|
-
turnId: string;
|
|
380
|
-
generation: number;
|
|
381
|
-
retrying: boolean;
|
|
382
|
-
action?: string;
|
|
383
|
-
preTokens?: number;
|
|
384
|
-
};
|
|
385
|
-
|
|
386
250
|
function providerError(error: unknown, fallback: string): { message: string } {
|
|
387
251
|
return { message: isOmpPublicError(error) ? error.message : fallback };
|
|
388
252
|
}
|
|
@@ -407,492 +271,6 @@ async function settleSessionCleanup(promises: readonly Promise<void>[]): Promise
|
|
|
407
271
|
throw new AggregateError(failures, "OMP session initialization cleanup failed");
|
|
408
272
|
}
|
|
409
273
|
}
|
|
410
|
-
function isSafeCommandName(name: string): boolean {
|
|
411
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]*(?::[A-Za-z0-9][A-Za-z0-9._-]*)*$/u.test(name);
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
type OmpPromptPayload = { text: string; images: OmpImage[]; commandName?: string };
|
|
415
|
-
|
|
416
|
-
const REVIEW_LINE_MARKERS = { add: "+", remove: "-", context: " " } as const;
|
|
417
|
-
|
|
418
|
-
function renderPromptAttachmentAsText(part: Exclude<ProviderContent, { type: "image" }>): string {
|
|
419
|
-
switch (part.type) {
|
|
420
|
-
case "forge_change_request": {
|
|
421
|
-
return renderChangeRequestAttachment({
|
|
422
|
-
forge: part.forge ?? "github",
|
|
423
|
-
number: part.number,
|
|
424
|
-
title: part.title,
|
|
425
|
-
url: part.url,
|
|
426
|
-
body: part.body,
|
|
427
|
-
projectPath: part.projectPath,
|
|
428
|
-
baseRefName: part.baseRefName,
|
|
429
|
-
headRefName: part.headRefName,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
case "github_pr": {
|
|
433
|
-
return renderChangeRequestAttachment({
|
|
434
|
-
forge: "github",
|
|
435
|
-
number: part.number,
|
|
436
|
-
title: part.title,
|
|
437
|
-
url: part.url,
|
|
438
|
-
body: part.body,
|
|
439
|
-
baseRefName: part.baseRefName,
|
|
440
|
-
headRefName: part.headRefName,
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
case "forge_issue": {
|
|
444
|
-
return renderIssueAttachment({
|
|
445
|
-
forge: part.forge ?? "github",
|
|
446
|
-
number: part.number,
|
|
447
|
-
title: part.title,
|
|
448
|
-
url: part.url,
|
|
449
|
-
body: part.body,
|
|
450
|
-
projectPath: part.projectPath,
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
case "github_issue": {
|
|
454
|
-
return renderIssueAttachment({
|
|
455
|
-
forge: "github",
|
|
456
|
-
number: part.number,
|
|
457
|
-
title: part.title,
|
|
458
|
-
url: part.url,
|
|
459
|
-
body: part.body,
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
case "text": {
|
|
463
|
-
return part.text;
|
|
464
|
-
}
|
|
465
|
-
case "review": {
|
|
466
|
-
const lines = [`Paseo review attachment (${part.mode})`, `CWD: ${part.cwd}`];
|
|
467
|
-
if (part.baseRef) {
|
|
468
|
-
lines.push(`Base: ${part.baseRef}`);
|
|
469
|
-
}
|
|
470
|
-
part.comments.forEach((comment, index) => {
|
|
471
|
-
lines.push(
|
|
472
|
-
"",
|
|
473
|
-
`Comment ${index + 1}: ${comment.filePath}:${comment.side}:${comment.lineNumber}`,
|
|
474
|
-
comment.body,
|
|
475
|
-
comment.context.hunkHeader,
|
|
476
|
-
);
|
|
477
|
-
const target = comment.context.targetLine;
|
|
478
|
-
for (const line of comment.context.lines) {
|
|
479
|
-
const isTarget =
|
|
480
|
-
line.oldLineNumber === target.oldLineNumber &&
|
|
481
|
-
line.newLineNumber === target.newLineNumber &&
|
|
482
|
-
line.type === target.type &&
|
|
483
|
-
line.content === target.content;
|
|
484
|
-
const prefix = isTarget ? "> " : " ";
|
|
485
|
-
const oldLn = padLineNumber(line.oldLineNumber);
|
|
486
|
-
const newLn = padLineNumber(line.newLineNumber);
|
|
487
|
-
lines.push(`${prefix}${oldLn} ${newLn} ${REVIEW_LINE_MARKERS[line.type]}${line.content}`);
|
|
488
|
-
}
|
|
489
|
-
});
|
|
490
|
-
return lines.join("\n");
|
|
491
|
-
}
|
|
492
|
-
case "uploaded_file": {
|
|
493
|
-
return [
|
|
494
|
-
`Uploaded file: ${part.fileName}`,
|
|
495
|
-
`Path: ${part.path}`,
|
|
496
|
-
`MIME: ${part.mimeType}`,
|
|
497
|
-
`Size: ${part.size} bytes`,
|
|
498
|
-
].join("\n");
|
|
499
|
-
}
|
|
500
|
-
default:
|
|
501
|
-
throw new Error("unreachable");
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function renderChangeRequestAttachment(input: {
|
|
506
|
-
forge: string;
|
|
507
|
-
number: number;
|
|
508
|
-
title: string;
|
|
509
|
-
url: string;
|
|
510
|
-
body?: string | null;
|
|
511
|
-
projectPath?: string;
|
|
512
|
-
baseRefName?: string | null;
|
|
513
|
-
headRefName?: string | null;
|
|
514
|
-
}): string {
|
|
515
|
-
const lines = [
|
|
516
|
-
`${formatForgeLabel(input.forge)} ${formatChangeRequestAbbrev(input.forge)} ${formatChangeRequestNumber(input.forge, input.number)}: ${input.title}`,
|
|
517
|
-
input.url,
|
|
518
|
-
];
|
|
519
|
-
if (input.projectPath) {
|
|
520
|
-
lines.push(`Project: ${input.projectPath}`);
|
|
521
|
-
}
|
|
522
|
-
if (input.baseRefName) {
|
|
523
|
-
lines.push(`Base: ${input.baseRefName}`);
|
|
524
|
-
}
|
|
525
|
-
if (input.headRefName) {
|
|
526
|
-
lines.push(`Head: ${input.headRefName}`);
|
|
527
|
-
}
|
|
528
|
-
if (input.body) {
|
|
529
|
-
lines.push("", input.body);
|
|
530
|
-
}
|
|
531
|
-
return lines.join("\n");
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function renderIssueAttachment(input: {
|
|
535
|
-
forge: string;
|
|
536
|
-
number: number;
|
|
537
|
-
title: string;
|
|
538
|
-
url: string;
|
|
539
|
-
body?: string | null;
|
|
540
|
-
projectPath?: string;
|
|
541
|
-
}): string {
|
|
542
|
-
const lines = [
|
|
543
|
-
`${formatForgeLabel(input.forge)} Issue ${formatIssueNumber(input.forge, input.number)}: ${input.title}`,
|
|
544
|
-
input.url,
|
|
545
|
-
];
|
|
546
|
-
if (input.projectPath) {
|
|
547
|
-
lines.push(`Project: ${input.projectPath}`);
|
|
548
|
-
}
|
|
549
|
-
if (input.body) {
|
|
550
|
-
lines.push("", input.body);
|
|
551
|
-
}
|
|
552
|
-
return lines.join("\n");
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function formatForgeLabel(forge: string): string {
|
|
556
|
-
return getForgeDefinitionOrNeutral(forge).displayName;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function formatChangeRequestAbbrev(forge: string): string {
|
|
560
|
-
return getForgeDefinitionOrNeutral(forge).changeRequestAbbrev;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
function formatChangeRequestNumber(forge: string, number: number): string {
|
|
564
|
-
return `${getForgeDefinitionOrNeutral(forge).changeRequestNumberPrefix}${number}`;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function formatIssueNumber(forge: string, number: number): string {
|
|
568
|
-
return `${getForgeDefinitionOrNeutral(forge).issueNumberPrefix}${number}`;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function padLineNumber(lineNumber: number | null): string {
|
|
572
|
-
return (lineNumber?.toString() ?? "-").padStart(2);
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
function promptPayload(input: SessionPromptInput): OmpPromptPayload {
|
|
576
|
-
if (input.prompt.outputSchema !== undefined || input.prompt.clearPendingPermissions) {
|
|
577
|
-
throw new OmpPublicError("OMP does not support structured output or permission controls");
|
|
578
|
-
}
|
|
579
|
-
if (input.prompt.input.type === "command") {
|
|
580
|
-
const name = input.prompt.input.name.trim();
|
|
581
|
-
if (!isSafeCommandName(name)) throw new OmpPublicError("Invalid OMP command name");
|
|
582
|
-
const argumentsText = input.prompt.input.arguments.trim();
|
|
583
|
-
const text = `/${name}${argumentsText ? ` ${argumentsText}` : ""}`;
|
|
584
|
-
if (utf8Bytes(text) > MAX_PROMPT_TEXT_LENGTH)
|
|
585
|
-
throw new OmpPublicError("OMP command is too large");
|
|
586
|
-
return { text, images: [], commandName: name };
|
|
587
|
-
}
|
|
588
|
-
if (input.prompt.input.content.length > MAX_PROMPT_PARTS) {
|
|
589
|
-
throw new OmpPublicError("OMP prompt has too many content parts");
|
|
590
|
-
}
|
|
591
|
-
const parts: string[] = [];
|
|
592
|
-
const images: OmpImage[] = [];
|
|
593
|
-
let length = 0;
|
|
594
|
-
const appendText = (text: string) => {
|
|
595
|
-
length += utf8Bytes(text) + (parts.length > 0 ? 2 : 0);
|
|
596
|
-
if (length > MAX_PROMPT_TEXT_LENGTH) throw new OmpPublicError("OMP prompt is too large");
|
|
597
|
-
parts.push(text);
|
|
598
|
-
};
|
|
599
|
-
for (const part of input.prompt.input.content) {
|
|
600
|
-
if (part.type === "text") {
|
|
601
|
-
appendText(part.text);
|
|
602
|
-
continue;
|
|
603
|
-
}
|
|
604
|
-
if (part.type === "image") {
|
|
605
|
-
if (!isValidImagePayload(part.data, part.mimeType, 8 * 1024 * 1024)) {
|
|
606
|
-
throw new OmpPublicError("OMP prompt image is invalid");
|
|
607
|
-
}
|
|
608
|
-
images.push({ type: "image", data: part.data, mimeType: part.mimeType });
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
appendText(renderPromptAttachmentAsText(part));
|
|
612
|
-
}
|
|
613
|
-
const text = parts.join("\n\n").trim();
|
|
614
|
-
if (!text && images.length === 0) throw new OmpPublicError("OMP prompt cannot be empty");
|
|
615
|
-
return { text, images };
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
function inlinePromptFrameBytes(payload: OmpPromptPayload, delivery: "prompt" | "steer"): number {
|
|
619
|
-
const images = payload.images.map(({ type, mimeType }) => ({ type, data: "", mimeType }));
|
|
620
|
-
const frame = {
|
|
621
|
-
type: delivery,
|
|
622
|
-
message: payload.text,
|
|
623
|
-
...(images.length > 0 ? { images } : {}),
|
|
624
|
-
...(delivery === "prompt" ? { id: "" } : {}),
|
|
625
|
-
};
|
|
626
|
-
const requestIdBytes = delivery === "prompt" ? RPC_REQUEST_ID_BYTES : 0;
|
|
627
|
-
return (
|
|
628
|
-
utf8Bytes(JSON.stringify(frame)) +
|
|
629
|
-
requestIdBytes +
|
|
630
|
-
payload.images.reduce((total, image) => total + image.data.length, 0) +
|
|
631
|
-
1
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function slashCommandName(text: string): string | undefined {
|
|
636
|
-
if (!text.startsWith("/")) return undefined;
|
|
637
|
-
const body = text.slice(1);
|
|
638
|
-
if (!body) return undefined;
|
|
639
|
-
const firstWhitespace = body.search(/\s/u);
|
|
640
|
-
const name = firstWhitespace === -1 ? body : body.slice(0, firstWhitespace);
|
|
641
|
-
return name || undefined;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function nativeEntryId(message: OmpMessage): string | undefined {
|
|
645
|
-
return message.entryId;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
type AgentEndOutcome = "completed" | "failed" | "canceled";
|
|
649
|
-
type AssistantTerminalStatus = AgentEndOutcome | "unavailable";
|
|
650
|
-
|
|
651
|
-
function assistantTerminalOutcome(
|
|
652
|
-
message: Extract<OmpMessage, { role: "assistant" }>,
|
|
653
|
-
): AgentEndOutcome {
|
|
654
|
-
const stopReason = message.stopReason?.toLowerCase();
|
|
655
|
-
if (stopReason === "aborted" || stopReason === "canceled" || stopReason === "cancelled") {
|
|
656
|
-
return "canceled";
|
|
657
|
-
}
|
|
658
|
-
return stopReason === "error" || message.errorMessage ? "failed" : "completed";
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function lastAssistantStatus(
|
|
662
|
-
messages: readonly OmpMessage[],
|
|
663
|
-
startIndex = 0,
|
|
664
|
-
): AssistantTerminalStatus {
|
|
665
|
-
for (let index = messages.length - 1; index >= startIndex; index -= 1) {
|
|
666
|
-
const message = messages[index];
|
|
667
|
-
if (message?.role !== "assistant") continue;
|
|
668
|
-
return assistantTerminalOutcome(message);
|
|
669
|
-
}
|
|
670
|
-
return "unavailable";
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
function terminalOutcome(
|
|
674
|
-
event: Extract<OmpRpcEvent, { type: "agent_end" }>,
|
|
675
|
-
turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
|
|
676
|
-
): AgentEndOutcome | undefined {
|
|
677
|
-
const messages = event.messages;
|
|
678
|
-
if (
|
|
679
|
-
messages !== undefined &&
|
|
680
|
-
(event.messageCount === undefined || messages.length >= event.messageCount)
|
|
681
|
-
) {
|
|
682
|
-
const status = lastAssistantStatus(messages);
|
|
683
|
-
return status === "unavailable" ? "completed" : status;
|
|
684
|
-
}
|
|
685
|
-
if (event.messageCount === 0) return "completed";
|
|
686
|
-
if (
|
|
687
|
-
turn.lastCompletedAssistantOutcome !== undefined &&
|
|
688
|
-
(event.messageCount === undefined || turn.completedMessageCount >= event.messageCount)
|
|
689
|
-
) {
|
|
690
|
-
return turn.lastCompletedAssistantOutcome;
|
|
691
|
-
}
|
|
692
|
-
return undefined;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
function historyTerminalOutcome(
|
|
696
|
-
messages: readonly OmpMessage[],
|
|
697
|
-
declaredCount: number,
|
|
698
|
-
turn: Pick<
|
|
699
|
-
ActiveTurn,
|
|
700
|
-
| "streamedMessageEntryIds"
|
|
701
|
-
| "streamedMessageIdentityComplete"
|
|
702
|
-
| "lastCompletedAssistantOutcome"
|
|
703
|
-
| "lastCompletedAssistantEntryId"
|
|
704
|
-
>,
|
|
705
|
-
retainedMessages: readonly OmpMessage[],
|
|
706
|
-
): AgentEndOutcome | undefined {
|
|
707
|
-
if (
|
|
708
|
-
messages.length < declaredCount ||
|
|
709
|
-
!turn.streamedMessageIdentityComplete ||
|
|
710
|
-
turn.streamedMessageEntryIds.length === 0
|
|
711
|
-
) {
|
|
712
|
-
return undefined;
|
|
713
|
-
}
|
|
714
|
-
const startIndex = messages.length - declaredCount;
|
|
715
|
-
let historyIndex = startIndex;
|
|
716
|
-
for (const entryId of turn.streamedMessageEntryIds) {
|
|
717
|
-
while (historyIndex < messages.length) {
|
|
718
|
-
const historyMessage = messages[historyIndex];
|
|
719
|
-
if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
|
|
720
|
-
historyIndex += 1;
|
|
721
|
-
}
|
|
722
|
-
if (historyIndex >= messages.length) return undefined;
|
|
723
|
-
const correlated = messages[historyIndex];
|
|
724
|
-
if (
|
|
725
|
-
entryId === turn.lastCompletedAssistantEntryId &&
|
|
726
|
-
(correlated?.role !== "assistant" ||
|
|
727
|
-
assistantTerminalOutcome(correlated) !== turn.lastCompletedAssistantOutcome)
|
|
728
|
-
)
|
|
729
|
-
return undefined;
|
|
730
|
-
historyIndex += 1;
|
|
731
|
-
}
|
|
732
|
-
historyIndex = startIndex;
|
|
733
|
-
for (const retained of retainedMessages) {
|
|
734
|
-
const entryId = nativeEntryId(retained);
|
|
735
|
-
if (!entryId) return undefined;
|
|
736
|
-
while (historyIndex < messages.length) {
|
|
737
|
-
const historyMessage = messages[historyIndex];
|
|
738
|
-
if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
|
|
739
|
-
historyIndex += 1;
|
|
740
|
-
}
|
|
741
|
-
if (historyIndex >= messages.length) return undefined;
|
|
742
|
-
const correlated = messages[historyIndex];
|
|
743
|
-
if (!correlated || correlated.role !== retained.role) return undefined;
|
|
744
|
-
if (
|
|
745
|
-
retained.role === "assistant" &&
|
|
746
|
-
correlated.role === "assistant" &&
|
|
747
|
-
assistantTerminalOutcome(retained) !== assistantTerminalOutcome(correlated)
|
|
748
|
-
)
|
|
749
|
-
return undefined;
|
|
750
|
-
historyIndex += 1;
|
|
751
|
-
}
|
|
752
|
-
const status = lastAssistantStatus(messages, startIndex);
|
|
753
|
-
return status === "unavailable" ? undefined : status;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
function unknownTerminalOutcomeError(
|
|
757
|
-
event: Extract<OmpRpcEvent, { type: "agent_end" }>,
|
|
758
|
-
turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
|
|
759
|
-
): string {
|
|
760
|
-
const retainedMessages = event.messages?.length ?? 0;
|
|
761
|
-
const retainedStatus = event.messages ? lastAssistantStatus(event.messages) : "unavailable";
|
|
762
|
-
const lastStatus =
|
|
763
|
-
retainedStatus === "unavailable"
|
|
764
|
-
? (turn.lastCompletedAssistantOutcome ?? "unavailable")
|
|
765
|
-
: retainedStatus;
|
|
766
|
-
return (
|
|
767
|
-
"OMP agent_end omitted terminal messages; outcome is unknown " +
|
|
768
|
-
`(declaredCount=${event.messageCount ?? "unavailable"}, ` +
|
|
769
|
-
`observedCount=${turn.completedMessageCount}, ` +
|
|
770
|
-
`retainedTerminalMessages=${retainedMessages}, lastAssistantStatus=${lastStatus})`
|
|
771
|
-
);
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
function isNativeTurnActivity(event: OmpRpcEvent): boolean {
|
|
775
|
-
if (
|
|
776
|
-
event.type === "agent_start" ||
|
|
777
|
-
event.type === "turn_start" ||
|
|
778
|
-
event.type === "turn_end" ||
|
|
779
|
-
event.type === "agent_end" ||
|
|
780
|
-
event.type === "auto_compaction_start" ||
|
|
781
|
-
event.type === "auto_compaction_end"
|
|
782
|
-
) {
|
|
783
|
-
return true;
|
|
784
|
-
}
|
|
785
|
-
if (
|
|
786
|
-
event.type === "message_start" ||
|
|
787
|
-
event.type === "message_update" ||
|
|
788
|
-
event.type === "message_end"
|
|
789
|
-
) {
|
|
790
|
-
return event.message.role === "assistant";
|
|
791
|
-
}
|
|
792
|
-
return event.type.startsWith("tool_execution_");
|
|
793
|
-
}
|
|
794
|
-
function isRuntimeConfigEvent(event: OmpRpcEvent): boolean {
|
|
795
|
-
return (
|
|
796
|
-
event.type === "model_changed" ||
|
|
797
|
-
event.type === "thinking_level_changed" ||
|
|
798
|
-
event.type === "retry_fallback_applied" ||
|
|
799
|
-
event.type === "retry_fallback_succeeded"
|
|
800
|
-
);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
function isPassiveUiMethod(method: string): boolean {
|
|
804
|
-
return (
|
|
805
|
-
method === "cancel" ||
|
|
806
|
-
method === "notify" ||
|
|
807
|
-
method === "open_url" ||
|
|
808
|
-
method === "setStatus" ||
|
|
809
|
-
method === "setWidget" ||
|
|
810
|
-
method === "setTitle" ||
|
|
811
|
-
method === "set_editor_text"
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
|
-
function createActiveTurn(
|
|
815
|
-
clientMessageId: string,
|
|
816
|
-
text: string,
|
|
817
|
-
generation: number,
|
|
818
|
-
terminalPolicy: TerminalCorrelation["policy"],
|
|
819
|
-
manualCompaction = false,
|
|
820
|
-
): ActiveTurn {
|
|
821
|
-
return {
|
|
822
|
-
turnId: randomUUID(),
|
|
823
|
-
clientMessageId,
|
|
824
|
-
agentInvoked: undefined,
|
|
825
|
-
generation,
|
|
826
|
-
promptResultEmitted: false,
|
|
827
|
-
started: false,
|
|
828
|
-
terminal: false,
|
|
829
|
-
awaitingPermissionEvidence: false,
|
|
830
|
-
interrupted: false,
|
|
831
|
-
starting: true,
|
|
832
|
-
nativeActivity: false,
|
|
833
|
-
userEchoObserved: false,
|
|
834
|
-
localOnlyDisabled: false,
|
|
835
|
-
localOnlyEligible: false,
|
|
836
|
-
usageSampleFloor: 0,
|
|
837
|
-
agentEndPending: false,
|
|
838
|
-
terminalizing: false,
|
|
839
|
-
manualCompactionPending: manualCompaction,
|
|
840
|
-
manualCompaction,
|
|
841
|
-
activitySequence: 0,
|
|
842
|
-
acknowledged: false,
|
|
843
|
-
terminalCorrelation: { policy: terminalPolicy, evidence: new Map() },
|
|
844
|
-
replayingBufferedEvents: false,
|
|
845
|
-
steersInFlight: 0,
|
|
846
|
-
activeToolCallIds: new Set(),
|
|
847
|
-
steerReady: Promise.withResolvers<void>(),
|
|
848
|
-
userCorrelationActive: false,
|
|
849
|
-
userLookups: new Set(),
|
|
850
|
-
userEchoes: [],
|
|
851
|
-
completedMessageCount: 0,
|
|
852
|
-
bufferedEvents: [],
|
|
853
|
-
streamedMessageEntryIds: [],
|
|
854
|
-
streamedMessageIdentityComplete: true,
|
|
855
|
-
pendingUsers: [
|
|
856
|
-
{
|
|
857
|
-
clientMessageId,
|
|
858
|
-
text,
|
|
859
|
-
accepted: true,
|
|
860
|
-
fallbackOnFinish: true,
|
|
861
|
-
bufferedEchoes: [],
|
|
862
|
-
},
|
|
863
|
-
],
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
function classifyTerminalCandidate(
|
|
867
|
-
turn: ActiveTurn,
|
|
868
|
-
event: Extract<OmpRpcEvent, { type: "agent_end" }>,
|
|
869
|
-
arrivalSequence = turn.activitySequence,
|
|
870
|
-
): TerminalCandidate {
|
|
871
|
-
if (event.requestId !== undefined) return { event, confidence: "keyed", arrivalSequence };
|
|
872
|
-
if (turn.agentInvoked === false) return { event, confidence: "ambiguous", arrivalSequence };
|
|
873
|
-
if (turn.terminalCorrelation.policy === "initial-turn") {
|
|
874
|
-
return { event, confidence: "initial-turn", arrivalSequence };
|
|
875
|
-
}
|
|
876
|
-
const assistantSequence = turn.terminalCorrelation.evidence.get("current-assistant");
|
|
877
|
-
if (
|
|
878
|
-
turn.terminalCorrelation.policy === "native-command" &&
|
|
879
|
-
assistantSequence !== undefined &&
|
|
880
|
-
assistantSequence <= arrivalSequence
|
|
881
|
-
) {
|
|
882
|
-
return { event, confidence: "native-command", arrivalSequence };
|
|
883
|
-
}
|
|
884
|
-
const userSequence = turn.terminalCorrelation.evidence.get("fresh-native-user");
|
|
885
|
-
if (
|
|
886
|
-
userSequence !== undefined &&
|
|
887
|
-
assistantSequence !== undefined &&
|
|
888
|
-
userSequence < assistantSequence &&
|
|
889
|
-
assistantSequence <= arrivalSequence
|
|
890
|
-
) {
|
|
891
|
-
return { event, confidence: "ordered-legacy", arrivalSequence };
|
|
892
|
-
}
|
|
893
|
-
return { event, confidence: "ambiguous", arrivalSequence };
|
|
894
|
-
}
|
|
895
|
-
|
|
896
274
|
export class OmpProviderSession {
|
|
897
275
|
readonly id: string;
|
|
898
276
|
readonly cwd: string;
|
|
@@ -928,30 +306,12 @@ export class OmpProviderSession {
|
|
|
928
306
|
private configRevision = 0;
|
|
929
307
|
private recoveryUsesNativeConfig = false;
|
|
930
308
|
private activeAbort: PendingAbort | null = null;
|
|
931
|
-
private
|
|
932
|
-
private
|
|
933
|
-
private usageSample: {
|
|
934
|
-
turn: ActiveTurn;
|
|
935
|
-
generation: number;
|
|
936
|
-
runtime: OmpRuntimeSession;
|
|
937
|
-
epoch: number;
|
|
938
|
-
sequence: number;
|
|
939
|
-
promise: Promise<OmpSessionState | undefined>;
|
|
940
|
-
} | null = null;
|
|
941
|
-
private activeCompaction: ActiveCompaction | null = null;
|
|
942
|
-
private discardedCompactionEnds = 0;
|
|
943
|
-
private lastUsage: ProviderUsage | null = null;
|
|
309
|
+
private readonly usage: OmpSessionUsage;
|
|
310
|
+
private readonly compaction: OmpSessionCompaction;
|
|
944
311
|
private revertInFlight = false;
|
|
945
312
|
private runtimeTurnCompleted = false;
|
|
946
313
|
private commandCatalog: OmpAvailableCommand[];
|
|
947
|
-
private
|
|
948
|
-
private readonly permissionNamespace = randomUUID();
|
|
949
|
-
private readonly pendingPermissions = new Map<string, PendingPermission>();
|
|
950
|
-
private readonly inFlightPermissions = new Map<string, PendingPermission>();
|
|
951
|
-
private pendingFreeformSelection: PendingFreeformSelection | null = null;
|
|
952
|
-
private readonly pendingToolPermissions = new Map<string, PendingToolPermission>();
|
|
953
|
-
private readonly inFlightToolPermissions = new Map<string, PendingToolPermission>();
|
|
954
|
-
private readonly resolvedToolApprovalIds = new BoundedStringSet(MAX_TRACKED_ENTRY_IDS);
|
|
314
|
+
private readonly permissions: OmpSessionPermissions;
|
|
955
315
|
private unsupportedThinkingNoticePending = false;
|
|
956
316
|
private unsupportedThinkingNoticePublished = false;
|
|
957
317
|
|
|
@@ -984,6 +344,26 @@ export class OmpProviderSession {
|
|
|
984
344
|
this.id = id;
|
|
985
345
|
this.cwd = config.cwd;
|
|
986
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
|
+
);
|
|
987
367
|
this.dataFilter = new OmpPublicDataSerializer(outputRedactionValues);
|
|
988
368
|
this.nativeModelsByPublicId = nativeModelsByPublicId;
|
|
989
369
|
this.commandCatalog = commandCatalog;
|
|
@@ -997,6 +377,31 @@ export class OmpProviderSession {
|
|
|
997
377
|
capabilities.includes("timeline.plugin"),
|
|
998
378
|
hostTools.labels,
|
|
999
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
|
+
});
|
|
1000
405
|
this.subsessions = capabilities.includes("session.subsession")
|
|
1001
406
|
? new OmpSubsessionProjector(
|
|
1002
407
|
id,
|
|
@@ -1046,6 +451,7 @@ export class OmpProviderSession {
|
|
|
1046
451
|
mcpConnector?: OmpMcpConnector,
|
|
1047
452
|
mcpInitializationTimeoutMs?: number,
|
|
1048
453
|
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
454
|
+
hostInheritEnv: readonly string[] = [],
|
|
1049
455
|
): Promise<OmpProviderSession> {
|
|
1050
456
|
const resumeSessionId = ompPersistenceSessionId(input);
|
|
1051
457
|
if (resumeSessionId && !input.config.persist) {
|
|
@@ -1061,6 +467,7 @@ export class OmpProviderSession {
|
|
|
1061
467
|
const normalizedConfig = normalizeOmpSessionConfig(
|
|
1062
468
|
effectiveConfig,
|
|
1063
469
|
capabilities.includes("permission"),
|
|
470
|
+
hostInheritEnv,
|
|
1064
471
|
);
|
|
1065
472
|
const configuredRedactionValues = configuredOutputRedactionValues(
|
|
1066
473
|
normalizedConfig.outputRedaction ?? "none",
|
|
@@ -1338,289 +745,6 @@ export class OmpProviderSession {
|
|
|
1338
745
|
});
|
|
1339
746
|
}
|
|
1340
747
|
|
|
1341
|
-
private usageFrom(
|
|
1342
|
-
state: OmpSessionState | undefined,
|
|
1343
|
-
stats: OmpSessionStats | undefined,
|
|
1344
|
-
): ProviderUsage | undefined {
|
|
1345
|
-
if (!state?.contextUsage && !stats) return undefined;
|
|
1346
|
-
const stateContext = state?.contextUsage;
|
|
1347
|
-
const statsContext = stats?.contextUsage;
|
|
1348
|
-
const modelCapacity = state?.model?.contextWindow;
|
|
1349
|
-
const inputTokens = stats?.tokens?.input;
|
|
1350
|
-
const cachedInputTokens = stats?.tokens?.cacheRead;
|
|
1351
|
-
const outputTokens = stats?.tokens?.output;
|
|
1352
|
-
const totalCostUsd = stats?.cost;
|
|
1353
|
-
const contextTokens =
|
|
1354
|
-
typeof stateContext?.tokens === "number"
|
|
1355
|
-
? stateContext.tokens
|
|
1356
|
-
: typeof statsContext?.tokens === "number"
|
|
1357
|
-
? statsContext.tokens
|
|
1358
|
-
: undefined;
|
|
1359
|
-
const contextWindow =
|
|
1360
|
-
typeof stateContext?.contextWindow === "number" && stateContext.contextWindow > 0
|
|
1361
|
-
? stateContext.contextWindow
|
|
1362
|
-
: typeof statsContext?.contextWindow === "number" && statsContext.contextWindow > 0
|
|
1363
|
-
? statsContext.contextWindow
|
|
1364
|
-
: typeof modelCapacity === "number" && modelCapacity > 0
|
|
1365
|
-
? modelCapacity
|
|
1366
|
-
: undefined;
|
|
1367
|
-
const usage: ProviderUsage = {
|
|
1368
|
-
...(typeof inputTokens === "number" ? { inputTokens } : {}),
|
|
1369
|
-
...(typeof cachedInputTokens === "number" ? { cachedInputTokens } : {}),
|
|
1370
|
-
...(typeof outputTokens === "number" ? { outputTokens } : {}),
|
|
1371
|
-
...(typeof totalCostUsd === "number" ? { totalCostUsd } : {}),
|
|
1372
|
-
...(contextTokens !== undefined ? { contextWindowUsedTokens: contextTokens } : {}),
|
|
1373
|
-
...(contextWindow !== undefined ? { contextWindowMaxTokens: contextWindow } : {}),
|
|
1374
|
-
};
|
|
1375
|
-
return Object.keys(usage).length > 0 ? usage : undefined;
|
|
1376
|
-
}
|
|
1377
|
-
|
|
1378
|
-
private ownsUsageSample(
|
|
1379
|
-
turn: ActiveTurn,
|
|
1380
|
-
generation: number,
|
|
1381
|
-
runtime: OmpRuntimeSession,
|
|
1382
|
-
): boolean {
|
|
1383
|
-
return (
|
|
1384
|
-
!this.closed &&
|
|
1385
|
-
!this.runtimeDead &&
|
|
1386
|
-
!turn.terminal &&
|
|
1387
|
-
generation === this.generation &&
|
|
1388
|
-
runtime === this.runtime &&
|
|
1389
|
-
this.activeTurn === turn
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
private publishUsageSnapshot(
|
|
1394
|
-
turn: ActiveTurn,
|
|
1395
|
-
minimumEpoch = this.usageEpoch,
|
|
1396
|
-
minimumSequence = turn.usageSampleFloor,
|
|
1397
|
-
): Promise<OmpSessionState | undefined> {
|
|
1398
|
-
const generation = turn.generation;
|
|
1399
|
-
const runtime = this.runtime;
|
|
1400
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return Promise.resolve(undefined);
|
|
1401
|
-
const current = this.usageSample;
|
|
1402
|
-
if (current) {
|
|
1403
|
-
if (
|
|
1404
|
-
current.turn === turn &&
|
|
1405
|
-
current.generation === generation &&
|
|
1406
|
-
current.runtime === runtime &&
|
|
1407
|
-
current.epoch >= minimumEpoch &&
|
|
1408
|
-
current.sequence >= minimumSequence
|
|
1409
|
-
) {
|
|
1410
|
-
return current.promise;
|
|
1411
|
-
}
|
|
1412
|
-
return current.promise.then(() => {
|
|
1413
|
-
if (!this.ownsUsageSample(turn, generation, runtime)) return undefined;
|
|
1414
|
-
return this.publishUsageSnapshot(turn, minimumEpoch, minimumSequence);
|
|
1415
|
-
});
|
|
1416
|
-
}
|
|
1417
|
-
const epoch = this.usageEpoch;
|
|
1418
|
-
const sequence = ++this.usageSequence;
|
|
1419
|
-
const promise = Promise.allSettled([runtime.getState(), runtime.getSessionStats()]).then(
|
|
1420
|
-
([stateResult, statsResult]) => {
|
|
1421
|
-
if (
|
|
1422
|
-
!this.ownsUsageSample(turn, generation, runtime) ||
|
|
1423
|
-
epoch !== this.usageEpoch ||
|
|
1424
|
-
epoch < minimumEpoch ||
|
|
1425
|
-
sequence < turn.usageSampleFloor ||
|
|
1426
|
-
sequence < minimumSequence
|
|
1427
|
-
) {
|
|
1428
|
-
return undefined;
|
|
1429
|
-
}
|
|
1430
|
-
const state = stateResult.status === "fulfilled" ? stateResult.value : undefined;
|
|
1431
|
-
const stats = statsResult.status === "fulfilled" ? statsResult.value : undefined;
|
|
1432
|
-
const usage = this.usageFrom(state, stats);
|
|
1433
|
-
if (usage) {
|
|
1434
|
-
this.lastUsage = usage;
|
|
1435
|
-
this.emit({ type: "session.usage", sessionId: this.id, turnId: turn.turnId, usage });
|
|
1436
|
-
}
|
|
1437
|
-
return state;
|
|
1438
|
-
},
|
|
1439
|
-
);
|
|
1440
|
-
this.usageSample = { turn, generation, runtime, epoch, sequence, promise };
|
|
1441
|
-
void promise.finally(() => {
|
|
1442
|
-
if (this.usageSample?.promise === promise) this.usageSample = null;
|
|
1443
|
-
});
|
|
1444
|
-
return promise;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
private async boundedUsageSnapshot(
|
|
1448
|
-
turn: ActiveTurn,
|
|
1449
|
-
timeoutMs: number,
|
|
1450
|
-
): Promise<OmpSessionState | undefined> {
|
|
1451
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
1452
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
1453
|
-
try {
|
|
1454
|
-
return await Promise.race([this.publishUsageSnapshot(turn), timeout.promise]);
|
|
1455
|
-
} finally {
|
|
1456
|
-
this.scheduler.clear(timer);
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
private async boundedTerminalState(
|
|
1461
|
-
turn: ActiveTurn,
|
|
1462
|
-
timeoutMs: number,
|
|
1463
|
-
): Promise<OmpSessionState | undefined> {
|
|
1464
|
-
const generation = turn.generation;
|
|
1465
|
-
const runtime = this.runtime;
|
|
1466
|
-
const timeout = Promise.withResolvers<undefined>();
|
|
1467
|
-
const timer = this.scheduler.set(() => timeout.resolve(undefined), timeoutMs);
|
|
1468
|
-
try {
|
|
1469
|
-
const state = await Promise.race([runtime.getState(), timeout.promise]);
|
|
1470
|
-
return this.ownsUsageSample(turn, generation, runtime) ? state : undefined;
|
|
1471
|
-
} catch {
|
|
1472
|
-
return undefined;
|
|
1473
|
-
} finally {
|
|
1474
|
-
this.scheduler.clear(timer);
|
|
1475
|
-
}
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
private isActiveTurn(turn: ActiveTurn): boolean {
|
|
1479
|
-
return (
|
|
1480
|
-
!this.closed &&
|
|
1481
|
-
!this.runtimeDead &&
|
|
1482
|
-
!turn.terminal &&
|
|
1483
|
-
!turn.terminalizing &&
|
|
1484
|
-
!turn.agentEndPending &&
|
|
1485
|
-
turn.generation === this.generation &&
|
|
1486
|
-
this.activeTurn === turn
|
|
1487
|
-
);
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
private scheduleUsagePoll(turn: ActiveTurn, delayMs = USAGE_POLL_MS): void {
|
|
1491
|
-
if (!this.isActiveTurn(turn) || turn.usagePollTimer !== undefined) return;
|
|
1492
|
-
turn.usagePollTimer = this.scheduler.set(() => {
|
|
1493
|
-
turn.usagePollTimer = undefined;
|
|
1494
|
-
this.pollUsage(turn);
|
|
1495
|
-
}, delayMs);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
private pollUsage(turn: ActiveTurn): void {
|
|
1499
|
-
if (!this.isActiveTurn(turn) || turn.usagePoll) return;
|
|
1500
|
-
const poll = this.publishUsageSnapshot(turn).then(() => undefined);
|
|
1501
|
-
turn.usagePoll = poll;
|
|
1502
|
-
void poll.finally(() => {
|
|
1503
|
-
if (turn.usagePoll === poll) turn.usagePoll = undefined;
|
|
1504
|
-
this.scheduleUsagePoll(turn);
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
private stopUsagePoll(turn: ActiveTurn): void {
|
|
1509
|
-
if (turn.usagePollTimer === undefined) return;
|
|
1510
|
-
this.scheduler.clear(turn.usagePollTimer);
|
|
1511
|
-
turn.usagePollTimer = undefined;
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
private startCompaction(turn: ActiveTurn, trigger: "auto" | "manual", action?: string): void {
|
|
1515
|
-
if (this.discardedCompactionEnds > 0) {
|
|
1516
|
-
this.discardedCompactionEnds += 1;
|
|
1517
|
-
return;
|
|
1518
|
-
}
|
|
1519
|
-
const active = this.activeCompaction;
|
|
1520
|
-
if (active && active.trigger === trigger && active.action === action) {
|
|
1521
|
-
active.retrying = false;
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
if (active) {
|
|
1525
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
1526
|
-
this.discardedCompactionEnds = 2;
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
const operation: ActiveCompaction = {
|
|
1530
|
-
id: randomUUID(),
|
|
1531
|
-
trigger,
|
|
1532
|
-
turnId: turn.turnId,
|
|
1533
|
-
generation: turn.generation,
|
|
1534
|
-
action,
|
|
1535
|
-
retrying: false,
|
|
1536
|
-
preTokens: this.lastUsage?.contextWindowUsedTokens,
|
|
1537
|
-
};
|
|
1538
|
-
this.activeCompaction = operation;
|
|
1539
|
-
this.projector.flush(true);
|
|
1540
|
-
this.emit({
|
|
1541
|
-
type: "timeline.item",
|
|
1542
|
-
sessionId: this.id,
|
|
1543
|
-
item: {
|
|
1544
|
-
id: operation.id,
|
|
1545
|
-
type: "compaction",
|
|
1546
|
-
status: "loading",
|
|
1547
|
-
trigger,
|
|
1548
|
-
...(operation.preTokens !== undefined ? { preTokens: operation.preTokens } : {}),
|
|
1549
|
-
},
|
|
1550
|
-
});
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
private finishCompaction(
|
|
1554
|
-
state: "completed" | "failed" | "canceled" | "skipped",
|
|
1555
|
-
options: { tokensBefore?: number | null; message?: string } = {},
|
|
1556
|
-
): void {
|
|
1557
|
-
const operation = this.activeCompaction;
|
|
1558
|
-
if (!operation) return;
|
|
1559
|
-
this.activeCompaction = null;
|
|
1560
|
-
this.projector.flush(true);
|
|
1561
|
-
if (state === "completed") {
|
|
1562
|
-
this.usageEpoch += 1;
|
|
1563
|
-
const staleSample = this.usageSample;
|
|
1564
|
-
if (staleSample && staleSample.epoch < this.usageEpoch) {
|
|
1565
|
-
this.usageSample = null;
|
|
1566
|
-
staleSample.turn.usagePoll = undefined;
|
|
1567
|
-
}
|
|
1568
|
-
this.lastUsage = null;
|
|
1569
|
-
}
|
|
1570
|
-
if (state !== "completed") {
|
|
1571
|
-
const defaultMessage =
|
|
1572
|
-
state === "failed"
|
|
1573
|
-
? "OMP compaction failed"
|
|
1574
|
-
: state === "canceled"
|
|
1575
|
-
? "OMP compaction canceled"
|
|
1576
|
-
: "OMP compaction skipped";
|
|
1577
|
-
this.emit({
|
|
1578
|
-
type: "timeline.item",
|
|
1579
|
-
sessionId: this.id,
|
|
1580
|
-
item: {
|
|
1581
|
-
id: operation.id,
|
|
1582
|
-
type: "notification",
|
|
1583
|
-
level: state === "failed" ? "error" : "info",
|
|
1584
|
-
message: this.dataFilter.text(options.message ?? defaultMessage, 4_096),
|
|
1585
|
-
},
|
|
1586
|
-
});
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
const tokensBefore = options.tokensBefore ?? operation.preTokens;
|
|
1590
|
-
this.emit({
|
|
1591
|
-
type: "timeline.item",
|
|
1592
|
-
sessionId: this.id,
|
|
1593
|
-
item: {
|
|
1594
|
-
id: operation.id,
|
|
1595
|
-
type: "compaction",
|
|
1596
|
-
status: "completed",
|
|
1597
|
-
trigger: operation.trigger,
|
|
1598
|
-
...(tokensBefore !== undefined ? { preTokens: tokensBefore } : {}),
|
|
1599
|
-
},
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
|
|
1603
|
-
private retireCompaction(message: string): void {
|
|
1604
|
-
const operation = this.activeCompaction;
|
|
1605
|
-
if (!operation) return;
|
|
1606
|
-
this.activeCompaction = null;
|
|
1607
|
-
this.emit({
|
|
1608
|
-
type: "timeline.item",
|
|
1609
|
-
sessionId: this.id,
|
|
1610
|
-
item: {
|
|
1611
|
-
id: operation.id,
|
|
1612
|
-
type: "compaction",
|
|
1613
|
-
status: "completed",
|
|
1614
|
-
trigger: operation.trigger,
|
|
1615
|
-
},
|
|
1616
|
-
});
|
|
1617
|
-
this.emit({
|
|
1618
|
-
type: "timeline.item",
|
|
1619
|
-
sessionId: this.id,
|
|
1620
|
-
item: { id: `${operation.id}:error`, type: "error", message },
|
|
1621
|
-
});
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
748
|
private async replayHistory(preferPersistedTranscript = false): Promise<void> {
|
|
1625
749
|
if (!this.runtime.canReplayHistory) {
|
|
1626
750
|
throw new OmpPublicError("OMP session history cannot be replayed safely");
|
|
@@ -1685,7 +809,7 @@ export class OmpProviderSession {
|
|
|
1685
809
|
});
|
|
1686
810
|
}
|
|
1687
811
|
}
|
|
1688
|
-
messages ??= await waitForReplay(this.runtime.getMessages(), replay.signal);
|
|
812
|
+
messages ??= await waitForReplay(this.runtime.getMessages(replay.signal), replay.signal);
|
|
1689
813
|
this.quarantineBranchEntries();
|
|
1690
814
|
for (const message of messages) {
|
|
1691
815
|
replay.signal.throwIfAborted();
|
|
@@ -1858,7 +982,7 @@ export class OmpProviderSession {
|
|
|
1858
982
|
const configRefresh = this.configRefreshInFlight;
|
|
1859
983
|
this.cancelConfigRefreshRetry();
|
|
1860
984
|
this.lifetime.abort(new Error(message));
|
|
1861
|
-
this.resolveAllPermissions(true);
|
|
985
|
+
this.permissions.resolveAllPermissions(true);
|
|
1862
986
|
this.subsessions?.close();
|
|
1863
987
|
this.projector.close();
|
|
1864
988
|
this.unsubscribe();
|
|
@@ -2013,8 +1137,8 @@ export class OmpProviderSession {
|
|
|
2013
1137
|
turn.starting = false;
|
|
2014
1138
|
turn.acknowledged = true;
|
|
2015
1139
|
this.startTurn(turn, false);
|
|
2016
|
-
this.
|
|
2017
|
-
this.
|
|
1140
|
+
this.compaction.start(turn, "manual");
|
|
1141
|
+
this.usage.poll(turn);
|
|
2018
1142
|
const bufferedEvents = turn.bufferedEvents.splice(0);
|
|
2019
1143
|
turn.replayingBufferedEvents = true;
|
|
2020
1144
|
for (const event of bufferedEvents) this.handleTurnEvent(turn, event);
|
|
@@ -2105,8 +1229,8 @@ export class OmpProviderSession {
|
|
|
2105
1229
|
else {
|
|
2106
1230
|
turn.steerReady.resolve();
|
|
2107
1231
|
turn.terminal = true;
|
|
2108
|
-
this.
|
|
2109
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
1232
|
+
this.compaction.finish("failed", { message: "OMP prompt failed" });
|
|
1233
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
2110
1234
|
this.projector.finishTurn(turn.turnId);
|
|
2111
1235
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
2112
1236
|
}
|
|
@@ -2278,7 +1402,7 @@ export class OmpProviderSession {
|
|
|
2278
1402
|
const result = await compaction;
|
|
2279
1403
|
turn.manualCompactionPending = false;
|
|
2280
1404
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
2281
|
-
this.
|
|
1405
|
+
this.compaction.finish("completed", { tokensBefore: result.tokensBefore });
|
|
2282
1406
|
await this.finishTurn(turn, turn.interrupted ? "canceled" : "completed");
|
|
2283
1407
|
} catch (error) {
|
|
2284
1408
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
@@ -2286,7 +1410,7 @@ export class OmpProviderSession {
|
|
|
2286
1410
|
const raw = providerError(error, "OMP compaction failed");
|
|
2287
1411
|
const failure = { message: this.dataFilter.text(raw.message, 4_096) };
|
|
2288
1412
|
const state = turn.interrupted ? "canceled" : "failed";
|
|
2289
|
-
this.
|
|
1413
|
+
this.compaction.finish(state, { message: failure.message });
|
|
2290
1414
|
await this.finishTurn(turn, state, state === "failed" ? failure : undefined, true, true);
|
|
2291
1415
|
}
|
|
2292
1416
|
}
|
|
@@ -2307,7 +1431,7 @@ export class OmpProviderSession {
|
|
|
2307
1431
|
}
|
|
2308
1432
|
const forceTerminal = turn.awaitingPermissionEvidence;
|
|
2309
1433
|
turn.awaitingPermissionEvidence = false;
|
|
2310
|
-
if (forceTerminal) this.resolveTurnPermissions(turn.turnId);
|
|
1434
|
+
if (forceTerminal) this.permissions.resolveTurnPermissions(turn.turnId);
|
|
2311
1435
|
const pending = this.activeAbort;
|
|
2312
1436
|
if (turn.interrupted) {
|
|
2313
1437
|
if (pending?.turn === turn) await this.settleInterrupt(input.requestId, pending);
|
|
@@ -2718,118 +1842,7 @@ export class OmpProviderSession {
|
|
|
2718
1842
|
}
|
|
2719
1843
|
|
|
2720
1844
|
async permission(input: SessionPermissionInput): Promise<void> {
|
|
2721
|
-
|
|
2722
|
-
if (typed) {
|
|
2723
|
-
await this.respondToToolPermission(input, typed);
|
|
2724
|
-
return;
|
|
2725
|
-
}
|
|
2726
|
-
const pending = this.pendingPermissions.get(input.permissionId);
|
|
2727
|
-
if (!pending) throw new OmpPublicError("Unknown OMP permission request");
|
|
2728
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
2729
|
-
this.pendingPermissions.delete(input.permissionId);
|
|
2730
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2731
|
-
this.emit({
|
|
2732
|
-
type: "session.permission_resolved",
|
|
2733
|
-
sessionId: this.id,
|
|
2734
|
-
permissionId: input.permissionId,
|
|
2735
|
-
});
|
|
2736
|
-
throw new OmpPublicError("OMP permission request is no longer active");
|
|
2737
|
-
}
|
|
2738
|
-
const { generation, runtime } = pending;
|
|
2739
|
-
this.pendingPermissions.delete(input.permissionId);
|
|
2740
|
-
this.inFlightPermissions.set(input.permissionId, pending);
|
|
2741
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2742
|
-
try {
|
|
2743
|
-
const freeformValue = this.freeformSelection(pending, input.response);
|
|
2744
|
-
if (freeformValue !== undefined) {
|
|
2745
|
-
this.pendingFreeformSelection = {
|
|
2746
|
-
value: freeformValue,
|
|
2747
|
-
nativeSelectId: pending.nativeId,
|
|
2748
|
-
generation,
|
|
2749
|
-
runtime,
|
|
2750
|
-
...(pending.turnId ? { turnId: pending.turnId } : {}),
|
|
2751
|
-
};
|
|
2752
|
-
}
|
|
2753
|
-
const response = this.extensionUiResponse(pending, input.response);
|
|
2754
|
-
await runtime.respondToExtensionUi(response);
|
|
2755
|
-
} catch (error) {
|
|
2756
|
-
if (this.pendingFreeformSelection?.nativeSelectId === pending.nativeId) {
|
|
2757
|
-
this.pendingFreeformSelection = null;
|
|
2758
|
-
}
|
|
2759
|
-
if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
|
|
2760
|
-
this.inFlightPermissions.delete(input.permissionId);
|
|
2761
|
-
if (
|
|
2762
|
-
this.permissionOwnerIsCurrent(pending) &&
|
|
2763
|
-
generation === this.generation &&
|
|
2764
|
-
runtime === this.runtime
|
|
2765
|
-
) {
|
|
2766
|
-
this.pendingPermissions.set(input.permissionId, pending);
|
|
2767
|
-
this.armPermissionTimeout(input.permissionId, pending);
|
|
2768
|
-
throw error;
|
|
2769
|
-
}
|
|
2770
|
-
return;
|
|
2771
|
-
}
|
|
2772
|
-
if (this.inFlightPermissions.get(input.permissionId) !== pending) return;
|
|
2773
|
-
this.inFlightPermissions.delete(input.permissionId);
|
|
2774
|
-
this.emit({
|
|
2775
|
-
type: "session.permission_resolved",
|
|
2776
|
-
sessionId: this.id,
|
|
2777
|
-
permissionId: input.permissionId,
|
|
2778
|
-
});
|
|
2779
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
2780
|
-
}
|
|
2781
|
-
private async respondToToolPermission(
|
|
2782
|
-
input: SessionPermissionInput,
|
|
2783
|
-
pending: PendingToolPermission,
|
|
2784
|
-
): Promise<void> {
|
|
2785
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
2786
|
-
this.pendingToolPermissions.delete(input.permissionId);
|
|
2787
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2788
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2789
|
-
this.emit({
|
|
2790
|
-
type: "session.permission_resolved",
|
|
2791
|
-
sessionId: this.id,
|
|
2792
|
-
permissionId: input.permissionId,
|
|
2793
|
-
});
|
|
2794
|
-
throw new OmpPublicError("OMP permission request is no longer active");
|
|
2795
|
-
}
|
|
2796
|
-
if (
|
|
2797
|
-
input.response.selectedActionId !== undefined &&
|
|
2798
|
-
input.response.selectedActionId !== input.response.behavior
|
|
2799
|
-
) {
|
|
2800
|
-
throw new OmpPublicError("OMP permission action is invalid");
|
|
2801
|
-
}
|
|
2802
|
-
this.pendingToolPermissions.delete(input.permissionId);
|
|
2803
|
-
this.inFlightToolPermissions.set(input.permissionId, pending);
|
|
2804
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
2805
|
-
try {
|
|
2806
|
-
await pending.runtime.respondToToolApproval({
|
|
2807
|
-
type: "tool_approval_response",
|
|
2808
|
-
id: pending.nativeId,
|
|
2809
|
-
toolCallId: pending.toolCallId,
|
|
2810
|
-
approved: input.response.behavior === "allow",
|
|
2811
|
-
});
|
|
2812
|
-
} catch (error) {
|
|
2813
|
-
if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
|
|
2814
|
-
this.inFlightToolPermissions.delete(input.permissionId);
|
|
2815
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2816
|
-
this.emit({
|
|
2817
|
-
type: "session.permission_resolved",
|
|
2818
|
-
sessionId: this.id,
|
|
2819
|
-
permissionId: input.permissionId,
|
|
2820
|
-
});
|
|
2821
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
2822
|
-
throw error;
|
|
2823
|
-
}
|
|
2824
|
-
if (this.inFlightToolPermissions.get(input.permissionId) !== pending) return;
|
|
2825
|
-
this.inFlightToolPermissions.delete(input.permissionId);
|
|
2826
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
2827
|
-
this.emit({
|
|
2828
|
-
type: "session.permission_resolved",
|
|
2829
|
-
sessionId: this.id,
|
|
2830
|
-
permissionId: input.permissionId,
|
|
2831
|
-
});
|
|
2832
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
1845
|
+
await this.permissions.respond(input);
|
|
2833
1846
|
}
|
|
2834
1847
|
|
|
2835
1848
|
close(input?: SessionCloseInput): Promise<void> {
|
|
@@ -2863,12 +1876,12 @@ export class OmpProviderSession {
|
|
|
2863
1876
|
if (turn.started) await this.finishTurn(turn, "canceled", undefined, true, true);
|
|
2864
1877
|
else {
|
|
2865
1878
|
this.settleUnstartedTurn(turn);
|
|
2866
|
-
this.
|
|
1879
|
+
this.compaction.finish("canceled");
|
|
2867
1880
|
}
|
|
2868
1881
|
}
|
|
2869
1882
|
this.imageMaterializer.clear();
|
|
2870
|
-
this.
|
|
2871
|
-
this.resolveAllPermissions(true);
|
|
1883
|
+
this.compaction.finish("canceled");
|
|
1884
|
+
this.permissions.resolveAllPermissions(true);
|
|
2872
1885
|
this.closed = true;
|
|
2873
1886
|
this.configRefreshAttempts = 0;
|
|
2874
1887
|
this.configRefreshDirty = false;
|
|
@@ -3027,7 +2040,7 @@ export class OmpProviderSession {
|
|
|
3027
2040
|
}
|
|
3028
2041
|
if (this.subsessions) await recovered.setSubagentSubscription("events");
|
|
3029
2042
|
this.generation += 1;
|
|
3030
|
-
this.
|
|
2043
|
+
this.usage.clearLatest();
|
|
3031
2044
|
this.runtimeDead = null;
|
|
3032
2045
|
this.runtimeDisposal = null;
|
|
3033
2046
|
unsubscribeBootstrap();
|
|
@@ -3193,7 +2206,7 @@ export class OmpProviderSession {
|
|
|
3193
2206
|
this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval request");
|
|
3194
2207
|
return;
|
|
3195
2208
|
}
|
|
3196
|
-
this.publishToolPermission(event);
|
|
2209
|
+
this.permissions.publishToolPermission(event);
|
|
3197
2210
|
return;
|
|
3198
2211
|
}
|
|
3199
2212
|
if (event.type === "tool_approval_cancel") {
|
|
@@ -3201,18 +2214,17 @@ export class OmpProviderSession {
|
|
|
3201
2214
|
this.handleRuntimeFailure("OMP emitted an unnegotiated tool approval cancellation");
|
|
3202
2215
|
return;
|
|
3203
2216
|
}
|
|
3204
|
-
this.cancelToolPermission(event);
|
|
2217
|
+
this.permissions.cancelToolPermission(event);
|
|
3205
2218
|
return;
|
|
3206
2219
|
}
|
|
3207
2220
|
if (event.type === "extension_ui_request") {
|
|
3208
2221
|
if (event.method === "cancel") {
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
}
|
|
3212
|
-
this.resolvePermissionByNativeId(event.targetId ?? "");
|
|
2222
|
+
this.permissions.clearPendingFreeformSelection(event.targetId);
|
|
2223
|
+
this.permissions.resolvePermissionByNativeId(event.targetId ?? "");
|
|
3213
2224
|
return;
|
|
3214
2225
|
}
|
|
3215
|
-
if (event.method === "input" && this.submitPendingFreeformSelection(event))
|
|
2226
|
+
if (event.method === "input" && this.permissions.submitPendingFreeformSelection(event))
|
|
2227
|
+
return;
|
|
3216
2228
|
if (
|
|
3217
2229
|
event.method === "select" ||
|
|
3218
2230
|
event.method === "confirm" ||
|
|
@@ -3223,7 +2235,7 @@ export class OmpProviderSession {
|
|
|
3223
2235
|
this.handleRuntimeFailure();
|
|
3224
2236
|
return;
|
|
3225
2237
|
}
|
|
3226
|
-
this.publishPermission(event);
|
|
2238
|
+
this.permissions.publishPermission(event);
|
|
3227
2239
|
return;
|
|
3228
2240
|
}
|
|
3229
2241
|
|
|
@@ -3234,6 +2246,17 @@ export class OmpProviderSession {
|
|
|
3234
2246
|
this.handleRuntimeFailure();
|
|
3235
2247
|
return;
|
|
3236
2248
|
}
|
|
2249
|
+
if (
|
|
2250
|
+
event.type === "config_warnings_changed" ||
|
|
2251
|
+
event.type === "advisor_cost_changed" ||
|
|
2252
|
+
event.type === "ttsr_triggered"
|
|
2253
|
+
) {
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
if (event.type === "irc_message") {
|
|
2257
|
+
this.projector.projectPassive(event);
|
|
2258
|
+
return;
|
|
2259
|
+
}
|
|
3237
2260
|
if (
|
|
3238
2261
|
event.type === "notice" ||
|
|
3239
2262
|
event.type === "todo_reminder" ||
|
|
@@ -3363,50 +2386,20 @@ export class OmpProviderSession {
|
|
|
3363
2386
|
if (event.type === "auto_compaction_start") {
|
|
3364
2387
|
turn.nativeActivity = true;
|
|
3365
2388
|
this.cancelLocalOnlyCompletion(turn);
|
|
3366
|
-
this.
|
|
2389
|
+
this.compaction.start(turn, "auto", event.action);
|
|
3367
2390
|
return;
|
|
3368
2391
|
}
|
|
3369
2392
|
if (event.type === "auto_compaction_end") {
|
|
3370
|
-
if (this.
|
|
3371
|
-
this.
|
|
3372
|
-
return;
|
|
3373
|
-
}
|
|
3374
|
-
const operation = this.activeCompaction;
|
|
3375
|
-
if (
|
|
3376
|
-
operation?.trigger !== "auto" ||
|
|
3377
|
-
operation.turnId !== turn.turnId ||
|
|
3378
|
-
operation.generation !== turn.generation
|
|
3379
|
-
) {
|
|
3380
|
-
return;
|
|
3381
|
-
}
|
|
3382
|
-
if (event.action !== undefined && operation.action !== event.action) {
|
|
3383
|
-
this.retireCompaction("OMP emitted overlapping compactions");
|
|
3384
|
-
this.discardedCompactionEnds = 1;
|
|
3385
|
-
return;
|
|
2393
|
+
if (this.compaction.handleAutoEnd(turn, event)) {
|
|
2394
|
+
this.usage.schedulePoll(turn, USAGE_REFRESH_MS);
|
|
3386
2395
|
}
|
|
3387
|
-
if (event.willRetry) {
|
|
3388
|
-
operation.retrying = true;
|
|
3389
|
-
return;
|
|
3390
|
-
}
|
|
3391
|
-
const state = event.aborted
|
|
3392
|
-
? "canceled"
|
|
3393
|
-
: event.errorMessage
|
|
3394
|
-
? "failed"
|
|
3395
|
-
: event.skipped
|
|
3396
|
-
? "skipped"
|
|
3397
|
-
: "completed";
|
|
3398
|
-
this.finishCompaction(state, {
|
|
3399
|
-
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
3400
|
-
message: event.errorMessage,
|
|
3401
|
-
});
|
|
3402
|
-
this.scheduleUsagePoll(turn, USAGE_REFRESH_MS);
|
|
3403
2396
|
return;
|
|
3404
2397
|
}
|
|
3405
2398
|
if (event.type === "agent_end" && turn.manualCompactionPending) return;
|
|
3406
2399
|
if (event.type === "tool_execution_start") turn.activeToolCallIds.add(event.toolCallId);
|
|
3407
2400
|
if (event.type === "tool_execution_end") {
|
|
3408
2401
|
turn.activeToolCallIds.delete(event.toolCallId);
|
|
3409
|
-
if (event.toolName === "ask_user") this.
|
|
2402
|
+
if (event.toolName === "ask_user") this.permissions.clearPendingFreeformSelection();
|
|
3410
2403
|
}
|
|
3411
2404
|
if (event.type === "tool_execution_start" || event.type === "tool_execution_end") {
|
|
3412
2405
|
try {
|
|
@@ -3681,24 +2674,6 @@ export class OmpProviderSession {
|
|
|
3681
2674
|
});
|
|
3682
2675
|
}
|
|
3683
2676
|
|
|
3684
|
-
private submitPendingFreeformSelection(
|
|
3685
|
-
request: Extract<OmpQuestionRequest, { method: "input" }>,
|
|
3686
|
-
): boolean {
|
|
3687
|
-
const pending = this.pendingFreeformSelection;
|
|
3688
|
-
if (!pending) return false;
|
|
3689
|
-
this.pendingFreeformSelection = null;
|
|
3690
|
-
if (
|
|
3691
|
-
pending.generation !== this.generation ||
|
|
3692
|
-
pending.runtime !== this.runtime ||
|
|
3693
|
-
(pending.turnId !== undefined && this.activeTurn?.turnId !== pending.turnId)
|
|
3694
|
-
) {
|
|
3695
|
-
return false;
|
|
3696
|
-
}
|
|
3697
|
-
void pending.runtime
|
|
3698
|
-
.respondToExtensionUi({ type: "extension_ui_response", id: request.id, value: pending.value })
|
|
3699
|
-
.catch(() => this.handleRuntimeFailure("OMP freeform response failed"));
|
|
3700
|
-
return true;
|
|
3701
|
-
}
|
|
3702
2677
|
private replaceSlashCommands(commands: OmpAvailableCommand[]): void {
|
|
3703
2678
|
this.slashCommands.clear();
|
|
3704
2679
|
for (const command of [...OMP_BUILTIN_COMMANDS, ...commands]) {
|
|
@@ -3730,687 +2705,11 @@ export class OmpProviderSession {
|
|
|
3730
2705
|
}),
|
|
3731
2706
|
});
|
|
3732
2707
|
}
|
|
3733
|
-
private toolPermissionDetail(
|
|
3734
|
-
request: OmpToolApprovalRequest,
|
|
3735
|
-
): ProviderToolCallDetail | undefined {
|
|
3736
|
-
switch (request.identity.kind) {
|
|
3737
|
-
case "shell":
|
|
3738
|
-
return {
|
|
3739
|
-
type: "shell",
|
|
3740
|
-
command: this.dataFilter.text(request.identity.command, 24 * 1024),
|
|
3741
|
-
};
|
|
3742
|
-
case "edit":
|
|
3743
|
-
return {
|
|
3744
|
-
type: "edit",
|
|
3745
|
-
filePath: this.dataFilter.text(request.identity.paths[0] ?? "", 4_096),
|
|
3746
|
-
newString: this.dataFilter.text(request.identity.content, 20 * 1024),
|
|
3747
|
-
};
|
|
3748
|
-
case "write":
|
|
3749
|
-
return {
|
|
3750
|
-
type: "write",
|
|
3751
|
-
filePath: this.dataFilter.text(request.identity.path, 4_096),
|
|
3752
|
-
content: this.dataFilter.text(request.identity.content, 20 * 1024),
|
|
3753
|
-
};
|
|
3754
|
-
case "other":
|
|
3755
|
-
return;
|
|
3756
|
-
}
|
|
3757
|
-
}
|
|
3758
|
-
|
|
3759
|
-
private publishToolPermission(request: OmpToolApprovalRequest): void {
|
|
3760
|
-
const fingerprint = createHash("sha256").update(JSON.stringify(request)).digest("base64url");
|
|
3761
|
-
if (this.resolvedToolApprovalIds.has(request.id)) {
|
|
3762
|
-
this.handleRuntimeFailure("OMP reused a resolved tool approval identifier");
|
|
3763
|
-
return;
|
|
3764
|
-
}
|
|
3765
|
-
for (const pending of [
|
|
3766
|
-
...this.pendingToolPermissions.values(),
|
|
3767
|
-
...this.inFlightToolPermissions.values(),
|
|
3768
|
-
]) {
|
|
3769
|
-
if (pending.nativeId !== request.id) continue;
|
|
3770
|
-
if (pending.fingerprint !== fingerprint || pending.toolCallId !== request.toolCallId) {
|
|
3771
|
-
this.handleRuntimeFailure("OMP changed a pending tool approval request");
|
|
3772
|
-
}
|
|
3773
|
-
return;
|
|
3774
|
-
}
|
|
3775
|
-
const pendingCount =
|
|
3776
|
-
this.pendingPermissions.size +
|
|
3777
|
-
this.inFlightPermissions.size +
|
|
3778
|
-
this.pendingToolPermissions.size +
|
|
3779
|
-
this.inFlightToolPermissions.size;
|
|
3780
|
-
if (pendingCount >= MAX_PENDING_PERMISSIONS) {
|
|
3781
|
-
this.resolvedToolApprovalIds.add(request.id);
|
|
3782
|
-
void this.runtime
|
|
3783
|
-
.respondToToolApproval({
|
|
3784
|
-
type: "tool_approval_response",
|
|
3785
|
-
id: request.id,
|
|
3786
|
-
toolCallId: request.toolCallId,
|
|
3787
|
-
cancelled: true,
|
|
3788
|
-
})
|
|
3789
|
-
.catch(() => this.handleRuntimeFailure());
|
|
3790
|
-
return;
|
|
3791
|
-
}
|
|
3792
|
-
|
|
3793
|
-
this.permissionSequence += 1;
|
|
3794
|
-
const permissionId = `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
|
|
3795
|
-
const detail = this.toolPermissionDetail(request);
|
|
3796
|
-
const filteredInput = this.dataFilter.json(request.input, 8 * 1024, 32 * 1024);
|
|
3797
|
-
const input =
|
|
3798
|
-
filteredInput && typeof filteredInput === "object" && !Array.isArray(filteredInput)
|
|
3799
|
-
? filteredInput
|
|
3800
|
-
: {};
|
|
3801
|
-
const descriptionParts = [
|
|
3802
|
-
request.detail.reason,
|
|
3803
|
-
...request.detail.lines,
|
|
3804
|
-
...(request.detail.providerSafetyChecks ?? []),
|
|
3805
|
-
].filter((part): part is string => Boolean(part));
|
|
3806
|
-
const publicRequest = {
|
|
3807
|
-
id: permissionId,
|
|
3808
|
-
name: `omp.${this.dataFilter.text(request.toolName, 256)}`,
|
|
3809
|
-
kind: "tool" as const,
|
|
3810
|
-
title: `Allow ${this.dataFilter.text(request.toolName, 256)}?`,
|
|
3811
|
-
...(descriptionParts.length > 0
|
|
3812
|
-
? { description: this.dataFilter.text(descriptionParts.join("\n"), 16 * 1024) }
|
|
3813
|
-
: {}),
|
|
3814
|
-
input: {
|
|
3815
|
-
...input,
|
|
3816
|
-
identity: this.dataFilter.json(request.identity, 20 * 1024, 32 * 1024),
|
|
3817
|
-
tier: request.tier,
|
|
3818
|
-
toolCallId: request.toolCallId,
|
|
3819
|
-
},
|
|
3820
|
-
...(detail ? { detail } : {}),
|
|
3821
|
-
actions: [
|
|
3822
|
-
{ id: "allow", label: "Allow", behavior: "allow" as const, variant: "primary" as const },
|
|
3823
|
-
{ id: "deny", label: "Deny", behavior: "deny" as const, variant: "danger" as const },
|
|
3824
|
-
],
|
|
3825
|
-
metadata: {
|
|
3826
|
-
tier: request.tier,
|
|
3827
|
-
redacted: request.detail.redacted,
|
|
3828
|
-
truncated: request.detail.truncated,
|
|
3829
|
-
redactedFields: request.detail.redactedFields,
|
|
3830
|
-
truncatedFields: request.detail.truncatedFields,
|
|
3831
|
-
},
|
|
3832
|
-
};
|
|
3833
|
-
const pending: PendingToolPermission = {
|
|
3834
|
-
nativeId: request.id,
|
|
3835
|
-
toolCallId: request.toolCallId,
|
|
3836
|
-
fingerprint,
|
|
3837
|
-
retainedBytes: boundedJsonBytes(
|
|
3838
|
-
publicRequest,
|
|
3839
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3840
|
-
512,
|
|
3841
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
3842
|
-
4_096,
|
|
3843
|
-
),
|
|
3844
|
-
generation: this.generation,
|
|
3845
|
-
runtime: this.runtime,
|
|
3846
|
-
...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
|
|
3847
|
-
...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
|
|
3848
|
-
};
|
|
3849
|
-
let retainedBytes = pending.retainedBytes;
|
|
3850
|
-
for (const item of this.pendingPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3851
|
-
for (const item of this.inFlightPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3852
|
-
for (const item of this.pendingToolPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3853
|
-
for (const item of this.inFlightToolPermissions.values()) retainedBytes += item.retainedBytes;
|
|
3854
|
-
if (
|
|
3855
|
-
pending.retainedBytes === Number.POSITIVE_INFINITY ||
|
|
3856
|
-
retainedBytes > MAX_PENDING_PERMISSION_BYTES
|
|
3857
|
-
) {
|
|
3858
|
-
void this.runtime
|
|
3859
|
-
.respondToToolApproval({
|
|
3860
|
-
type: "tool_approval_response",
|
|
3861
|
-
id: request.id,
|
|
3862
|
-
toolCallId: request.toolCallId,
|
|
3863
|
-
cancelled: true,
|
|
3864
|
-
})
|
|
3865
|
-
.catch(() => this.handleRuntimeFailure());
|
|
3866
|
-
this.resolvedToolApprovalIds.add(request.id);
|
|
3867
|
-
return;
|
|
3868
|
-
}
|
|
3869
|
-
this.pendingToolPermissions.set(permissionId, pending);
|
|
3870
|
-
this.armToolPermissionTimeout(permissionId, pending);
|
|
3871
|
-
this.projector.markAskPermissionRendered();
|
|
3872
|
-
if (this.activeTurn) {
|
|
3873
|
-
this.activeTurn.awaitingPermissionEvidence = true;
|
|
3874
|
-
this.markAgentEvidence(this.activeTurn);
|
|
3875
|
-
}
|
|
3876
|
-
this.emit({ type: "session.permission", sessionId: this.id, request: publicRequest });
|
|
3877
|
-
}
|
|
3878
|
-
|
|
3879
|
-
private cancelToolPermission(request: OmpToolApprovalCancel): void {
|
|
3880
|
-
for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
|
|
3881
|
-
for (const [permissionId, pending] of permissions) {
|
|
3882
|
-
if (pending.nativeId !== request.targetId) continue;
|
|
3883
|
-
if (pending.toolCallId !== request.toolCallId) {
|
|
3884
|
-
this.handleRuntimeFailure("OMP tool approval cancellation did not match its tool call");
|
|
3885
|
-
return;
|
|
3886
|
-
}
|
|
3887
|
-
permissions.delete(permissionId);
|
|
3888
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3889
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
3890
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3891
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
3892
|
-
return;
|
|
3893
|
-
}
|
|
3894
|
-
}
|
|
3895
|
-
if (!this.resolvedToolApprovalIds.has(request.targetId)) {
|
|
3896
|
-
this.handleRuntimeFailure("OMP canceled an unknown tool approval request");
|
|
3897
|
-
}
|
|
3898
|
-
}
|
|
3899
|
-
|
|
3900
|
-
private publishPermission(request: OmpQuestionRequest): void {
|
|
3901
|
-
if (request.method === "select" && !request.options?.length) {
|
|
3902
|
-
this.handleRuntimeFailure();
|
|
3903
|
-
return;
|
|
3904
|
-
}
|
|
3905
|
-
const fingerprint = permissionFingerprint(request);
|
|
3906
|
-
let existingId: string | undefined;
|
|
3907
|
-
let existingPending: PendingPermission | undefined;
|
|
3908
|
-
for (const [permissionId, pending] of this.pendingPermissions) {
|
|
3909
|
-
if (pending.nativeId !== request.id) continue;
|
|
3910
|
-
if (pending.fingerprint === fingerprint) {
|
|
3911
|
-
existingId = permissionId;
|
|
3912
|
-
existingPending = pending;
|
|
3913
|
-
} else {
|
|
3914
|
-
this.pendingPermissions.delete(permissionId);
|
|
3915
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
3916
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
3917
|
-
}
|
|
3918
|
-
break;
|
|
3919
|
-
}
|
|
3920
|
-
if (!existingId) {
|
|
3921
|
-
for (const pending of this.inFlightPermissions.values()) {
|
|
3922
|
-
if (pending.nativeId !== request.id) continue;
|
|
3923
|
-
if (pending.fingerprint !== fingerprint) this.handleRuntimeFailure();
|
|
3924
|
-
return;
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
|
-
if (
|
|
3928
|
-
!existingId &&
|
|
3929
|
-
this.pendingPermissions.size +
|
|
3930
|
-
this.inFlightPermissions.size +
|
|
3931
|
-
this.pendingToolPermissions.size +
|
|
3932
|
-
this.inFlightToolPermissions.size >=
|
|
3933
|
-
MAX_PENDING_PERMISSIONS
|
|
3934
|
-
) {
|
|
3935
|
-
this.rejectPermissionRequest(request, "Too many OMP questions are already pending");
|
|
3936
|
-
return;
|
|
3937
|
-
}
|
|
3938
|
-
if (existingPending?.timer !== undefined) this.scheduler.clear(existingPending.timer);
|
|
3939
|
-
if (!existingId) this.permissionSequence += 1;
|
|
3940
|
-
const id =
|
|
3941
|
-
existingId ?? `omp:permission:${this.permissionNamespace}:${this.permissionSequence}`;
|
|
3942
|
-
const header = this.dataFilter.text(request.title ?? "OMP question", 4_096);
|
|
3943
|
-
const freeformSentinel =
|
|
3944
|
-
request.method === "select" && request.options.includes(OMP_ASK_USER_FREEFORM_SENTINEL)
|
|
3945
|
-
? OMP_ASK_USER_FREEFORM_SENTINEL
|
|
3946
|
-
: undefined;
|
|
3947
|
-
const optionValues = new Map<string, string>();
|
|
3948
|
-
const displayValues = new Map<string, string>();
|
|
3949
|
-
const usedOptionLabels = new Set<string>();
|
|
3950
|
-
const optionDetails = request.method === "select" ? request.optionDetails : undefined;
|
|
3951
|
-
const selectableOptions =
|
|
3952
|
-
request.method === "select"
|
|
3953
|
-
? request.options.flatMap((nativeValue, index) =>
|
|
3954
|
-
nativeValue === freeformSentinel ? [] : [{ nativeValue, index }],
|
|
3955
|
-
)
|
|
3956
|
-
: [];
|
|
3957
|
-
const options =
|
|
3958
|
-
request.method === "select"
|
|
3959
|
-
? selectableOptions.map(({ nativeValue, index }) => {
|
|
3960
|
-
const baseLabel = this.dataFilter.text(nativeValue, 4_096);
|
|
3961
|
-
let label = baseLabel;
|
|
3962
|
-
let suffix = 2;
|
|
3963
|
-
while (usedOptionLabels.has(label)) {
|
|
3964
|
-
label = `${baseLabel} (${suffix})`;
|
|
3965
|
-
suffix += 1;
|
|
3966
|
-
}
|
|
3967
|
-
usedOptionLabels.add(label);
|
|
3968
|
-
const value = `${id}:option:${index}`;
|
|
3969
|
-
optionValues.set(value, nativeValue);
|
|
3970
|
-
displayValues.set(label, nativeValue);
|
|
3971
|
-
return {
|
|
3972
|
-
label,
|
|
3973
|
-
value,
|
|
3974
|
-
...(optionDetails?.[index]?.description
|
|
3975
|
-
? {
|
|
3976
|
-
description: this.dataFilter.text(
|
|
3977
|
-
optionDetails[index]?.description ?? "",
|
|
3978
|
-
16_384,
|
|
3979
|
-
),
|
|
3980
|
-
}
|
|
3981
|
-
: {}),
|
|
3982
|
-
};
|
|
3983
|
-
})
|
|
3984
|
-
: undefined;
|
|
3985
|
-
const questionOptions =
|
|
3986
|
-
request.method === "confirm" ? [{ label: "Yes" }, { label: "No" }] : (options ?? []);
|
|
3987
|
-
const actions =
|
|
3988
|
-
request.method === "select"
|
|
3989
|
-
? [
|
|
3990
|
-
...(options ?? []).map((option) => ({
|
|
3991
|
-
id: option.value,
|
|
3992
|
-
label: option.label,
|
|
3993
|
-
behavior: "allow" as const,
|
|
3994
|
-
variant: "secondary" as const,
|
|
3995
|
-
})),
|
|
3996
|
-
{
|
|
3997
|
-
id: "cancel",
|
|
3998
|
-
label: "Cancel",
|
|
3999
|
-
behavior: "deny" as const,
|
|
4000
|
-
variant: "secondary" as const,
|
|
4001
|
-
},
|
|
4002
|
-
]
|
|
4003
|
-
: [
|
|
4004
|
-
{
|
|
4005
|
-
id: "submit",
|
|
4006
|
-
label: request.method === "confirm" ? "Confirm" : "Submit",
|
|
4007
|
-
behavior: "allow" as const,
|
|
4008
|
-
variant: "primary" as const,
|
|
4009
|
-
},
|
|
4010
|
-
{
|
|
4011
|
-
id: "cancel",
|
|
4012
|
-
label: "Cancel",
|
|
4013
|
-
behavior: "deny" as const,
|
|
4014
|
-
variant: "secondary" as const,
|
|
4015
|
-
},
|
|
4016
|
-
];
|
|
4017
|
-
const pending: PendingPermission = {
|
|
4018
|
-
nativeId: request.id,
|
|
4019
|
-
header,
|
|
4020
|
-
fingerprint,
|
|
4021
|
-
optionValues,
|
|
4022
|
-
actionBehaviors: new Map(actions.map((action) => [action.id, action.behavior])),
|
|
4023
|
-
displayValues,
|
|
4024
|
-
generation: this.generation,
|
|
4025
|
-
runtime: this.runtime,
|
|
4026
|
-
request,
|
|
4027
|
-
retainedBytes: boundedJsonBytes(
|
|
4028
|
-
{ request, header, options: questionOptions, actions },
|
|
4029
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
4030
|
-
512,
|
|
4031
|
-
MAX_PENDING_PERMISSION_BYTES,
|
|
4032
|
-
4_096,
|
|
4033
|
-
),
|
|
4034
|
-
...(this.activeTurn ? { turnId: this.activeTurn.turnId } : {}),
|
|
4035
|
-
...(request.timeout !== undefined ? { expiresAt: Date.now() + request.timeout } : {}),
|
|
4036
|
-
...(freeformSentinel ? { freeformSentinel } : {}),
|
|
4037
|
-
};
|
|
4038
|
-
let retainedPermissionBytes = pending.retainedBytes;
|
|
4039
|
-
for (const [permissionId, retained] of this.pendingPermissions) {
|
|
4040
|
-
if (permissionId !== existingId) retainedPermissionBytes += retained.retainedBytes;
|
|
4041
|
-
}
|
|
4042
|
-
for (const retained of this.inFlightPermissions.values()) {
|
|
4043
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
4044
|
-
}
|
|
4045
|
-
for (const retained of this.pendingToolPermissions.values()) {
|
|
4046
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
4047
|
-
}
|
|
4048
|
-
for (const retained of this.inFlightToolPermissions.values()) {
|
|
4049
|
-
retainedPermissionBytes += retained.retainedBytes;
|
|
4050
|
-
}
|
|
4051
|
-
if (
|
|
4052
|
-
pending.retainedBytes === Number.POSITIVE_INFINITY ||
|
|
4053
|
-
retainedPermissionBytes > MAX_PENDING_PERMISSION_BYTES
|
|
4054
|
-
) {
|
|
4055
|
-
if (existingId) {
|
|
4056
|
-
this.pendingPermissions.delete(existingId);
|
|
4057
|
-
this.emit({
|
|
4058
|
-
type: "session.permission_resolved",
|
|
4059
|
-
sessionId: this.id,
|
|
4060
|
-
permissionId: existingId,
|
|
4061
|
-
});
|
|
4062
|
-
}
|
|
4063
|
-
this.rejectPermissionRequest(request, "OMP question data exceeded the pending input budget");
|
|
4064
|
-
return;
|
|
4065
|
-
}
|
|
4066
|
-
this.pendingPermissions.set(id, pending);
|
|
4067
|
-
this.armPermissionTimeout(id, pending);
|
|
4068
|
-
this.projector.markAskPermissionRendered();
|
|
4069
|
-
if (this.activeTurn) {
|
|
4070
|
-
this.activeTurn.awaitingPermissionEvidence = true;
|
|
4071
|
-
this.markAgentEvidence(this.activeTurn);
|
|
4072
|
-
}
|
|
4073
|
-
this.emit({
|
|
4074
|
-
type: "session.permission",
|
|
4075
|
-
sessionId: this.id,
|
|
4076
|
-
request: {
|
|
4077
|
-
id,
|
|
4078
|
-
name: `omp.${request.method}`,
|
|
4079
|
-
kind: "question",
|
|
4080
|
-
title: header,
|
|
4081
|
-
...(request.method === "confirm"
|
|
4082
|
-
? { description: this.dataFilter.text(request.message, 64 * 1024) }
|
|
4083
|
-
: {}),
|
|
4084
|
-
input: {
|
|
4085
|
-
questions: [
|
|
4086
|
-
{
|
|
4087
|
-
header,
|
|
4088
|
-
question: this.dataFilter.text(
|
|
4089
|
-
request.method === "confirm" ? request.message : request.title,
|
|
4090
|
-
64 * 1024,
|
|
4091
|
-
),
|
|
4092
|
-
options: questionOptions,
|
|
4093
|
-
multiSelect: false,
|
|
4094
|
-
...(freeformSentinel ? { allowOther: true } : {}),
|
|
4095
|
-
...(request.method === "input" && request.placeholder
|
|
4096
|
-
? { placeholder: this.dataFilter.text(request.placeholder, 4_096) }
|
|
4097
|
-
: {}),
|
|
4098
|
-
...((request.method === "input" || request.method === "editor") && request.prefill
|
|
4099
|
-
? { prefill: this.dataFilter.text(request.prefill) }
|
|
4100
|
-
: {}),
|
|
4101
|
-
},
|
|
4102
|
-
],
|
|
4103
|
-
},
|
|
4104
|
-
actions,
|
|
4105
|
-
},
|
|
4106
|
-
});
|
|
4107
|
-
}
|
|
4108
|
-
|
|
4109
|
-
private rejectPermissionRequest(request: OmpQuestionRequest, description: string): void {
|
|
4110
|
-
this.emit({
|
|
4111
|
-
type: "session.notice",
|
|
4112
|
-
sessionId: this.id,
|
|
4113
|
-
notice: {
|
|
4114
|
-
id: `omp:permission-rejected:${this.permissionSequence + 1}`,
|
|
4115
|
-
severity: "warning",
|
|
4116
|
-
title: "OMP question canceled",
|
|
4117
|
-
description,
|
|
4118
|
-
},
|
|
4119
|
-
});
|
|
4120
|
-
void this.runtime
|
|
4121
|
-
.respondToExtensionUi({ type: "extension_ui_response", id: request.id, cancelled: true })
|
|
4122
|
-
.catch(() => this.handleRuntimeFailure());
|
|
4123
|
-
}
|
|
4124
|
-
|
|
4125
|
-
private freeformSelection(
|
|
4126
|
-
pending: PendingPermission,
|
|
4127
|
-
response: ProviderPermissionResponse,
|
|
4128
|
-
): string | undefined {
|
|
4129
|
-
if (
|
|
4130
|
-
pending.request.method !== "select" ||
|
|
4131
|
-
!pending.freeformSentinel ||
|
|
4132
|
-
response.behavior !== "allow" ||
|
|
4133
|
-
response.selectedActionId !== undefined
|
|
4134
|
-
) {
|
|
4135
|
-
return;
|
|
4136
|
-
}
|
|
4137
|
-
const answers = response.updatedInput?.answers;
|
|
4138
|
-
const answer =
|
|
4139
|
-
answers && typeof answers === "object" && !Array.isArray(answers)
|
|
4140
|
-
? answers[pending.header]
|
|
4141
|
-
: undefined;
|
|
4142
|
-
const value = Array.isArray(answer) ? answer[0] : answer;
|
|
4143
|
-
if (
|
|
4144
|
-
typeof value !== "string" ||
|
|
4145
|
-
pending.optionValues.has(value) ||
|
|
4146
|
-
pending.displayValues.has(value)
|
|
4147
|
-
) {
|
|
4148
|
-
return;
|
|
4149
|
-
}
|
|
4150
|
-
if (
|
|
4151
|
-
value === pending.freeformSentinel ||
|
|
4152
|
-
value.trim().length === 0 ||
|
|
4153
|
-
value.includes("\0") ||
|
|
4154
|
-
utf8Bytes(value) > MAX_FREEFORM_RESPONSE_BYTES
|
|
4155
|
-
) {
|
|
4156
|
-
throw new OmpPublicError("OMP freeform response is invalid");
|
|
4157
|
-
}
|
|
4158
|
-
return value;
|
|
4159
|
-
}
|
|
4160
|
-
|
|
4161
|
-
private extensionUiResponse(
|
|
4162
|
-
pending: PendingPermission,
|
|
4163
|
-
response: ProviderPermissionResponse,
|
|
4164
|
-
): OmpExtensionUiResponse {
|
|
4165
|
-
if (response.selectedActionId !== undefined) {
|
|
4166
|
-
const expectedBehavior = pending.actionBehaviors.get(response.selectedActionId);
|
|
4167
|
-
if (expectedBehavior === undefined || expectedBehavior !== response.behavior) {
|
|
4168
|
-
throw new OmpPublicError("OMP permission action is invalid");
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4171
|
-
const { nativeId, request, header } = pending;
|
|
4172
|
-
if (request.method === "confirm") {
|
|
4173
|
-
if (response.behavior === "deny") {
|
|
4174
|
-
return { type: "extension_ui_response", id: nativeId, confirmed: false };
|
|
4175
|
-
}
|
|
4176
|
-
const answers = response.updatedInput?.answers;
|
|
4177
|
-
const answer =
|
|
4178
|
-
answers && typeof answers === "object" && !Array.isArray(answers)
|
|
4179
|
-
? answers[header]
|
|
4180
|
-
: undefined;
|
|
4181
|
-
return {
|
|
4182
|
-
type: "extension_ui_response",
|
|
4183
|
-
id: nativeId,
|
|
4184
|
-
confirmed: typeof answer === "string" ? /^yes$/iu.test(answer.trim()) : true,
|
|
4185
|
-
};
|
|
4186
|
-
}
|
|
4187
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4188
|
-
if (response.behavior === "deny") {
|
|
4189
|
-
return { type: "extension_ui_response", id: nativeId, cancelled: true };
|
|
4190
|
-
}
|
|
4191
|
-
const selectedValue = response.selectedActionId
|
|
4192
|
-
? pending.optionValues.get(response.selectedActionId)
|
|
4193
|
-
: undefined;
|
|
4194
|
-
if (selectedValue !== undefined) {
|
|
4195
|
-
return { type: "extension_ui_response", id: nativeId, value: selectedValue };
|
|
4196
|
-
}
|
|
4197
|
-
const answers = response.updatedInput?.answers;
|
|
4198
|
-
if (!answers || typeof answers !== "object" || Array.isArray(answers)) {
|
|
4199
|
-
throw new OmpPublicError("OMP question response requires answers");
|
|
4200
|
-
}
|
|
4201
|
-
const answer = answers[header];
|
|
4202
|
-
const publicValue = Array.isArray(answer) ? answer[0] : answer;
|
|
4203
|
-
if (typeof publicValue !== "string") {
|
|
4204
|
-
throw new OmpPublicError("OMP question response is invalid");
|
|
4205
|
-
}
|
|
4206
|
-
if (
|
|
4207
|
-
request.method === "select" &&
|
|
4208
|
-
pending.freeformSentinel &&
|
|
4209
|
-
publicValue !== pending.freeformSentinel &&
|
|
4210
|
-
!pending.displayValues.has(publicValue) &&
|
|
4211
|
-
!pending.optionValues.has(publicValue)
|
|
4212
|
-
) {
|
|
4213
|
-
if (
|
|
4214
|
-
publicValue.trim().length === 0 ||
|
|
4215
|
-
publicValue.includes("\0") ||
|
|
4216
|
-
utf8Bytes(publicValue) > MAX_FREEFORM_RESPONSE_BYTES
|
|
4217
|
-
) {
|
|
4218
|
-
throw new OmpPublicError("OMP freeform response is invalid");
|
|
4219
|
-
}
|
|
4220
|
-
this.pendingFreeformSelection = {
|
|
4221
|
-
value: publicValue,
|
|
4222
|
-
nativeSelectId: pending.nativeId,
|
|
4223
|
-
generation: pending.generation,
|
|
4224
|
-
runtime: pending.runtime,
|
|
4225
|
-
...(pending.turnId ? { turnId: pending.turnId } : {}),
|
|
4226
|
-
};
|
|
4227
|
-
return {
|
|
4228
|
-
type: "extension_ui_response",
|
|
4229
|
-
id: nativeId,
|
|
4230
|
-
value: pending.freeformSentinel,
|
|
4231
|
-
};
|
|
4232
|
-
}
|
|
4233
|
-
if (request.method === "select") {
|
|
4234
|
-
const mapped =
|
|
4235
|
-
pending.optionValues.get(publicValue) ?? pending.displayValues.get(publicValue);
|
|
4236
|
-
if (mapped === undefined) {
|
|
4237
|
-
throw new OmpPublicError("OMP selection response is invalid");
|
|
4238
|
-
}
|
|
4239
|
-
return { type: "extension_ui_response", id: nativeId, value: mapped };
|
|
4240
|
-
}
|
|
4241
|
-
return { type: "extension_ui_response", id: nativeId, value: publicValue };
|
|
4242
|
-
}
|
|
4243
|
-
|
|
4244
|
-
private resolvePermissionByNativeId(nativeId: string): void {
|
|
4245
|
-
for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
|
|
4246
|
-
for (const [permissionId, pending] of permissions) {
|
|
4247
|
-
if (pending.nativeId !== nativeId) continue;
|
|
4248
|
-
permissions.delete(permissionId);
|
|
4249
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
4250
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4251
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4252
|
-
return;
|
|
4253
|
-
}
|
|
4254
|
-
}
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
private resolveTurnPermissions(turnId: string): void {
|
|
4258
|
-
if (this.pendingFreeformSelection?.turnId === turnId) this.pendingFreeformSelection = null;
|
|
4259
|
-
this.resolvePermissions((pending) => pending.turnId === turnId, true);
|
|
4260
|
-
this.resolveToolPermissions((pending) => pending.turnId === turnId, true);
|
|
4261
|
-
}
|
|
4262
|
-
private resolveAllPermissions(cancelNative = false): void {
|
|
4263
|
-
this.pendingFreeformSelection = null;
|
|
4264
|
-
this.resolvePermissions(() => true, cancelNative);
|
|
4265
|
-
this.resolveToolPermissions(() => true, cancelNative);
|
|
4266
|
-
}
|
|
4267
|
-
|
|
4268
|
-
private resolvePermissions(
|
|
4269
|
-
matches: (pending: PendingPermission) => boolean,
|
|
4270
|
-
cancelNative: boolean,
|
|
4271
|
-
): void {
|
|
4272
|
-
const permissionIds = new Set<string>();
|
|
4273
|
-
for (const permissions of [this.pendingPermissions, this.inFlightPermissions]) {
|
|
4274
|
-
for (const [permissionId, pending] of permissions) {
|
|
4275
|
-
if (!matches(pending)) continue;
|
|
4276
|
-
permissions.delete(permissionId);
|
|
4277
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
4278
|
-
permissionIds.add(permissionId);
|
|
4279
|
-
if (cancelNative && permissions === this.pendingPermissions) {
|
|
4280
|
-
void pending.runtime
|
|
4281
|
-
.respondToExtensionUi({
|
|
4282
|
-
type: "extension_ui_response",
|
|
4283
|
-
id: pending.nativeId,
|
|
4284
|
-
cancelled: true,
|
|
4285
|
-
})
|
|
4286
|
-
.catch(() => {
|
|
4287
|
-
if (!this.closed && !this.runtimeDead) {
|
|
4288
|
-
this.invalidateRuntime("OMP permission cancellation failed");
|
|
4289
|
-
}
|
|
4290
|
-
});
|
|
4291
|
-
}
|
|
4292
|
-
}
|
|
4293
|
-
}
|
|
4294
|
-
for (const permissionId of permissionIds) {
|
|
4295
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4296
|
-
}
|
|
4297
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4298
|
-
}
|
|
4299
|
-
private resolveToolPermissions(
|
|
4300
|
-
matches: (pending: PendingToolPermission) => boolean,
|
|
4301
|
-
cancelNative: boolean,
|
|
4302
|
-
): void {
|
|
4303
|
-
const permissionIds = new Set<string>();
|
|
4304
|
-
for (const permissions of [this.pendingToolPermissions, this.inFlightToolPermissions]) {
|
|
4305
|
-
for (const [permissionId, pending] of permissions) {
|
|
4306
|
-
if (!matches(pending)) continue;
|
|
4307
|
-
permissions.delete(permissionId);
|
|
4308
|
-
if (pending.timer !== undefined) this.scheduler.clear(pending.timer);
|
|
4309
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
4310
|
-
permissionIds.add(permissionId);
|
|
4311
|
-
if (cancelNative && permissions === this.pendingToolPermissions) {
|
|
4312
|
-
void pending.runtime
|
|
4313
|
-
.respondToToolApproval({
|
|
4314
|
-
type: "tool_approval_response",
|
|
4315
|
-
id: pending.nativeId,
|
|
4316
|
-
toolCallId: pending.toolCallId,
|
|
4317
|
-
cancelled: true,
|
|
4318
|
-
})
|
|
4319
|
-
.catch(() => {
|
|
4320
|
-
if (!this.closed && !this.runtimeDead) {
|
|
4321
|
-
this.invalidateRuntime("OMP tool permission cancellation failed");
|
|
4322
|
-
}
|
|
4323
|
-
});
|
|
4324
|
-
}
|
|
4325
|
-
}
|
|
4326
|
-
}
|
|
4327
|
-
for (const permissionId of permissionIds) {
|
|
4328
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4329
|
-
}
|
|
4330
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
private armToolPermissionTimeout(permissionId: string, pending: PendingToolPermission): void {
|
|
4334
|
-
if (pending.expiresAt === undefined) return;
|
|
4335
|
-
const remainingMs = Math.max(0, pending.expiresAt - Date.now());
|
|
4336
|
-
pending.timer = this.scheduler.set(() => {
|
|
4337
|
-
if (this.pendingToolPermissions.get(permissionId) !== pending) return;
|
|
4338
|
-
this.pendingToolPermissions.delete(permissionId);
|
|
4339
|
-
this.inFlightToolPermissions.set(permissionId, pending);
|
|
4340
|
-
void pending.runtime
|
|
4341
|
-
.respondToToolApproval({
|
|
4342
|
-
type: "tool_approval_response",
|
|
4343
|
-
id: pending.nativeId,
|
|
4344
|
-
toolCallId: pending.toolCallId,
|
|
4345
|
-
cancelled: true,
|
|
4346
|
-
timedOut: true,
|
|
4347
|
-
})
|
|
4348
|
-
.then(
|
|
4349
|
-
() => {
|
|
4350
|
-
if (this.inFlightToolPermissions.get(permissionId) !== pending) return;
|
|
4351
|
-
this.inFlightToolPermissions.delete(permissionId);
|
|
4352
|
-
this.resolvedToolApprovalIds.add(pending.nativeId);
|
|
4353
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4354
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4355
|
-
},
|
|
4356
|
-
() => this.handleRuntimeFailure(),
|
|
4357
|
-
);
|
|
4358
|
-
}, remainingMs);
|
|
4359
|
-
}
|
|
4360
|
-
|
|
4361
|
-
private armPermissionTimeout(permissionId: string, pending: PendingPermission): void {
|
|
4362
|
-
if (pending.expiresAt === undefined) return;
|
|
4363
|
-
const remainingMs = Math.max(0, pending.expiresAt - Date.now());
|
|
4364
|
-
pending.timer = this.scheduler.set(() => {
|
|
4365
|
-
if (this.pendingPermissions.get(permissionId) !== pending) return;
|
|
4366
|
-
this.pendingPermissions.delete(permissionId);
|
|
4367
|
-
if (!this.permissionOwnerIsCurrent(pending)) {
|
|
4368
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4369
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4370
|
-
return;
|
|
4371
|
-
}
|
|
4372
|
-
this.inFlightPermissions.set(permissionId, pending);
|
|
4373
|
-
void pending.runtime
|
|
4374
|
-
.respondToExtensionUi({
|
|
4375
|
-
type: "extension_ui_response",
|
|
4376
|
-
id: pending.nativeId,
|
|
4377
|
-
cancelled: true,
|
|
4378
|
-
timedOut: true,
|
|
4379
|
-
})
|
|
4380
|
-
.then(
|
|
4381
|
-
() => {
|
|
4382
|
-
if (this.inFlightPermissions.get(permissionId) !== pending) return;
|
|
4383
|
-
this.inFlightPermissions.delete(permissionId);
|
|
4384
|
-
this.emit({ type: "session.permission_resolved", sessionId: this.id, permissionId });
|
|
4385
|
-
this.reevaluateDeferredPermissionTerminal();
|
|
4386
|
-
},
|
|
4387
|
-
() => this.handleRuntimeFailure(),
|
|
4388
|
-
);
|
|
4389
|
-
}, remainingMs);
|
|
4390
|
-
}
|
|
4391
|
-
|
|
4392
|
-
private permissionOwnerIsCurrent(pending: PendingPermission | PendingToolPermission): boolean {
|
|
4393
|
-
if (
|
|
4394
|
-
this.closed ||
|
|
4395
|
-
this.runtimeDead ||
|
|
4396
|
-
pending.generation !== this.generation ||
|
|
4397
|
-
pending.runtime !== this.runtime
|
|
4398
|
-
) {
|
|
4399
|
-
return false;
|
|
4400
|
-
}
|
|
4401
|
-
if (pending.turnId === undefined) return true;
|
|
4402
|
-
return this.activeTurn?.turnId === pending.turnId && !this.activeTurn.terminal;
|
|
4403
|
-
}
|
|
4404
2708
|
|
|
4405
2709
|
private hasTerminalConflict(turn: ActiveTurn, includeChildren = true): boolean {
|
|
4406
|
-
const ownsTurn = (pending: PendingPermission | PendingToolPermission) =>
|
|
4407
|
-
pending.turnId === turn.turnId;
|
|
4408
2710
|
return (
|
|
4409
2711
|
turn.awaitingPermissionEvidence ||
|
|
4410
|
-
|
|
4411
|
-
[...this.inFlightPermissions.values()].some(ownsTurn) ||
|
|
4412
|
-
[...this.pendingToolPermissions.values()].some(ownsTurn) ||
|
|
4413
|
-
[...this.inFlightToolPermissions.values()].some(ownsTurn) ||
|
|
2712
|
+
this.permissions.hasForTurn(turn.turnId) ||
|
|
4414
2713
|
turn.activeToolCallIds.size > 0 ||
|
|
4415
2714
|
turn.steersInFlight > 0 ||
|
|
4416
2715
|
(includeChildren && Boolean(this.subsessions?.hasActiveChildren()))
|
|
@@ -4546,7 +2845,7 @@ export class OmpProviderSession {
|
|
|
4546
2845
|
await Promise.allSettled(turn.userLookups);
|
|
4547
2846
|
this.refreshAmbiguousTerminal(turn);
|
|
4548
2847
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
4549
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
2848
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4550
2849
|
if (turn.terminal || this.activeTurn !== turn || turn.deferredAgentEnd !== candidate) return;
|
|
4551
2850
|
if (!state) {
|
|
4552
2851
|
turn.operationalTerminalStage = "unresolved";
|
|
@@ -4576,10 +2875,7 @@ export class OmpProviderSession {
|
|
|
4576
2875
|
) {
|
|
4577
2876
|
return;
|
|
4578
2877
|
}
|
|
4579
|
-
|
|
4580
|
-
if (this.usageSample?.turn === turn && this.usageSample.sequence < turn.usageSampleFloor) {
|
|
4581
|
-
this.usageSample = null;
|
|
4582
|
-
}
|
|
2878
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
4583
2879
|
this.publishPendingUsers(turn);
|
|
4584
2880
|
await this.finishTurn(turn, "completed", undefined, false, false, true);
|
|
4585
2881
|
}
|
|
@@ -4608,7 +2904,7 @@ export class OmpProviderSession {
|
|
|
4608
2904
|
this.beginTerminalization(turn, replacement);
|
|
4609
2905
|
return;
|
|
4610
2906
|
}
|
|
4611
|
-
this.
|
|
2907
|
+
this.usage.poll(turn);
|
|
4612
2908
|
}
|
|
4613
2909
|
|
|
4614
2910
|
private beginTerminalization(turn: ActiveTurn, candidate: TerminalCandidate): void {
|
|
@@ -4622,11 +2918,8 @@ export class OmpProviderSession {
|
|
|
4622
2918
|
}
|
|
4623
2919
|
turn.terminalizing = true;
|
|
4624
2920
|
this.cancelAmbiguousTerminal(turn);
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
this.usageSample = null;
|
|
4628
|
-
}
|
|
4629
|
-
this.stopUsagePoll(turn);
|
|
2921
|
+
this.usage.invalidateBeforeNextSample(turn);
|
|
2922
|
+
this.usage.stopPoll(turn);
|
|
4630
2923
|
turn.agentEndDeadlineTimer = this.scheduler.set(() => {
|
|
4631
2924
|
turn.agentEndDeadlineTimer = undefined;
|
|
4632
2925
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
@@ -4718,14 +3011,14 @@ export class OmpProviderSession {
|
|
|
4718
3011
|
) {
|
|
4719
3012
|
this.resetAgentEndProbe(turn);
|
|
4720
3013
|
turn.deferredAgentEnd = candidate;
|
|
4721
|
-
this.
|
|
3014
|
+
this.usage.poll(turn);
|
|
4722
3015
|
return;
|
|
4723
3016
|
}
|
|
4724
3017
|
if (turn.interrupted) {
|
|
4725
3018
|
await this.completeAgentEnd(turn, candidate.event);
|
|
4726
3019
|
return;
|
|
4727
3020
|
}
|
|
4728
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3021
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4729
3022
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
4730
3023
|
if (!turn.interrupted && this.subsessions?.hasActiveChildren()) {
|
|
4731
3024
|
this.resetAgentEndProbe(turn);
|
|
@@ -4738,7 +3031,7 @@ export class OmpProviderSession {
|
|
|
4738
3031
|
) {
|
|
4739
3032
|
this.resetAgentEndProbe(turn);
|
|
4740
3033
|
turn.deferredAgentEnd = candidate;
|
|
4741
|
-
this.
|
|
3034
|
+
this.usage.poll(turn);
|
|
4742
3035
|
return;
|
|
4743
3036
|
}
|
|
4744
3037
|
if (state) {
|
|
@@ -4780,7 +3073,7 @@ export class OmpProviderSession {
|
|
|
4780
3073
|
!turn.interrupted &&
|
|
4781
3074
|
this.activeTurn === turn
|
|
4782
3075
|
) {
|
|
4783
|
-
const state = await this.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
3076
|
+
const state = await this.usage.boundedTerminalState(turn, FINAL_USAGE_WAIT_MS);
|
|
4784
3077
|
if (
|
|
4785
3078
|
turn.terminal ||
|
|
4786
3079
|
this.activeTurn !== turn ||
|
|
@@ -4858,9 +3151,9 @@ export class OmpProviderSession {
|
|
|
4858
3151
|
turn.steerReady.resolve();
|
|
4859
3152
|
turn.starting = false;
|
|
4860
3153
|
turn.terminal = true;
|
|
4861
|
-
this.
|
|
3154
|
+
this.usage.stopPoll(turn);
|
|
4862
3155
|
this.cancelAmbiguousTerminal(turn);
|
|
4863
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
3156
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
4864
3157
|
this.projector.finishTurn(turn.turnId);
|
|
4865
3158
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
4866
3159
|
}
|
|
@@ -4885,7 +3178,7 @@ export class OmpProviderSession {
|
|
|
4885
3178
|
turn.starting = false;
|
|
4886
3179
|
turn.steerReady.resolve();
|
|
4887
3180
|
this.emit({ type: "session.turn", sessionId: this.id, turnId: turn.turnId, state: "started" });
|
|
4888
|
-
if (pollUsage) this.
|
|
3181
|
+
if (pollUsage) this.usage.poll(turn);
|
|
4889
3182
|
}
|
|
4890
3183
|
private finishTurn(
|
|
4891
3184
|
turn: ActiveTurn,
|
|
@@ -4915,7 +3208,7 @@ export class OmpProviderSession {
|
|
|
4915
3208
|
turn.agentEndPending = false;
|
|
4916
3209
|
this.cancelLocalOnlyCompletion(turn);
|
|
4917
3210
|
this.cancelAmbiguousTerminal(turn);
|
|
4918
|
-
this.
|
|
3211
|
+
this.usage.stopPoll(turn);
|
|
4919
3212
|
if (turn.manualCompactionDeadlineTimer !== undefined) {
|
|
4920
3213
|
this.scheduler.clear(turn.manualCompactionDeadlineTimer);
|
|
4921
3214
|
turn.manualCompactionDeadlineTimer = undefined;
|
|
@@ -4934,7 +3227,7 @@ export class OmpProviderSession {
|
|
|
4934
3227
|
const terminalization = (async () => {
|
|
4935
3228
|
if (!turn.terminalOutcome?.usageSampled && !this.closed && !this.runtimeDead) {
|
|
4936
3229
|
await Promise.race([
|
|
4937
|
-
this.
|
|
3230
|
+
this.usage.boundedSnapshot(turn, FINAL_USAGE_WAIT_MS),
|
|
4938
3231
|
wake.promise.then(() => undefined),
|
|
4939
3232
|
]);
|
|
4940
3233
|
}
|
|
@@ -4961,17 +3254,17 @@ export class OmpProviderSession {
|
|
|
4961
3254
|
}
|
|
4962
3255
|
this.imageMaterializer.clear();
|
|
4963
3256
|
turn.terminal = true;
|
|
4964
|
-
|
|
4965
|
-
if (!preserveCompactions && this.
|
|
4966
|
-
if (outcome.state === "canceled") this.
|
|
3257
|
+
this.usage.clearSample(turn);
|
|
3258
|
+
if (!preserveCompactions && this.compaction.active) {
|
|
3259
|
+
if (outcome.state === "canceled") this.compaction.finish("canceled");
|
|
4967
3260
|
else {
|
|
4968
|
-
this.
|
|
3261
|
+
this.compaction.finish("failed", {
|
|
4969
3262
|
message: outcome.error?.message ?? "OMP compaction ended without a terminal result",
|
|
4970
3263
|
});
|
|
4971
3264
|
}
|
|
4972
3265
|
}
|
|
4973
3266
|
this.publishPendingUsers(turn);
|
|
4974
|
-
this.resolveTurnPermissions(turn.turnId);
|
|
3267
|
+
this.permissions.resolveTurnPermissions(turn.turnId);
|
|
4975
3268
|
try {
|
|
4976
3269
|
this.projector.finishTurn(turn.turnId, preserveCompactions);
|
|
4977
3270
|
} catch (error) {
|
|
@@ -5002,15 +3295,14 @@ export class OmpProviderSession {
|
|
|
5002
3295
|
): void {
|
|
5003
3296
|
this.imageMaterializer.clear();
|
|
5004
3297
|
if (this.closed || this.runtimeDead) return;
|
|
5005
|
-
this.
|
|
5006
|
-
this.resolveAllPermissions();
|
|
3298
|
+
this.compaction.resetDiscardedEnds();
|
|
3299
|
+
this.permissions.resolveAllPermissions();
|
|
5007
3300
|
this.recoveryUsesNativeConfig ||=
|
|
5008
3301
|
this.configRefreshInFlight !== null || this.configRefreshDirty || this.configMutationInFlight;
|
|
5009
3302
|
const turn = this.activeTurn;
|
|
5010
|
-
if (turn) this.
|
|
5011
|
-
this.
|
|
5012
|
-
this.
|
|
5013
|
-
this.lastUsage = null;
|
|
3303
|
+
if (turn) this.usage.stopPoll(turn);
|
|
3304
|
+
this.usage.reset();
|
|
3305
|
+
this.compaction.finish(compactionState, { message });
|
|
5014
3306
|
this.generation += 1;
|
|
5015
3307
|
this.projector.resetRuntimeGeneration("OMP runtime ended during compaction");
|
|
5016
3308
|
this.runtimeDead = message;
|