@omercnet/paseo-omp 0.4.0-next.122.1 → 0.4.0-next.123.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "0.4.0-next.122.1",
3
+ "version": "0.4.0-next.123.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -1,2 +1,2 @@
1
1
  /** Release Please keeps this bundle-safe constant aligned with package.json. */
2
- export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.122.1"; // x-release-please-version
2
+ export const PASEO_OMP_PACKAGE_VERSION = "0.4.0-next.123.1"; // x-release-please-version
@@ -0,0 +1,42 @@
1
+ import type { OmpRpcEvent } from "./omp-rpc-protocol";
2
+
3
+ export function isNativeTurnActivity(event: OmpRpcEvent): boolean {
4
+ if (
5
+ event.type === "agent_start" ||
6
+ event.type === "turn_start" ||
7
+ event.type === "turn_end" ||
8
+ event.type === "agent_end" ||
9
+ event.type === "auto_compaction_start" ||
10
+ event.type === "auto_compaction_end"
11
+ ) {
12
+ return true;
13
+ }
14
+ if (
15
+ event.type === "message_start" ||
16
+ event.type === "message_update" ||
17
+ event.type === "message_end"
18
+ ) {
19
+ return event.message.role === "assistant";
20
+ }
21
+ return event.type.startsWith("tool_execution_");
22
+ }
23
+ export function isRuntimeConfigEvent(event: OmpRpcEvent): boolean {
24
+ return (
25
+ event.type === "model_changed" ||
26
+ event.type === "thinking_level_changed" ||
27
+ event.type === "retry_fallback_applied" ||
28
+ event.type === "retry_fallback_succeeded"
29
+ );
30
+ }
31
+
32
+ export function isPassiveUiMethod(method: string): boolean {
33
+ return (
34
+ method === "cancel" ||
35
+ method === "notify" ||
36
+ method === "open_url" ||
37
+ method === "setStatus" ||
38
+ method === "setWidget" ||
39
+ method === "setTitle" ||
40
+ method === "set_editor_text"
41
+ );
42
+ }
@@ -0,0 +1,307 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import type { ProviderError } from "@getpaseo/plugin/server/provider";
3
+ import type { OmpMessage, OmpRpcEvent } from "./omp-rpc-protocol";
4
+
5
+ export type PendingUser = {
6
+ clientMessageId: string;
7
+ text: string;
8
+ accepted: boolean;
9
+ fallbackOnFinish: boolean;
10
+ bufferedEchoes: OmpMessage[];
11
+ };
12
+ type TerminalCorrelationEvidence = "fresh-native-user" | "current-assistant";
13
+
14
+ type TerminalCorrelation = {
15
+ policy: "initial-turn" | "ordered-legacy" | "native-command";
16
+ evidence: Map<TerminalCorrelationEvidence, number>;
17
+ };
18
+
19
+ export type TerminalCandidate = {
20
+ event: Extract<OmpRpcEvent, { type: "agent_end" }>;
21
+ arrivalSequence: number;
22
+ confidence:
23
+ | "keyed"
24
+ | "initial-turn"
25
+ | "ordered-legacy"
26
+ | "native-command"
27
+ | "interrupted"
28
+ | "ambiguous";
29
+ };
30
+
31
+ export type ActiveTurn = {
32
+ turnId: string;
33
+ clientMessageId: string;
34
+ generation: number;
35
+ promptResultEmitted: boolean;
36
+ started: boolean;
37
+ terminal: boolean;
38
+ interrupted: boolean;
39
+ starting: boolean;
40
+ nativeActivity: boolean;
41
+ userEchoObserved: boolean;
42
+ localOnlyDisabled: boolean;
43
+ localOnlyEligible: boolean;
44
+ awaitingPermissionEvidence: boolean;
45
+ activitySequence: number;
46
+ acknowledged: boolean;
47
+ terminalCorrelation: TerminalCorrelation;
48
+ replayingBufferedEvents: boolean;
49
+ agentInvoked?: boolean;
50
+ nativeRequestId?: string;
51
+ promptAcceptedEventIndex?: number;
52
+ localOnlyTimer?: unknown;
53
+ usagePollTimer?: unknown;
54
+ usagePoll?: Promise<void>;
55
+ usageSampleFloor: number;
56
+ manualCompaction: boolean;
57
+ manualCompactionPending: boolean;
58
+ manualCompactionDeadlineTimer?: unknown;
59
+ agentEndPending: boolean;
60
+ agentEndRetryTimer?: unknown;
61
+ agentEndDeadlineTimer?: unknown;
62
+ agentEndCheck?: Promise<void>;
63
+ ambiguousTerminalTimer?: unknown;
64
+ terminalizing: boolean;
65
+ terminalization?: Promise<void>;
66
+ terminalOutcome?: TurnOutcome;
67
+ operationalTerminalStage?: "failed" | "unresolved";
68
+ terminalWake?: VoidDeferred;
69
+ steerReady: VoidDeferred;
70
+ steersInFlight: number;
71
+ deferredAgentEnd?: TerminalCandidate;
72
+ activeToolCallIds: Set<string>;
73
+ bufferedEvents: OmpRpcEvent[];
74
+ pendingUsers: PendingUser[];
75
+ userEchoes: OmpMessage[];
76
+ userCorrelationActive: boolean;
77
+ userLookups: Set<Promise<void>>;
78
+ completedMessageCount: number;
79
+ streamedMessageEntryIds: string[];
80
+ streamedMessageIdentityComplete: boolean;
81
+ lastCompletedAssistantOutcome?: AgentEndOutcome;
82
+ lastCompletedAssistantEntryId?: string;
83
+ };
84
+
85
+ export type VoidDeferred = {
86
+ promise: Promise<void>;
87
+ resolve(value?: void | PromiseLike<void>): void;
88
+ reject(reason?: unknown): void;
89
+ };
90
+
91
+ export type TurnOutcome = {
92
+ state: "completed" | "failed" | "canceled";
93
+ error?: ProviderError;
94
+ usageSampled: boolean;
95
+ };
96
+
97
+ export function nativeEntryId(message: OmpMessage): string | undefined {
98
+ return message.entryId;
99
+ }
100
+
101
+ export type AgentEndOutcome = "completed" | "failed" | "canceled";
102
+ type AssistantTerminalStatus = AgentEndOutcome | "unavailable";
103
+
104
+ export function assistantTerminalOutcome(
105
+ message: Extract<OmpMessage, { role: "assistant" }>,
106
+ ): AgentEndOutcome {
107
+ const stopReason = message.stopReason?.toLowerCase();
108
+ if (stopReason === "aborted" || stopReason === "canceled" || stopReason === "cancelled") {
109
+ return "canceled";
110
+ }
111
+ return stopReason === "error" || message.errorMessage ? "failed" : "completed";
112
+ }
113
+
114
+ function lastAssistantStatus(
115
+ messages: readonly OmpMessage[],
116
+ startIndex = 0,
117
+ ): AssistantTerminalStatus {
118
+ for (let index = messages.length - 1; index >= startIndex; index -= 1) {
119
+ const message = messages[index];
120
+ if (message?.role !== "assistant") continue;
121
+ return assistantTerminalOutcome(message);
122
+ }
123
+ return "unavailable";
124
+ }
125
+
126
+ export function terminalOutcome(
127
+ event: Extract<OmpRpcEvent, { type: "agent_end" }>,
128
+ turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
129
+ ): AgentEndOutcome | undefined {
130
+ const messages = event.messages;
131
+ if (
132
+ messages !== undefined &&
133
+ (event.messageCount === undefined || messages.length >= event.messageCount)
134
+ ) {
135
+ const status = lastAssistantStatus(messages);
136
+ return status === "unavailable" ? "completed" : status;
137
+ }
138
+ if (event.messageCount === 0) return "completed";
139
+ if (
140
+ turn.lastCompletedAssistantOutcome !== undefined &&
141
+ (event.messageCount === undefined || turn.completedMessageCount >= event.messageCount)
142
+ ) {
143
+ return turn.lastCompletedAssistantOutcome;
144
+ }
145
+ return undefined;
146
+ }
147
+
148
+ export function historyTerminalOutcome(
149
+ messages: readonly OmpMessage[],
150
+ declaredCount: number,
151
+ turn: Pick<
152
+ ActiveTurn,
153
+ | "streamedMessageEntryIds"
154
+ | "streamedMessageIdentityComplete"
155
+ | "lastCompletedAssistantOutcome"
156
+ | "lastCompletedAssistantEntryId"
157
+ >,
158
+ retainedMessages: readonly OmpMessage[],
159
+ ): AgentEndOutcome | undefined {
160
+ if (
161
+ messages.length < declaredCount ||
162
+ !turn.streamedMessageIdentityComplete ||
163
+ turn.streamedMessageEntryIds.length === 0
164
+ ) {
165
+ return undefined;
166
+ }
167
+ const startIndex = messages.length - declaredCount;
168
+ let historyIndex = startIndex;
169
+ for (const entryId of turn.streamedMessageEntryIds) {
170
+ while (historyIndex < messages.length) {
171
+ const historyMessage = messages[historyIndex];
172
+ if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
173
+ historyIndex += 1;
174
+ }
175
+ if (historyIndex >= messages.length) return undefined;
176
+ const correlated = messages[historyIndex];
177
+ if (
178
+ entryId === turn.lastCompletedAssistantEntryId &&
179
+ (correlated?.role !== "assistant" ||
180
+ assistantTerminalOutcome(correlated) !== turn.lastCompletedAssistantOutcome)
181
+ )
182
+ return undefined;
183
+ historyIndex += 1;
184
+ }
185
+ historyIndex = startIndex;
186
+ for (const retained of retainedMessages) {
187
+ const entryId = nativeEntryId(retained);
188
+ if (!entryId) return undefined;
189
+ while (historyIndex < messages.length) {
190
+ const historyMessage = messages[historyIndex];
191
+ if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
192
+ historyIndex += 1;
193
+ }
194
+ if (historyIndex >= messages.length) return undefined;
195
+ const correlated = messages[historyIndex];
196
+ if (!correlated || correlated.role !== retained.role) return undefined;
197
+ if (
198
+ retained.role === "assistant" &&
199
+ correlated.role === "assistant" &&
200
+ assistantTerminalOutcome(retained) !== assistantTerminalOutcome(correlated)
201
+ )
202
+ return undefined;
203
+ historyIndex += 1;
204
+ }
205
+ const status = lastAssistantStatus(messages, startIndex);
206
+ return status === "unavailable" ? undefined : status;
207
+ }
208
+
209
+ export function unknownTerminalOutcomeError(
210
+ event: Extract<OmpRpcEvent, { type: "agent_end" }>,
211
+ turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
212
+ ): string {
213
+ const retainedMessages = event.messages?.length ?? 0;
214
+ const retainedStatus = event.messages ? lastAssistantStatus(event.messages) : "unavailable";
215
+ const lastStatus =
216
+ retainedStatus === "unavailable"
217
+ ? (turn.lastCompletedAssistantOutcome ?? "unavailable")
218
+ : retainedStatus;
219
+ return (
220
+ "OMP agent_end omitted terminal messages; outcome is unknown " +
221
+ `(declaredCount=${event.messageCount ?? "unavailable"}, ` +
222
+ `observedCount=${turn.completedMessageCount}, ` +
223
+ `retainedTerminalMessages=${retainedMessages}, lastAssistantStatus=${lastStatus})`
224
+ );
225
+ }
226
+
227
+ export function createActiveTurn(
228
+ clientMessageId: string,
229
+ text: string,
230
+ generation: number,
231
+ terminalPolicy: TerminalCorrelation["policy"],
232
+ manualCompaction = false,
233
+ ): ActiveTurn {
234
+ return {
235
+ turnId: randomUUID(),
236
+ clientMessageId,
237
+ agentInvoked: undefined,
238
+ generation,
239
+ promptResultEmitted: false,
240
+ started: false,
241
+ terminal: false,
242
+ awaitingPermissionEvidence: false,
243
+ interrupted: false,
244
+ starting: true,
245
+ nativeActivity: false,
246
+ userEchoObserved: false,
247
+ localOnlyDisabled: false,
248
+ localOnlyEligible: false,
249
+ usageSampleFloor: 0,
250
+ agentEndPending: false,
251
+ terminalizing: false,
252
+ manualCompactionPending: manualCompaction,
253
+ manualCompaction,
254
+ activitySequence: 0,
255
+ acknowledged: false,
256
+ terminalCorrelation: { policy: terminalPolicy, evidence: new Map() },
257
+ replayingBufferedEvents: false,
258
+ steersInFlight: 0,
259
+ activeToolCallIds: new Set(),
260
+ steerReady: Promise.withResolvers<void>(),
261
+ userCorrelationActive: false,
262
+ userLookups: new Set(),
263
+ userEchoes: [],
264
+ completedMessageCount: 0,
265
+ bufferedEvents: [],
266
+ streamedMessageEntryIds: [],
267
+ streamedMessageIdentityComplete: true,
268
+ pendingUsers: [
269
+ {
270
+ clientMessageId,
271
+ text,
272
+ accepted: true,
273
+ fallbackOnFinish: true,
274
+ bufferedEchoes: [],
275
+ },
276
+ ],
277
+ };
278
+ }
279
+ export function classifyTerminalCandidate(
280
+ turn: ActiveTurn,
281
+ event: Extract<OmpRpcEvent, { type: "agent_end" }>,
282
+ arrivalSequence = turn.activitySequence,
283
+ ): TerminalCandidate {
284
+ if (event.requestId !== undefined) return { event, confidence: "keyed", arrivalSequence };
285
+ if (turn.agentInvoked === false) return { event, confidence: "ambiguous", arrivalSequence };
286
+ if (turn.terminalCorrelation.policy === "initial-turn") {
287
+ return { event, confidence: "initial-turn", arrivalSequence };
288
+ }
289
+ const assistantSequence = turn.terminalCorrelation.evidence.get("current-assistant");
290
+ if (
291
+ turn.terminalCorrelation.policy === "native-command" &&
292
+ assistantSequence !== undefined &&
293
+ assistantSequence <= arrivalSequence
294
+ ) {
295
+ return { event, confidence: "native-command", arrivalSequence };
296
+ }
297
+ const userSequence = turn.terminalCorrelation.evidence.get("fresh-native-user");
298
+ if (
299
+ userSequence !== undefined &&
300
+ assistantSequence !== undefined &&
301
+ userSequence < assistantSequence &&
302
+ assistantSequence <= arrivalSequence
303
+ ) {
304
+ return { event, confidence: "ordered-legacy", arrivalSequence };
305
+ }
306
+ return { event, confidence: "ambiguous", arrivalSequence };
307
+ }
@@ -66,6 +66,19 @@ import {
66
66
  } from "./security";
67
67
  import type { OmpSessionDescriptor } from "./session-descriptors";
68
68
  import { validateNativeSessionId } from "./session-descriptors";
69
+ import { isNativeTurnActivity, isPassiveUiMethod, isRuntimeConfigEvent } from "./session-events";
70
+ import {
71
+ type ActiveTurn,
72
+ assistantTerminalOutcome,
73
+ classifyTerminalCandidate,
74
+ createActiveTurn,
75
+ historyTerminalOutcome,
76
+ nativeEntryId,
77
+ type PendingUser,
78
+ type TerminalCandidate,
79
+ terminalOutcome,
80
+ unknownTerminalOutcomeError,
81
+ } from "./session-terminal";
69
82
  import { OmpSubsessionProjector } from "./subsessions";
70
83
  import {
71
84
  defaultOmpTimelineScheduler,
@@ -235,86 +248,6 @@ async function waitForReplay<T>(operation: Promise<T>, signal: AbortSignal): Pro
235
248
  }
236
249
  }
237
250
 
238
- type PendingUser = {
239
- clientMessageId: string;
240
- text: string;
241
- accepted: boolean;
242
- fallbackOnFinish: boolean;
243
- bufferedEchoes: OmpMessage[];
244
- };
245
- type TerminalCorrelationEvidence = "fresh-native-user" | "current-assistant";
246
-
247
- type TerminalCorrelation = {
248
- policy: "initial-turn" | "ordered-legacy" | "native-command";
249
- evidence: Map<TerminalCorrelationEvidence, number>;
250
- };
251
-
252
- type TerminalCandidate = {
253
- event: Extract<OmpRpcEvent, { type: "agent_end" }>;
254
- arrivalSequence: number;
255
- confidence:
256
- | "keyed"
257
- | "initial-turn"
258
- | "ordered-legacy"
259
- | "native-command"
260
- | "interrupted"
261
- | "ambiguous";
262
- };
263
-
264
- type ActiveTurn = {
265
- turnId: string;
266
- clientMessageId: string;
267
- generation: number;
268
- promptResultEmitted: boolean;
269
- started: boolean;
270
- terminal: boolean;
271
- interrupted: boolean;
272
- starting: boolean;
273
- nativeActivity: boolean;
274
- userEchoObserved: boolean;
275
- localOnlyDisabled: boolean;
276
- localOnlyEligible: boolean;
277
- awaitingPermissionEvidence: boolean;
278
- activitySequence: number;
279
- acknowledged: boolean;
280
- terminalCorrelation: TerminalCorrelation;
281
- replayingBufferedEvents: boolean;
282
- agentInvoked?: boolean;
283
- nativeRequestId?: string;
284
- promptAcceptedEventIndex?: number;
285
- localOnlyTimer?: unknown;
286
- usagePollTimer?: unknown;
287
- usagePoll?: Promise<void>;
288
- usageSampleFloor: number;
289
- manualCompaction: boolean;
290
- manualCompactionPending: boolean;
291
- manualCompactionDeadlineTimer?: unknown;
292
- agentEndPending: boolean;
293
- agentEndRetryTimer?: unknown;
294
- agentEndDeadlineTimer?: unknown;
295
- agentEndCheck?: Promise<void>;
296
- ambiguousTerminalTimer?: unknown;
297
- terminalizing: boolean;
298
- terminalization?: Promise<void>;
299
- terminalOutcome?: TurnOutcome;
300
- operationalTerminalStage?: "failed" | "unresolved";
301
- terminalWake?: VoidDeferred;
302
- steerReady: VoidDeferred;
303
- steersInFlight: number;
304
- deferredAgentEnd?: TerminalCandidate;
305
- activeToolCallIds: Set<string>;
306
- bufferedEvents: OmpRpcEvent[];
307
- pendingUsers: PendingUser[];
308
- userEchoes: OmpMessage[];
309
- userCorrelationActive: boolean;
310
- userLookups: Set<Promise<void>>;
311
- completedMessageCount: number;
312
- streamedMessageEntryIds: string[];
313
- streamedMessageIdentityComplete: boolean;
314
- lastCompletedAssistantOutcome?: AgentEndOutcome;
315
- lastCompletedAssistantEntryId?: string;
316
- };
317
-
318
251
  type PendingAbort = {
319
252
  turn: ActiveTurn;
320
253
  generation: number;
@@ -362,18 +295,6 @@ function permissionFingerprint(request: OmpQuestionRequest): string {
362
295
  return createHash("sha256").update(JSON.stringify(request)).digest("base64url");
363
296
  }
364
297
 
365
- type VoidDeferred = {
366
- promise: Promise<void>;
367
- resolve(value?: void | PromiseLike<void>): void;
368
- reject(reason?: unknown): void;
369
- };
370
-
371
- type TurnOutcome = {
372
- state: "completed" | "failed" | "canceled";
373
- error?: ProviderError;
374
- usageSampled: boolean;
375
- };
376
-
377
298
  type ActiveCompaction = {
378
299
  id: string;
379
300
  trigger: "auto" | "manual";
@@ -408,258 +329,6 @@ async function settleSessionCleanup(promises: readonly Promise<void>[]): Promise
408
329
  throw new AggregateError(failures, "OMP session initialization cleanup failed");
409
330
  }
410
331
  }
411
- function nativeEntryId(message: OmpMessage): string | undefined {
412
- return message.entryId;
413
- }
414
-
415
- type AgentEndOutcome = "completed" | "failed" | "canceled";
416
- type AssistantTerminalStatus = AgentEndOutcome | "unavailable";
417
-
418
- function assistantTerminalOutcome(
419
- message: Extract<OmpMessage, { role: "assistant" }>,
420
- ): AgentEndOutcome {
421
- const stopReason = message.stopReason?.toLowerCase();
422
- if (stopReason === "aborted" || stopReason === "canceled" || stopReason === "cancelled") {
423
- return "canceled";
424
- }
425
- return stopReason === "error" || message.errorMessage ? "failed" : "completed";
426
- }
427
-
428
- function lastAssistantStatus(
429
- messages: readonly OmpMessage[],
430
- startIndex = 0,
431
- ): AssistantTerminalStatus {
432
- for (let index = messages.length - 1; index >= startIndex; index -= 1) {
433
- const message = messages[index];
434
- if (message?.role !== "assistant") continue;
435
- return assistantTerminalOutcome(message);
436
- }
437
- return "unavailable";
438
- }
439
-
440
- function terminalOutcome(
441
- event: Extract<OmpRpcEvent, { type: "agent_end" }>,
442
- turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
443
- ): AgentEndOutcome | undefined {
444
- const messages = event.messages;
445
- if (
446
- messages !== undefined &&
447
- (event.messageCount === undefined || messages.length >= event.messageCount)
448
- ) {
449
- const status = lastAssistantStatus(messages);
450
- return status === "unavailable" ? "completed" : status;
451
- }
452
- if (event.messageCount === 0) return "completed";
453
- if (
454
- turn.lastCompletedAssistantOutcome !== undefined &&
455
- (event.messageCount === undefined || turn.completedMessageCount >= event.messageCount)
456
- ) {
457
- return turn.lastCompletedAssistantOutcome;
458
- }
459
- return undefined;
460
- }
461
-
462
- function historyTerminalOutcome(
463
- messages: readonly OmpMessage[],
464
- declaredCount: number,
465
- turn: Pick<
466
- ActiveTurn,
467
- | "streamedMessageEntryIds"
468
- | "streamedMessageIdentityComplete"
469
- | "lastCompletedAssistantOutcome"
470
- | "lastCompletedAssistantEntryId"
471
- >,
472
- retainedMessages: readonly OmpMessage[],
473
- ): AgentEndOutcome | undefined {
474
- if (
475
- messages.length < declaredCount ||
476
- !turn.streamedMessageIdentityComplete ||
477
- turn.streamedMessageEntryIds.length === 0
478
- ) {
479
- return undefined;
480
- }
481
- const startIndex = messages.length - declaredCount;
482
- let historyIndex = startIndex;
483
- for (const entryId of turn.streamedMessageEntryIds) {
484
- while (historyIndex < messages.length) {
485
- const historyMessage = messages[historyIndex];
486
- if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
487
- historyIndex += 1;
488
- }
489
- if (historyIndex >= messages.length) return undefined;
490
- const correlated = messages[historyIndex];
491
- if (
492
- entryId === turn.lastCompletedAssistantEntryId &&
493
- (correlated?.role !== "assistant" ||
494
- assistantTerminalOutcome(correlated) !== turn.lastCompletedAssistantOutcome)
495
- )
496
- return undefined;
497
- historyIndex += 1;
498
- }
499
- historyIndex = startIndex;
500
- for (const retained of retainedMessages) {
501
- const entryId = nativeEntryId(retained);
502
- if (!entryId) return undefined;
503
- while (historyIndex < messages.length) {
504
- const historyMessage = messages[historyIndex];
505
- if (historyMessage && nativeEntryId(historyMessage) === entryId) break;
506
- historyIndex += 1;
507
- }
508
- if (historyIndex >= messages.length) return undefined;
509
- const correlated = messages[historyIndex];
510
- if (!correlated || correlated.role !== retained.role) return undefined;
511
- if (
512
- retained.role === "assistant" &&
513
- correlated.role === "assistant" &&
514
- assistantTerminalOutcome(retained) !== assistantTerminalOutcome(correlated)
515
- )
516
- return undefined;
517
- historyIndex += 1;
518
- }
519
- const status = lastAssistantStatus(messages, startIndex);
520
- return status === "unavailable" ? undefined : status;
521
- }
522
-
523
- function unknownTerminalOutcomeError(
524
- event: Extract<OmpRpcEvent, { type: "agent_end" }>,
525
- turn: Pick<ActiveTurn, "completedMessageCount" | "lastCompletedAssistantOutcome">,
526
- ): string {
527
- const retainedMessages = event.messages?.length ?? 0;
528
- const retainedStatus = event.messages ? lastAssistantStatus(event.messages) : "unavailable";
529
- const lastStatus =
530
- retainedStatus === "unavailable"
531
- ? (turn.lastCompletedAssistantOutcome ?? "unavailable")
532
- : retainedStatus;
533
- return (
534
- "OMP agent_end omitted terminal messages; outcome is unknown " +
535
- `(declaredCount=${event.messageCount ?? "unavailable"}, ` +
536
- `observedCount=${turn.completedMessageCount}, ` +
537
- `retainedTerminalMessages=${retainedMessages}, lastAssistantStatus=${lastStatus})`
538
- );
539
- }
540
-
541
- function isNativeTurnActivity(event: OmpRpcEvent): boolean {
542
- if (
543
- event.type === "agent_start" ||
544
- event.type === "turn_start" ||
545
- event.type === "turn_end" ||
546
- event.type === "agent_end" ||
547
- event.type === "auto_compaction_start" ||
548
- event.type === "auto_compaction_end"
549
- ) {
550
- return true;
551
- }
552
- if (
553
- event.type === "message_start" ||
554
- event.type === "message_update" ||
555
- event.type === "message_end"
556
- ) {
557
- return event.message.role === "assistant";
558
- }
559
- return event.type.startsWith("tool_execution_");
560
- }
561
- function isRuntimeConfigEvent(event: OmpRpcEvent): boolean {
562
- return (
563
- event.type === "model_changed" ||
564
- event.type === "thinking_level_changed" ||
565
- event.type === "retry_fallback_applied" ||
566
- event.type === "retry_fallback_succeeded"
567
- );
568
- }
569
-
570
- function isPassiveUiMethod(method: string): boolean {
571
- return (
572
- method === "cancel" ||
573
- method === "notify" ||
574
- method === "open_url" ||
575
- method === "setStatus" ||
576
- method === "setWidget" ||
577
- method === "setTitle" ||
578
- method === "set_editor_text"
579
- );
580
- }
581
- function createActiveTurn(
582
- clientMessageId: string,
583
- text: string,
584
- generation: number,
585
- terminalPolicy: TerminalCorrelation["policy"],
586
- manualCompaction = false,
587
- ): ActiveTurn {
588
- return {
589
- turnId: randomUUID(),
590
- clientMessageId,
591
- agentInvoked: undefined,
592
- generation,
593
- promptResultEmitted: false,
594
- started: false,
595
- terminal: false,
596
- awaitingPermissionEvidence: false,
597
- interrupted: false,
598
- starting: true,
599
- nativeActivity: false,
600
- userEchoObserved: false,
601
- localOnlyDisabled: false,
602
- localOnlyEligible: false,
603
- usageSampleFloor: 0,
604
- agentEndPending: false,
605
- terminalizing: false,
606
- manualCompactionPending: manualCompaction,
607
- manualCompaction,
608
- activitySequence: 0,
609
- acknowledged: false,
610
- terminalCorrelation: { policy: terminalPolicy, evidence: new Map() },
611
- replayingBufferedEvents: false,
612
- steersInFlight: 0,
613
- activeToolCallIds: new Set(),
614
- steerReady: Promise.withResolvers<void>(),
615
- userCorrelationActive: false,
616
- userLookups: new Set(),
617
- userEchoes: [],
618
- completedMessageCount: 0,
619
- bufferedEvents: [],
620
- streamedMessageEntryIds: [],
621
- streamedMessageIdentityComplete: true,
622
- pendingUsers: [
623
- {
624
- clientMessageId,
625
- text,
626
- accepted: true,
627
- fallbackOnFinish: true,
628
- bufferedEchoes: [],
629
- },
630
- ],
631
- };
632
- }
633
- function classifyTerminalCandidate(
634
- turn: ActiveTurn,
635
- event: Extract<OmpRpcEvent, { type: "agent_end" }>,
636
- arrivalSequence = turn.activitySequence,
637
- ): TerminalCandidate {
638
- if (event.requestId !== undefined) return { event, confidence: "keyed", arrivalSequence };
639
- if (turn.agentInvoked === false) return { event, confidence: "ambiguous", arrivalSequence };
640
- if (turn.terminalCorrelation.policy === "initial-turn") {
641
- return { event, confidence: "initial-turn", arrivalSequence };
642
- }
643
- const assistantSequence = turn.terminalCorrelation.evidence.get("current-assistant");
644
- if (
645
- turn.terminalCorrelation.policy === "native-command" &&
646
- assistantSequence !== undefined &&
647
- assistantSequence <= arrivalSequence
648
- ) {
649
- return { event, confidence: "native-command", arrivalSequence };
650
- }
651
- const userSequence = turn.terminalCorrelation.evidence.get("fresh-native-user");
652
- if (
653
- userSequence !== undefined &&
654
- assistantSequence !== undefined &&
655
- userSequence < assistantSequence &&
656
- assistantSequence <= arrivalSequence
657
- ) {
658
- return { event, confidence: "ordered-legacy", arrivalSequence };
659
- }
660
- return { event, confidence: "ambiguous", arrivalSequence };
661
- }
662
-
663
332
  export class OmpProviderSession {
664
333
  readonly id: string;
665
334
  readonly cwd: string;