@omercnet/paseo-omp 5.1.1-next.336.1 → 5.1.1-next.339.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 +3 -3
- package/server/package-version.ts +1 -1
- package/server/provider/omp-rpc-protocol.ts +35 -0
- package/server/provider/omp-rpc-transport.ts +5 -2
- package/server/provider/session-compaction.ts +29 -25
- package/server/provider/session-events.ts +1 -0
- package/server/provider/session-terminal.ts +52 -1
- package/server/provider/session.ts +215 -64
- package/server/provider/timeline-projector.ts +29 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omercnet/paseo-omp",
|
|
3
|
-
"version": "5.1.1-next.
|
|
3
|
+
"version": "5.1.1-next.339.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"@getpaseo/client": "0.14.0",
|
|
65
65
|
"@getpaseo/plugin": "0.14.0",
|
|
66
66
|
"@getpaseo/server": "0.14.0",
|
|
67
|
-
"@oh-my-pi/pi-agent-core": "18.
|
|
68
|
-
"@oh-my-pi/pi-coding-agent": "18.
|
|
67
|
+
"@oh-my-pi/pi-agent-core": "18.14.0",
|
|
68
|
+
"@oh-my-pi/pi-coding-agent": "18.14.0",
|
|
69
69
|
"@tanstack/react-query": "^5.102.3",
|
|
70
70
|
"@types/node": "^24.10.1",
|
|
71
71
|
"@types/react": "^19.2.18",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Release Please keeps this bundle-safe constant aligned with package.json. */
|
|
2
|
-
export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.
|
|
2
|
+
export const PASEO_OMP_PACKAGE_VERSION = "5.1.1-next.339.1"; // x-release-please-version
|
|
@@ -71,6 +71,8 @@ export const OMP_SESSION_EVENT_POLICIES = {
|
|
|
71
71
|
tool_execution_end: "normalize",
|
|
72
72
|
auto_compaction_start: "normalize",
|
|
73
73
|
auto_compaction_end: "normalize",
|
|
74
|
+
manual_compaction_start: "normalize",
|
|
75
|
+
manual_compaction_end: "normalize",
|
|
74
76
|
auto_retry_start: "normalize",
|
|
75
77
|
auto_retry_end: "normalize",
|
|
76
78
|
retry_fallback_applied: "normalize",
|
|
@@ -148,9 +150,15 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
148
150
|
get_logout_accounts: "unsupported",
|
|
149
151
|
goal: "unsupported",
|
|
150
152
|
live_mute: "unsupported",
|
|
153
|
+
live_connect: "unsupported",
|
|
151
154
|
live_start: "unsupported",
|
|
152
155
|
live_stop: "unsupported",
|
|
153
156
|
logout: "unsupported",
|
|
157
|
+
navigate_tree: "unsupported",
|
|
158
|
+
abort_branch_summary: "unsupported",
|
|
159
|
+
set_label: "unsupported",
|
|
160
|
+
dictation_start: "unsupported",
|
|
161
|
+
dictation_stop: "unsupported",
|
|
154
162
|
predict_word: "unsupported",
|
|
155
163
|
predict_word_feedback: "unsupported",
|
|
156
164
|
promote_queued_message: "unsupported",
|
|
@@ -254,9 +262,14 @@ export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
|
254
262
|
"todo_auto_clear",
|
|
255
263
|
"auto_compaction_start",
|
|
256
264
|
"auto_compaction_end",
|
|
265
|
+
"manual_compaction_start",
|
|
266
|
+
"manual_compaction_end",
|
|
257
267
|
"available_commands_update",
|
|
258
268
|
"notice",
|
|
259
269
|
"command_output",
|
|
270
|
+
"extension_error",
|
|
271
|
+
"session_info_update",
|
|
272
|
+
"config_update",
|
|
260
273
|
"extension_ui_request",
|
|
261
274
|
"prompt_result",
|
|
262
275
|
"host_tool_call",
|
|
@@ -403,6 +416,8 @@ function eventSchemaClassification(
|
|
|
403
416
|
case "cache_warming_start":
|
|
404
417
|
case "cache_warming_end":
|
|
405
418
|
case "queue_update":
|
|
419
|
+
case "session_info_update":
|
|
420
|
+
case "config_update":
|
|
406
421
|
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
407
422
|
case "extension_ui_request":
|
|
408
423
|
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
@@ -1606,6 +1621,17 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
1606
1621
|
errorMessage: boundedString(64 * 1024).optional(),
|
|
1607
1622
|
skipped: z.boolean().optional(),
|
|
1608
1623
|
}),
|
|
1624
|
+
z.object({
|
|
1625
|
+
type: z.literal("manual_compaction_start"),
|
|
1626
|
+
mode: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
|
|
1627
|
+
}),
|
|
1628
|
+
z.object({
|
|
1629
|
+
type: z.literal("manual_compaction_end"),
|
|
1630
|
+
result: OmpCompactionResultSchema.optional(),
|
|
1631
|
+
aborted: z.boolean(),
|
|
1632
|
+
skipped: z.boolean().optional(),
|
|
1633
|
+
errorMessage: boundedString(64 * 1024).optional(),
|
|
1634
|
+
}),
|
|
1609
1635
|
z.object({
|
|
1610
1636
|
type: z.literal("notice"),
|
|
1611
1637
|
id: IDENTIFIER.optional(),
|
|
@@ -1799,6 +1825,14 @@ export const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
|
1799
1825
|
commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
|
|
1800
1826
|
}),
|
|
1801
1827
|
z.object({ type: z.literal("command_output"), text: TEXT.optional() }),
|
|
1828
|
+
z.object({
|
|
1829
|
+
type: z.literal("extension_error"),
|
|
1830
|
+
extensionPath: boundedString(MAX_PATH_LENGTH),
|
|
1831
|
+
event: NAME,
|
|
1832
|
+
error: boundedString(64 * 1024),
|
|
1833
|
+
}),
|
|
1834
|
+
z.object({ type: z.literal("session_info_update") }),
|
|
1835
|
+
z.object({ type: z.literal("config_update") }),
|
|
1802
1836
|
OmpExtensionUiRequestSchema,
|
|
1803
1837
|
z.object({
|
|
1804
1838
|
type: z.literal("prompt_result"),
|
|
@@ -1997,6 +2031,7 @@ export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["
|
|
|
1997
2031
|
export type OmpRpcEvent =
|
|
1998
2032
|
| z.infer<typeof OmpRuntimeEventSchema>
|
|
1999
2033
|
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
2034
|
+
| { type: "session_settled" }
|
|
2000
2035
|
| { type: "process_exit"; error: string };
|
|
2001
2036
|
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
2002
2037
|
export type OmpSubagentSnapshot = z.infer<typeof OmpSubagentsResultSchema>["subagents"][number];
|
|
@@ -1090,8 +1090,11 @@ export class OmpRpcProcess {
|
|
|
1090
1090
|
});
|
|
1091
1091
|
return;
|
|
1092
1092
|
}
|
|
1093
|
-
// Emitted
|
|
1094
|
-
if (type === "session_settled")
|
|
1093
|
+
// Emitted when the session is quiet: nothing live, queued, or pending in the background.
|
|
1094
|
+
if (type === "session_settled") {
|
|
1095
|
+
this.emit({ type: "session_settled" });
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1095
1098
|
if (type === "ready") {
|
|
1096
1099
|
if (this.readyReceived) {
|
|
1097
1100
|
this.recordProtocolViolation("duplicate-ready", {
|
|
@@ -7,6 +7,7 @@ import type { OmpSessionUsage } from "./session-usage";
|
|
|
7
7
|
import type { OmpTimelineProjector } from "./timeline-projector";
|
|
8
8
|
|
|
9
9
|
type Emit = (event: ProviderEvent) => void;
|
|
10
|
+
export type CompactionOutcome = "completed" | "failed" | "canceled" | "skipped";
|
|
10
11
|
|
|
11
12
|
export interface ActiveCompaction {
|
|
12
13
|
id: string;
|
|
@@ -73,51 +74,54 @@ export class OmpSessionCompaction {
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
handleEnd(
|
|
77
78
|
turn: ActiveTurn,
|
|
78
|
-
event: Extract<OmpRpcEvent, { type: "auto_compaction_end" }>,
|
|
79
|
-
):
|
|
79
|
+
event: Extract<OmpRpcEvent, { type: "auto_compaction_end" | "manual_compaction_end" }>,
|
|
80
|
+
): CompactionOutcome | undefined {
|
|
80
81
|
if (this.discardedEnds > 0) {
|
|
81
82
|
this.discardedEnds -= 1;
|
|
82
|
-
return
|
|
83
|
+
return undefined;
|
|
83
84
|
}
|
|
84
85
|
const operation = this.operation;
|
|
86
|
+
const trigger = event.type === "auto_compaction_end" ? "auto" : "manual";
|
|
85
87
|
if (
|
|
86
|
-
operation?.trigger !==
|
|
88
|
+
operation?.trigger !== trigger ||
|
|
87
89
|
operation.turnId !== turn.turnId ||
|
|
88
90
|
operation.generation !== turn.generation
|
|
89
91
|
) {
|
|
90
|
-
return
|
|
92
|
+
return undefined;
|
|
91
93
|
}
|
|
92
|
-
if (event.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
operation.retrying
|
|
103
|
-
|
|
94
|
+
if (event.type === "auto_compaction_end") {
|
|
95
|
+
if (event.action !== undefined && operation.action !== event.action) {
|
|
96
|
+
this.retire("OMP emitted overlapping compactions");
|
|
97
|
+
this.discardedEnds = 1;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (event.willRetry) {
|
|
101
|
+
operation.retrying = true;
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (operation.retrying) {
|
|
105
|
+
operation.retrying = false;
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
104
108
|
}
|
|
105
|
-
const state = event.aborted
|
|
109
|
+
const state: CompactionOutcome = event.aborted
|
|
106
110
|
? "canceled"
|
|
107
|
-
: event.
|
|
108
|
-
? "
|
|
109
|
-
: event.
|
|
110
|
-
? "
|
|
111
|
+
: event.skipped
|
|
112
|
+
? "skipped"
|
|
113
|
+
: event.errorMessage
|
|
114
|
+
? "failed"
|
|
111
115
|
: "completed";
|
|
112
116
|
this.finish(state, {
|
|
113
117
|
tokensBefore: event.result?.tokensBefore ?? event.result?.preTokens,
|
|
114
118
|
message: event.errorMessage,
|
|
115
119
|
});
|
|
116
|
-
return
|
|
120
|
+
return state;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
finish(
|
|
120
|
-
state:
|
|
124
|
+
state: CompactionOutcome,
|
|
121
125
|
options: { tokensBefore?: number | null; message?: string } = {},
|
|
122
126
|
): void {
|
|
123
127
|
const operation = this.operation;
|
|
@@ -24,6 +24,7 @@ export function isRuntimeConfigEvent(event: OmpRpcEvent): boolean {
|
|
|
24
24
|
return (
|
|
25
25
|
event.type === "model_changed" ||
|
|
26
26
|
event.type === "thinking_level_changed" ||
|
|
27
|
+
event.type === "config_update" ||
|
|
27
28
|
event.type === "retry_fallback_applied" ||
|
|
28
29
|
event.type === "retry_fallback_succeeded"
|
|
29
30
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import type { ProviderError } from "@getpaseo/plugin/server/provider";
|
|
3
3
|
import type { OmpMessage, OmpRpcEvent } from "./omp-rpc-protocol";
|
|
4
|
+
import type { CompactionOutcome } from "./session-compaction";
|
|
4
5
|
|
|
5
6
|
export type PendingUser = {
|
|
6
7
|
clientMessageId: string;
|
|
@@ -12,7 +13,7 @@ export type PendingUser = {
|
|
|
12
13
|
type TerminalCorrelationEvidence = "fresh-native-user" | "current-assistant";
|
|
13
14
|
|
|
14
15
|
type TerminalCorrelation = {
|
|
15
|
-
policy: "initial-turn" | "ordered-legacy" | "native-command";
|
|
16
|
+
policy: "initial-turn" | "ordered-legacy" | "native-command" | "native-run";
|
|
16
17
|
evidence: Map<TerminalCorrelationEvidence, number>;
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -24,6 +25,7 @@ export type TerminalCandidate = {
|
|
|
24
25
|
| "initial-turn"
|
|
25
26
|
| "ordered-legacy"
|
|
26
27
|
| "native-command"
|
|
28
|
+
| "native-run"
|
|
27
29
|
| "interrupted"
|
|
28
30
|
| "ambiguous";
|
|
29
31
|
};
|
|
@@ -55,6 +57,7 @@ export type ActiveTurn = {
|
|
|
55
57
|
usageSampleFloor: number;
|
|
56
58
|
manualCompaction: boolean;
|
|
57
59
|
manualCompactionPending: boolean;
|
|
60
|
+
manualCompactionOutcome?: CompactionOutcome;
|
|
58
61
|
manualCompactionDeadlineTimer?: unknown;
|
|
59
62
|
agentEndPending: boolean;
|
|
60
63
|
agentEndRetryTimer?: unknown;
|
|
@@ -64,11 +67,14 @@ export type ActiveTurn = {
|
|
|
64
67
|
terminalizing: boolean;
|
|
65
68
|
terminalization?: Promise<void>;
|
|
66
69
|
terminalOutcome?: TurnOutcome;
|
|
70
|
+
/** Listeners may hold this turn's terminal state from here on, even if publishing threw. */
|
|
71
|
+
terminalEmitAttempted: boolean;
|
|
67
72
|
operationalTerminalStage?: "failed" | "unresolved";
|
|
68
73
|
terminalWake?: VoidDeferred;
|
|
69
74
|
steerReady: VoidDeferred;
|
|
70
75
|
steersInFlight: number;
|
|
71
76
|
deferredAgentEnd?: TerminalCandidate;
|
|
77
|
+
heldAgentEnd?: HeldAgentEnd;
|
|
72
78
|
activeToolCallIds: Set<string>;
|
|
73
79
|
bufferedEvents: OmpRpcEvent[];
|
|
74
80
|
pendingUsers: PendingUser[];
|
|
@@ -82,6 +88,38 @@ export type ActiveTurn = {
|
|
|
82
88
|
lastCompletedAssistantEntryId?: string;
|
|
83
89
|
};
|
|
84
90
|
|
|
91
|
+
type RunEvidence = Pick<
|
|
92
|
+
ActiveTurn,
|
|
93
|
+
| "completedMessageCount"
|
|
94
|
+
| "streamedMessageEntryIds"
|
|
95
|
+
| "streamedMessageIdentityComplete"
|
|
96
|
+
| "lastCompletedAssistantOutcome"
|
|
97
|
+
| "lastCompletedAssistantEntryId"
|
|
98
|
+
>;
|
|
99
|
+
|
|
100
|
+
/** A run that ended with `isTerminal: false`, kept until another run starts or OMP settles. */
|
|
101
|
+
export type HeldAgentEnd = {
|
|
102
|
+
event: Extract<OmpRpcEvent, { type: "agent_end" }>;
|
|
103
|
+
arrivalSequence: number;
|
|
104
|
+
evidence: RunEvidence;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/** Hands over the evidence of the run that just ended and starts the next run's evidence empty. */
|
|
108
|
+
export function takeRunEvidence(turn: ActiveTurn): RunEvidence {
|
|
109
|
+
const evidence: RunEvidence = {
|
|
110
|
+
completedMessageCount: turn.completedMessageCount,
|
|
111
|
+
streamedMessageEntryIds: turn.streamedMessageEntryIds.splice(0),
|
|
112
|
+
streamedMessageIdentityComplete: turn.streamedMessageIdentityComplete,
|
|
113
|
+
lastCompletedAssistantOutcome: turn.lastCompletedAssistantOutcome,
|
|
114
|
+
lastCompletedAssistantEntryId: turn.lastCompletedAssistantEntryId,
|
|
115
|
+
};
|
|
116
|
+
turn.completedMessageCount = 0;
|
|
117
|
+
turn.streamedMessageIdentityComplete = true;
|
|
118
|
+
turn.lastCompletedAssistantOutcome = undefined;
|
|
119
|
+
turn.lastCompletedAssistantEntryId = undefined;
|
|
120
|
+
return evidence;
|
|
121
|
+
}
|
|
122
|
+
|
|
85
123
|
export type VoidDeferred = {
|
|
86
124
|
promise: Promise<void>;
|
|
87
125
|
resolve(value?: void | PromiseLike<void>): void;
|
|
@@ -249,6 +287,7 @@ export function createActiveTurn(
|
|
|
249
287
|
usageSampleFloor: 0,
|
|
250
288
|
agentEndPending: false,
|
|
251
289
|
terminalizing: false,
|
|
290
|
+
terminalEmitAttempted: false,
|
|
252
291
|
manualCompactionPending: manualCompaction,
|
|
253
292
|
manualCompaction,
|
|
254
293
|
activitySequence: 0,
|
|
@@ -276,6 +315,15 @@ export function createActiveTurn(
|
|
|
276
315
|
],
|
|
277
316
|
};
|
|
278
317
|
}
|
|
318
|
+
/** A run OMP started without a client prompt: an extension, goal, or background wake. */
|
|
319
|
+
export function createNativeRunTurn(generation: number): ActiveTurn {
|
|
320
|
+
return {
|
|
321
|
+
...createActiveTurn("", "", generation, "native-run"),
|
|
322
|
+
promptResultEmitted: true,
|
|
323
|
+
pendingUsers: [],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
279
327
|
export function classifyTerminalCandidate(
|
|
280
328
|
turn: ActiveTurn,
|
|
281
329
|
event: Extract<OmpRpcEvent, { type: "agent_end" }>,
|
|
@@ -286,6 +334,9 @@ export function classifyTerminalCandidate(
|
|
|
286
334
|
if (turn.terminalCorrelation.policy === "initial-turn") {
|
|
287
335
|
return { event, confidence: "initial-turn", arrivalSequence };
|
|
288
336
|
}
|
|
337
|
+
if (turn.terminalCorrelation.policy === "native-run") {
|
|
338
|
+
return { event, confidence: "native-run", arrivalSequence };
|
|
339
|
+
}
|
|
289
340
|
const assistantSequence = turn.terminalCorrelation.evidence.get("current-assistant");
|
|
290
341
|
if (
|
|
291
342
|
turn.terminalCorrelation.policy === "native-command" &&
|
|
@@ -73,10 +73,13 @@ import {
|
|
|
73
73
|
assistantTerminalOutcome,
|
|
74
74
|
classifyTerminalCandidate,
|
|
75
75
|
createActiveTurn,
|
|
76
|
+
createNativeRunTurn,
|
|
77
|
+
type HeldAgentEnd,
|
|
76
78
|
historyTerminalOutcome,
|
|
77
79
|
nativeEntryId,
|
|
78
80
|
type PendingUser,
|
|
79
81
|
type TerminalCandidate,
|
|
82
|
+
takeRunEvidence,
|
|
80
83
|
terminalOutcome,
|
|
81
84
|
unknownTerminalOutcomeError,
|
|
82
85
|
} from "./session-terminal";
|
|
@@ -330,6 +333,8 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
330
333
|
private configRevision = 0;
|
|
331
334
|
private recoveryUsesNativeConfig = false;
|
|
332
335
|
private activeAbort: PendingAbort | null = null;
|
|
336
|
+
/** Frames of a run that started while the previous terminal was being confirmed or published. */
|
|
337
|
+
private runStartBacklog: OmpRpcEvent[] | null = null;
|
|
333
338
|
private readonly usage: OmpSessionUsage;
|
|
334
339
|
private readonly compaction: OmpSessionCompaction;
|
|
335
340
|
private revertInFlight = false;
|
|
@@ -1458,33 +1463,46 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
1458
1463
|
} catch (error) {
|
|
1459
1464
|
if (this.closed || this.runtimeDead || turn.generation !== this.generation) return;
|
|
1460
1465
|
turn.manualCompactionPending = false;
|
|
1466
|
+
// OMP 18.13.3+ reports the outcome in manual_compaction_end before rejecting `compact`.
|
|
1467
|
+
const outcome = turn.manualCompactionOutcome;
|
|
1468
|
+
if (outcome === "skipped") {
|
|
1469
|
+
await this.finishTurn(turn, turn.interrupted ? "canceled" : "completed");
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1461
1472
|
const raw = providerError(error, "OMP compaction failed");
|
|
1462
1473
|
const failure = { message: this.dataFilter.text(raw.message, 4_096) };
|
|
1463
|
-
const state = turn.interrupted ? "canceled" : "failed";
|
|
1474
|
+
const state = turn.interrupted || outcome === "canceled" ? "canceled" : "failed";
|
|
1464
1475
|
this.compaction.finish(state, { message: failure.message });
|
|
1465
1476
|
await this.finishTurn(turn, state, state === "failed" ? failure : undefined, true, true);
|
|
1466
1477
|
}
|
|
1467
1478
|
}
|
|
1468
1479
|
|
|
1469
1480
|
async canStopRuntime(): Promise<boolean> {
|
|
1470
|
-
|
|
1481
|
+
if (this.hasLocalRuntimeWork()) return false;
|
|
1482
|
+
const state = await this.runtime.getState();
|
|
1483
|
+
// OMP can start a run of its own while the state lookup is pending.
|
|
1484
|
+
if (this.hasLocalRuntimeWork()) return false;
|
|
1485
|
+
// OMP builds that predate these fields report neither, which keeps the runtime alive.
|
|
1486
|
+
return (
|
|
1487
|
+
state.isSettled === true &&
|
|
1488
|
+
state.hasPendingAsyncWork === false &&
|
|
1489
|
+
!state.isStreaming &&
|
|
1490
|
+
!state.isCompacting
|
|
1491
|
+
);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
private hasLocalRuntimeWork(): boolean {
|
|
1495
|
+
return (
|
|
1471
1496
|
this.closed ||
|
|
1472
1497
|
this.runtimeDead !== null ||
|
|
1473
1498
|
this.recoveryPromise !== null ||
|
|
1474
1499
|
this.activeTurn !== null ||
|
|
1500
|
+
this.runStartBacklog !== null ||
|
|
1475
1501
|
this.activeAbort !== null ||
|
|
1476
1502
|
this.revertInFlight ||
|
|
1477
1503
|
this.configMutationInFlight ||
|
|
1478
1504
|
this.permissions.hasPending() ||
|
|
1479
|
-
Boolean(this.subsessions?.hasActiveChildren())
|
|
1480
|
-
if (hasLocalWork) return false;
|
|
1481
|
-
const state = await this.runtime.getState();
|
|
1482
|
-
// OMP builds that predate these fields report neither, which keeps the runtime alive.
|
|
1483
|
-
return (
|
|
1484
|
-
state.isSettled === true &&
|
|
1485
|
-
state.hasPendingAsyncWork === false &&
|
|
1486
|
-
!state.isStreaming &&
|
|
1487
|
-
!state.isCompacting
|
|
1505
|
+
Boolean(this.subsessions?.hasActiveChildren())
|
|
1488
1506
|
);
|
|
1489
1507
|
}
|
|
1490
1508
|
|
|
@@ -2259,6 +2277,17 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2259
2277
|
|
|
2260
2278
|
private handleRuntimeEvent(event: OmpRpcEvent): void {
|
|
2261
2279
|
if (this.closed) return;
|
|
2280
|
+
if (this.runStartBacklog) {
|
|
2281
|
+
this.bufferEvent(this.runStartBacklog, event);
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
// A run that starts while the previous terminal is being confirmed or published waits for it,
|
|
2285
|
+
// so neither the old turn's outcome nor the new run's frames are lost.
|
|
2286
|
+
const pendingTerminal = this.activeTurn?.terminalization ?? this.activeTurn?.agentEndCheck;
|
|
2287
|
+
if (event.type === "agent_start" && pendingTerminal) {
|
|
2288
|
+
this.deferRunStart(event, pendingTerminal);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2262
2291
|
if (
|
|
2263
2292
|
event.type === "subagent_lifecycle" ||
|
|
2264
2293
|
event.type === "subagent_progress" ||
|
|
@@ -2336,7 +2365,8 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2336
2365
|
event.type === "ttsr_triggered" ||
|
|
2337
2366
|
event.type === "cache_warming_start" ||
|
|
2338
2367
|
event.type === "cache_warming_end" ||
|
|
2339
|
-
event.type === "queue_update"
|
|
2368
|
+
event.type === "queue_update" ||
|
|
2369
|
+
event.type === "session_info_update"
|
|
2340
2370
|
) {
|
|
2341
2371
|
return;
|
|
2342
2372
|
}
|
|
@@ -2353,7 +2383,8 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2353
2383
|
event.type === "auto_retry_end" ||
|
|
2354
2384
|
event.type === "compaction_start" ||
|
|
2355
2385
|
event.type === "compaction_end" ||
|
|
2356
|
-
event.type === "advisor_yielded"
|
|
2386
|
+
event.type === "advisor_yielded" ||
|
|
2387
|
+
event.type === "extension_error"
|
|
2357
2388
|
) {
|
|
2358
2389
|
this.projector.projectPassive(event);
|
|
2359
2390
|
return;
|
|
@@ -2367,13 +2398,23 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2367
2398
|
this.scheduleCommittedConfigRefresh();
|
|
2368
2399
|
return;
|
|
2369
2400
|
}
|
|
2370
|
-
if (
|
|
2401
|
+
if (
|
|
2402
|
+
event.type === "model_changed" ||
|
|
2403
|
+
event.type === "thinking_level_changed" ||
|
|
2404
|
+
event.type === "config_update"
|
|
2405
|
+
) {
|
|
2371
2406
|
this.scheduleCommittedConfigRefresh();
|
|
2372
2407
|
return;
|
|
2373
2408
|
}
|
|
2374
|
-
const turn =
|
|
2409
|
+
const turn =
|
|
2410
|
+
this.activeTurn ?? (event.type === "agent_start" ? this.openNativeRunTurn() : null);
|
|
2375
2411
|
if (!turn) {
|
|
2376
|
-
if (
|
|
2412
|
+
if (
|
|
2413
|
+
event.type === "auto_compaction_start" ||
|
|
2414
|
+
event.type === "auto_compaction_end" ||
|
|
2415
|
+
event.type === "manual_compaction_start" ||
|
|
2416
|
+
event.type === "manual_compaction_end"
|
|
2417
|
+
) {
|
|
2377
2418
|
this.projector.projectPassive(event);
|
|
2378
2419
|
}
|
|
2379
2420
|
return;
|
|
@@ -2386,27 +2427,69 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2386
2427
|
return;
|
|
2387
2428
|
}
|
|
2388
2429
|
if (turn.starting) {
|
|
2389
|
-
|
|
2390
|
-
turn.bufferedEvents.length >= MAX_BUFFERED_TURN_EVENTS ||
|
|
2391
|
-
retainedBytes(turn.bufferedEvents, MAX_BUFFERED_TURN_BYTES) +
|
|
2392
|
-
boundedJsonBytes(
|
|
2393
|
-
event,
|
|
2394
|
-
MAX_BUFFERED_TURN_BYTES,
|
|
2395
|
-
MAX_BUFFERED_VALUE_ITEMS,
|
|
2396
|
-
MAX_BUFFERED_TURN_BYTES,
|
|
2397
|
-
MAX_BUFFERED_VALUE_NODES,
|
|
2398
|
-
) >
|
|
2399
|
-
MAX_BUFFERED_TURN_BYTES
|
|
2400
|
-
) {
|
|
2401
|
-
this.handleRuntimeFailure();
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
turn.bufferedEvents.push(event);
|
|
2430
|
+
this.bufferEvent(turn.bufferedEvents, event);
|
|
2405
2431
|
return;
|
|
2406
2432
|
}
|
|
2407
2433
|
this.handleTurnEvent(turn, event);
|
|
2408
2434
|
}
|
|
2409
2435
|
|
|
2436
|
+
private bufferEvent(buffer: OmpRpcEvent[], event: OmpRpcEvent): void {
|
|
2437
|
+
if (
|
|
2438
|
+
buffer.length >= MAX_BUFFERED_TURN_EVENTS ||
|
|
2439
|
+
retainedBytes(buffer, MAX_BUFFERED_TURN_BYTES) +
|
|
2440
|
+
boundedJsonBytes(
|
|
2441
|
+
event,
|
|
2442
|
+
MAX_BUFFERED_TURN_BYTES,
|
|
2443
|
+
MAX_BUFFERED_VALUE_ITEMS,
|
|
2444
|
+
MAX_BUFFERED_TURN_BYTES,
|
|
2445
|
+
MAX_BUFFERED_VALUE_NODES,
|
|
2446
|
+
) >
|
|
2447
|
+
MAX_BUFFERED_TURN_BYTES
|
|
2448
|
+
) {
|
|
2449
|
+
this.handleRuntimeFailure();
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
buffer.push(event);
|
|
2453
|
+
}
|
|
2454
|
+
|
|
2455
|
+
private deferRunStart(event: OmpRpcEvent, pendingTerminal: Promise<void>): void {
|
|
2456
|
+
const backlog = [event];
|
|
2457
|
+
this.runStartBacklog = backlog;
|
|
2458
|
+
const replay = () => {
|
|
2459
|
+
if (this.runStartBacklog !== backlog) return;
|
|
2460
|
+
this.runStartBacklog = null;
|
|
2461
|
+
for (const buffered of backlog) this.handleRuntimeEvent(buffered);
|
|
2462
|
+
};
|
|
2463
|
+
void pendingTerminal.then(replay, replay);
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
private scheduleHeldTurnCheck(turn: ActiveTurn, held: HeldAgentEnd): void {
|
|
2467
|
+
this.scheduler.set(() => this.checkHeldTurn(turn, held), USAGE_POLL_MS);
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/** Released OMP does not always write `session_settled` once a held run's async work is gone. */
|
|
2471
|
+
private async checkHeldTurn(turn: ActiveTurn, held: HeldAgentEnd): Promise<void> {
|
|
2472
|
+
if (turn.heldAgentEnd !== held || this.activeTurn !== turn) return;
|
|
2473
|
+
const state = await this.usage.boundedSnapshot(turn, FINAL_USAGE_WAIT_MS);
|
|
2474
|
+
if (turn.heldAgentEnd !== held || this.activeTurn !== turn) return;
|
|
2475
|
+
const settled =
|
|
2476
|
+
state?.isSettled === true &&
|
|
2477
|
+
state.hasPendingAsyncWork === false &&
|
|
2478
|
+
!state.isStreaming &&
|
|
2479
|
+
!state.isCompacting;
|
|
2480
|
+
if (settled) this.handleTurnEvent(turn, { type: "session_settled" });
|
|
2481
|
+
else this.scheduleHeldTurnCheck(turn, held);
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
private openNativeRunTurn(): ActiveTurn {
|
|
2485
|
+
const turn = createNativeRunTurn(this.generation);
|
|
2486
|
+
this.activeTurn = turn;
|
|
2487
|
+
turn.acknowledged = true;
|
|
2488
|
+
this.startTurn(turn);
|
|
2489
|
+
this.projector.acceptLiveTurn(turn.turnId);
|
|
2490
|
+
return turn;
|
|
2491
|
+
}
|
|
2492
|
+
|
|
2410
2493
|
private handleTurnEvent(
|
|
2411
2494
|
turn: ActiveTurn,
|
|
2412
2495
|
event: OmpRpcEvent,
|
|
@@ -2476,10 +2559,27 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2476
2559
|
this.compaction.start(turn, "auto", event.action);
|
|
2477
2560
|
return;
|
|
2478
2561
|
}
|
|
2479
|
-
if (event.type === "
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2562
|
+
if (event.type === "manual_compaction_start") {
|
|
2563
|
+
turn.nativeActivity = true;
|
|
2564
|
+
this.cancelLocalOnlyCompletion(turn);
|
|
2565
|
+
// A no-op when this turn's own `compact` call already opened the operation.
|
|
2566
|
+
this.compaction.start(turn, "manual");
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
if (event.type === "auto_compaction_end" || event.type === "manual_compaction_end") {
|
|
2570
|
+
const outcome = this.compaction.handleEnd(turn, event);
|
|
2571
|
+
if (outcome && turn.manualCompaction) turn.manualCompactionOutcome = outcome;
|
|
2572
|
+
if (outcome) this.usage.schedulePoll(turn, USAGE_REFRESH_MS);
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
if (event.type === "session_settled") {
|
|
2576
|
+
// OMP is quiet, so the held end closes this stretch; a later run opens its own turn.
|
|
2577
|
+
const held = turn.heldAgentEnd;
|
|
2578
|
+
if (!held) return;
|
|
2579
|
+
turn.heldAgentEnd = undefined;
|
|
2580
|
+
Object.assign(turn, held.evidence);
|
|
2581
|
+
// Ownership is judged as of the held end's arrival, never from evidence that came later.
|
|
2582
|
+
this.acceptTerminal(turn, { ...held.event, isTerminal: true }, held.arrivalSequence);
|
|
2483
2583
|
return;
|
|
2484
2584
|
}
|
|
2485
2585
|
if (event.type === "agent_end" && turn.manualCompactionPending) return;
|
|
@@ -2502,35 +2602,30 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2502
2602
|
return;
|
|
2503
2603
|
}
|
|
2504
2604
|
}
|
|
2605
|
+
if (event.type === "agent_start") {
|
|
2606
|
+
turn.heldAgentEnd = undefined;
|
|
2607
|
+
// Evidence is per run; records OMP writes between runs belong to neither.
|
|
2608
|
+
takeRunEvidence(turn);
|
|
2609
|
+
if (turn.agentEndPending && !turn.interrupted) {
|
|
2610
|
+
// A terminal waiting to retry its idle check is stale once another run starts.
|
|
2611
|
+
this.resetAgentEndProbe(turn);
|
|
2612
|
+
turn.deferredAgentEnd = undefined;
|
|
2613
|
+
this.usage.poll(turn);
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2505
2616
|
if (event.type === "agent_end") {
|
|
2506
2617
|
if (event.isTerminal === false) {
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
const candidate: TerminalCandidate = turn.interrupted
|
|
2515
|
-
? { event, confidence: "interrupted", arrivalSequence: turn.activitySequence }
|
|
2516
|
-
: classifyTerminalCandidate(turn, event);
|
|
2517
|
-
if (candidate.confidence === "ambiguous") {
|
|
2518
|
-
this.deferAmbiguousTerminal(turn, candidate);
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2521
|
-
turn.nativeActivity = true;
|
|
2522
|
-
this.cancelLocalOnlyCompletion(turn);
|
|
2523
|
-
if (
|
|
2524
|
-
(!turn.interrupted &&
|
|
2525
|
-
candidate.confidence !== "keyed" &&
|
|
2526
|
-
this.hasTerminalConflict(turn, false)) ||
|
|
2527
|
-
turn.steersInFlight > 0 ||
|
|
2528
|
-
turn.terminalizing
|
|
2529
|
-
) {
|
|
2530
|
-
turn.deferredAgentEnd = candidate;
|
|
2618
|
+
const held = {
|
|
2619
|
+
event,
|
|
2620
|
+
arrivalSequence: turn.activitySequence,
|
|
2621
|
+
evidence: takeRunEvidence(turn),
|
|
2622
|
+
};
|
|
2623
|
+
turn.heldAgentEnd = held;
|
|
2624
|
+
this.scheduleHeldTurnCheck(turn, held);
|
|
2531
2625
|
return;
|
|
2532
2626
|
}
|
|
2533
|
-
|
|
2627
|
+
turn.heldAgentEnd = undefined;
|
|
2628
|
+
this.acceptTerminal(turn, event, turn.activitySequence);
|
|
2534
2629
|
return;
|
|
2535
2630
|
}
|
|
2536
2631
|
if (isNativeTurnActivity(event)) {
|
|
@@ -2566,6 +2661,33 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
2566
2661
|
if (event.type === "tool_execution_end") this.resumeDeferredAgentEnd();
|
|
2567
2662
|
}
|
|
2568
2663
|
|
|
2664
|
+
private acceptTerminal(
|
|
2665
|
+
turn: ActiveTurn,
|
|
2666
|
+
event: Extract<OmpRpcEvent, { type: "agent_end" }>,
|
|
2667
|
+
arrivalSequence: number,
|
|
2668
|
+
): void {
|
|
2669
|
+
const candidate: TerminalCandidate = turn.interrupted
|
|
2670
|
+
? { event, confidence: "interrupted", arrivalSequence }
|
|
2671
|
+
: classifyTerminalCandidate(turn, event, arrivalSequence);
|
|
2672
|
+
if (candidate.confidence === "ambiguous") {
|
|
2673
|
+
this.deferAmbiguousTerminal(turn, candidate);
|
|
2674
|
+
return;
|
|
2675
|
+
}
|
|
2676
|
+
turn.nativeActivity = true;
|
|
2677
|
+
this.cancelLocalOnlyCompletion(turn);
|
|
2678
|
+
if (
|
|
2679
|
+
(!turn.interrupted &&
|
|
2680
|
+
candidate.confidence !== "keyed" &&
|
|
2681
|
+
this.hasTerminalConflict(turn, false)) ||
|
|
2682
|
+
turn.steersInFlight > 0 ||
|
|
2683
|
+
turn.terminalizing
|
|
2684
|
+
) {
|
|
2685
|
+
turn.deferredAgentEnd = candidate;
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
this.beginTerminalization(turn, candidate);
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2569
2691
|
private projectUserEcho(turn: ActiveTurn, message: OmpMessage): void {
|
|
2570
2692
|
turn.userEchoObserved = true;
|
|
2571
2693
|
const entryId = nativeEntryId(message);
|
|
@@ -3033,6 +3155,7 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
3033
3155
|
if (!turn.agentEndPending || turn.terminal || this.activeTurn !== turn) return;
|
|
3034
3156
|
if (
|
|
3035
3157
|
candidate.confidence === "keyed" ||
|
|
3158
|
+
candidate.confidence === "native-run" ||
|
|
3036
3159
|
(candidate.confidence === "initial-turn" && !turn.userEchoObserved)
|
|
3037
3160
|
) {
|
|
3038
3161
|
void this.completeAgentEnd(turn, candidate.event);
|
|
@@ -3146,14 +3269,16 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
3146
3269
|
return;
|
|
3147
3270
|
}
|
|
3148
3271
|
if (state) {
|
|
3149
|
-
|
|
3272
|
+
// A run start buffered behind this check proves this end was the last one before it, so a
|
|
3273
|
+
// streaming state belongs to that new run, not to this turn.
|
|
3274
|
+
if ((state.isStreaming || state.isCompacting) && !this.runStartBacklog) {
|
|
3150
3275
|
this.ignoreActiveTerminalCandidate(turn, candidate);
|
|
3151
3276
|
return;
|
|
3152
3277
|
}
|
|
3153
3278
|
await this.completeAgentEnd(turn, candidate.event, true);
|
|
3154
3279
|
return;
|
|
3155
3280
|
}
|
|
3156
|
-
if (turn.userEchoObserved) {
|
|
3281
|
+
if (turn.userEchoObserved && candidate.confidence !== "native-run") {
|
|
3157
3282
|
await this.cancelIndeterminateTerminal(
|
|
3158
3283
|
turn,
|
|
3159
3284
|
"OMP agent_end state could not be confirmed",
|
|
@@ -3389,6 +3514,7 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
3389
3514
|
throw error;
|
|
3390
3515
|
}
|
|
3391
3516
|
this.unclaimedBranchEntries.length = 0;
|
|
3517
|
+
turn.terminalEmitAttempted = true;
|
|
3392
3518
|
this.emit({
|
|
3393
3519
|
type: "session.turn",
|
|
3394
3520
|
sessionId: this.id,
|
|
@@ -3398,17 +3524,42 @@ export class OmpProviderSession implements OmpOpenedSession {
|
|
|
3398
3524
|
});
|
|
3399
3525
|
this.runtimeTurnCompleted = true;
|
|
3400
3526
|
if (this.activeTurn === turn) this.activeTurn = null;
|
|
3401
|
-
})();
|
|
3527
|
+
})().catch(() => this.failTerminalization(turn));
|
|
3402
3528
|
turn.terminalization = terminalization;
|
|
3403
3529
|
return terminalization;
|
|
3404
3530
|
}
|
|
3405
3531
|
|
|
3532
|
+
/** A turn whose terminal state could not be published must not keep owning the session. */
|
|
3533
|
+
private failTerminalization(turn: ActiveTurn): void {
|
|
3534
|
+
const message = "OMP turn could not be finalized";
|
|
3535
|
+
turn.terminal = true;
|
|
3536
|
+
if (this.activeTurn === turn) this.activeTurn = null;
|
|
3537
|
+
this.recordOperationalFailure({ category: "terminal-outcome", stage: "failed" });
|
|
3538
|
+
// Once the terminal emit was attempted, earlier listeners may already hold it; a second,
|
|
3539
|
+
// conflicting terminal would be worse than failing the runtime alone.
|
|
3540
|
+
if (!turn.terminalEmitAttempted) {
|
|
3541
|
+
try {
|
|
3542
|
+
this.emit({
|
|
3543
|
+
type: "session.turn",
|
|
3544
|
+
sessionId: this.id,
|
|
3545
|
+
turnId: turn.turnId,
|
|
3546
|
+
state: "failed",
|
|
3547
|
+
error: { message },
|
|
3548
|
+
});
|
|
3549
|
+
} catch {
|
|
3550
|
+
// The listener that failed may fail again; the runtime failure below still applies.
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
this.handleRuntimeFailure(message);
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3406
3556
|
private invalidateRuntime(
|
|
3407
3557
|
message: string,
|
|
3408
3558
|
compactionState: "failed" | "canceled" = "failed",
|
|
3409
3559
|
): void {
|
|
3410
3560
|
this.imageMaterializer.clear();
|
|
3411
3561
|
if (this.closed || this.runtimeDead) return;
|
|
3562
|
+
this.runStartBacklog = null;
|
|
3412
3563
|
this.compaction.resetDiscardedEnds();
|
|
3413
3564
|
this.permissions.resolveAllPermissions();
|
|
3414
3565
|
this.recoveryUsesNativeConfig ||=
|
|
@@ -467,6 +467,9 @@ export class OmpTimelineProjector {
|
|
|
467
467
|
event.type === "auto_compaction_end" ||
|
|
468
468
|
event.type === "compaction_start" ||
|
|
469
469
|
event.type === "compaction_end" ||
|
|
470
|
+
event.type === "manual_compaction_start" ||
|
|
471
|
+
event.type === "manual_compaction_end" ||
|
|
472
|
+
event.type === "extension_error" ||
|
|
470
473
|
event.type === "advisor_yielded" ||
|
|
471
474
|
event.type === "config_warnings_changed" ||
|
|
472
475
|
event.type === "advisor_cost_changed" ||
|
|
@@ -743,6 +746,19 @@ export class OmpTimelineProjector {
|
|
|
743
746
|
});
|
|
744
747
|
return;
|
|
745
748
|
}
|
|
749
|
+
if (event.type === "extension_error") {
|
|
750
|
+
this.noticeSequence += 1;
|
|
751
|
+
this.publish({
|
|
752
|
+
type: "notification",
|
|
753
|
+
id: `omp:extension-error:${this.noticeSequence}`,
|
|
754
|
+
level: "error",
|
|
755
|
+
message: this.dataFilter.text(
|
|
756
|
+
`OMP extension ${event.extensionPath} failed in ${event.event}: ${event.error}`,
|
|
757
|
+
64 * 1024,
|
|
758
|
+
),
|
|
759
|
+
});
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
746
762
|
if (event.type === "advisor_yielded") {
|
|
747
763
|
this.noticeSequence += 1;
|
|
748
764
|
this.publish({
|
|
@@ -753,7 +769,11 @@ export class OmpTimelineProjector {
|
|
|
753
769
|
});
|
|
754
770
|
return;
|
|
755
771
|
}
|
|
756
|
-
if (
|
|
772
|
+
if (
|
|
773
|
+
event.type === "auto_compaction_start" ||
|
|
774
|
+
event.type === "compaction_start" ||
|
|
775
|
+
event.type === "manual_compaction_start"
|
|
776
|
+
) {
|
|
757
777
|
const trigger = event.type === "auto_compaction_start" ? "auto" : "manual";
|
|
758
778
|
const action = event.type === "auto_compaction_start" ? event.action : undefined;
|
|
759
779
|
if (this.discardedCompactionEnds > 0) {
|
|
@@ -781,7 +801,11 @@ export class OmpTimelineProjector {
|
|
|
781
801
|
this.publish({ type: "compaction", id: slot.id, status: "loading", trigger });
|
|
782
802
|
return;
|
|
783
803
|
}
|
|
784
|
-
if (
|
|
804
|
+
if (
|
|
805
|
+
event.type === "auto_compaction_end" ||
|
|
806
|
+
event.type === "compaction_end" ||
|
|
807
|
+
event.type === "manual_compaction_end"
|
|
808
|
+
) {
|
|
785
809
|
if (this.discardedCompactionEnds > 0) {
|
|
786
810
|
this.discardedCompactionEnds -= 1;
|
|
787
811
|
return;
|
|
@@ -803,7 +827,7 @@ export class OmpTimelineProjector {
|
|
|
803
827
|
this.discardedCompactionEnds = 1;
|
|
804
828
|
return;
|
|
805
829
|
}
|
|
806
|
-
if (event.willRetry) {
|
|
830
|
+
if (event.type !== "manual_compaction_end" && event.willRetry) {
|
|
807
831
|
slot.retrying = true;
|
|
808
832
|
return;
|
|
809
833
|
}
|
|
@@ -826,10 +850,9 @@ export class OmpTimelineProjector {
|
|
|
826
850
|
type: "notification",
|
|
827
851
|
id: `${slot.id}:skipped`,
|
|
828
852
|
level: "warning",
|
|
829
|
-
message: "OMP compaction was skipped",
|
|
853
|
+
message: this.dataFilter.text(event.errorMessage ?? "OMP compaction was skipped", 4_096),
|
|
830
854
|
});
|
|
831
|
-
}
|
|
832
|
-
if (event.aborted || event.errorMessage) {
|
|
855
|
+
} else if (event.aborted || event.errorMessage) {
|
|
833
856
|
this.publish({
|
|
834
857
|
type: "error",
|
|
835
858
|
id: `${slot.id}:error`,
|