@omercnet/paseo-omp 5.1.1-next.336.1 → 5.1.1-next.340.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/OVERVIEW.md +1 -0
- package/README.md +2 -2
- package/client/subagents-state.ts +108 -0
- package/client/subagents-strip.tsx +391 -0
- package/index.client.tsx +9 -0
- package/index.server.ts +16 -0
- package/package.json +3 -3
- package/server/package-version.ts +1 -1
- package/server/provider/connection.ts +16 -3
- package/server/provider/omp-rpc-protocol.ts +39 -2
- package/server/provider/omp-rpc-transport.ts +5 -2
- package/server/provider/omp-rpc.ts +25 -0
- package/server/provider/registration.ts +3 -0
- 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 +252 -66
- package/server/provider/subsessions.ts +109 -7
- package/server/provider/timeline-projector.ts +29 -6
- package/server/subagents.ts +97 -0
- package/shared/subagents.ts +60 -0
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
OmpOperationalFailure,
|
|
14
14
|
OmpOperationalFailureReporter,
|
|
15
15
|
} from "../operational-failure-diagnostics";
|
|
16
|
+
import type { OmpSubagentControlRegistry } from "../subagents";
|
|
16
17
|
import { discoverOmpCatalog } from "./catalog";
|
|
17
18
|
import { normalizeOmpCatalogOptions } from "./config-normalization";
|
|
18
19
|
import type { OmpMcpConnector } from "./host-tools";
|
|
@@ -724,6 +725,7 @@ export function createOmpConnection(
|
|
|
724
725
|
reportOperationalFailure: OmpOperationalFailureReporter = () => {},
|
|
725
726
|
resolveHostInheritEnv?: () => Promise<readonly string[]>,
|
|
726
727
|
sessionLimit = new OmpSessionLimit(),
|
|
728
|
+
subagentControlRegistry?: OmpSubagentControlRegistry,
|
|
727
729
|
): ProviderConnection {
|
|
728
730
|
const errorDetails = (error: unknown, fallback: string): { message: string } => {
|
|
729
731
|
if (isOmpPublicError(error)) return { message: error.message };
|
|
@@ -758,6 +760,7 @@ export function createOmpConnection(
|
|
|
758
760
|
session: OmpOpenedSession;
|
|
759
761
|
nativeSessionId?: string;
|
|
760
762
|
removeBrowserAuthorization?: () => void;
|
|
763
|
+
removeSubagentControls?: () => void;
|
|
761
764
|
}
|
|
762
765
|
>();
|
|
763
766
|
const opening = new Map<
|
|
@@ -791,6 +794,7 @@ export function createOmpConnection(
|
|
|
791
794
|
if (slot?.token !== token) return false;
|
|
792
795
|
sessions.delete(sessionId);
|
|
793
796
|
slot.removeBrowserAuthorization?.();
|
|
797
|
+
slot.removeSubagentControls?.();
|
|
794
798
|
return true;
|
|
795
799
|
};
|
|
796
800
|
|
|
@@ -1033,12 +1037,12 @@ export function createOmpConnection(
|
|
|
1033
1037
|
nativeReservations.release(nativeSessionId, token);
|
|
1034
1038
|
return;
|
|
1035
1039
|
}
|
|
1036
|
-
const
|
|
1040
|
+
const agentId = input.config.env.PASEO_AGENT_ID?.trim() || input.sessionId;
|
|
1037
1041
|
// A history session has no live Paseo browser tools to authorize.
|
|
1038
1042
|
const browserAuthorization = historyOnly
|
|
1039
1043
|
? undefined
|
|
1040
1044
|
: browserAuthorizationRegistry?.register(
|
|
1041
|
-
|
|
1045
|
+
agentId,
|
|
1042
1046
|
session.openPaseoBrowser.bind(session),
|
|
1043
1047
|
);
|
|
1044
1048
|
session.setBrowserAuthorizationIssuer(browserAuthorization?.issue ?? null);
|
|
@@ -1048,11 +1052,17 @@ export function createOmpConnection(
|
|
|
1048
1052
|
browserAuthorization.remove();
|
|
1049
1053
|
}
|
|
1050
1054
|
: undefined;
|
|
1055
|
+
// A history session has no runtime, so it has no subagents to control.
|
|
1056
|
+
const removeSubagentControls =
|
|
1057
|
+
session instanceof OmpProviderSession
|
|
1058
|
+
? subagentControlRegistry?.register(agentId, session)
|
|
1059
|
+
: undefined;
|
|
1051
1060
|
sessions.set(input.sessionId, {
|
|
1052
1061
|
token,
|
|
1053
1062
|
session,
|
|
1054
1063
|
nativeSessionId,
|
|
1055
1064
|
...(removeBrowserAuthorization ? { removeBrowserAuthorization } : {}),
|
|
1065
|
+
...(removeSubagentControls ? { removeSubagentControls } : {}),
|
|
1056
1066
|
});
|
|
1057
1067
|
await session.publishOpened(input.requestId);
|
|
1058
1068
|
if (
|
|
@@ -1204,7 +1214,10 @@ export function createOmpConnection(
|
|
|
1204
1214
|
shutdown.abort(shutdownReason);
|
|
1205
1215
|
for (const { controller } of opening.values()) controller.abort(shutdownReason);
|
|
1206
1216
|
for (const { session } of sessions.values()) session.beginConnectionShutdown();
|
|
1207
|
-
for (const slot of sessions.values())
|
|
1217
|
+
for (const slot of sessions.values()) {
|
|
1218
|
+
slot.removeBrowserAuthorization?.();
|
|
1219
|
+
slot.removeSubagentControls?.();
|
|
1220
|
+
}
|
|
1208
1221
|
const failures: unknown[] = [];
|
|
1209
1222
|
const operationBatch = [...activeOperations];
|
|
1210
1223
|
const operationResults = await Promise.allSettled(operationBatch);
|
|
@@ -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",
|
|
@@ -142,15 +144,21 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
142
144
|
abort_and_restore_queue: "unsupported",
|
|
143
145
|
btw: "unsupported",
|
|
144
146
|
btw_cancel: "unsupported",
|
|
145
|
-
cancel_subagent: "
|
|
147
|
+
cancel_subagent: "implemented",
|
|
146
148
|
fork: "unsupported",
|
|
147
149
|
get_btw_history: "unsupported",
|
|
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",
|
|
@@ -158,7 +166,7 @@ export const OMP_RPC_COMMAND_POLICIES = {
|
|
|
158
166
|
set_ask_dialog: "implemented",
|
|
159
167
|
set_cache_warming: "unsupported",
|
|
160
168
|
set_slow_mode: "unsupported",
|
|
161
|
-
steer_subagent: "
|
|
169
|
+
steer_subagent: "implemented",
|
|
162
170
|
} as const;
|
|
163
171
|
export const OMP_RPC_RESPONSE_POLICIES = OMP_RPC_COMMAND_POLICIES;
|
|
164
172
|
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
@@ -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"),
|
|
@@ -1967,6 +2001,8 @@ export const OmpSubagentsResultSchema = z.object({
|
|
|
1967
2001
|
)
|
|
1968
2002
|
.max(1_024),
|
|
1969
2003
|
});
|
|
2004
|
+
export const OmpCancelSubagentResultSchema = z.object({ cancelled: z.boolean() });
|
|
2005
|
+
export const OmpSteerSubagentResultSchema = z.object({}).strict().optional();
|
|
1970
2006
|
export const OmpSubagentMessagesResultSchema = z.object({
|
|
1971
2007
|
sessionFile: boundedString(MAX_PATH_LENGTH),
|
|
1972
2008
|
fromByte: z.number().int().nonnegative(),
|
|
@@ -1997,6 +2033,7 @@ export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["
|
|
|
1997
2033
|
export type OmpRpcEvent =
|
|
1998
2034
|
| z.infer<typeof OmpRuntimeEventSchema>
|
|
1999
2035
|
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
2036
|
+
| { type: "session_settled" }
|
|
2000
2037
|
| { type: "process_exit"; error: string };
|
|
2001
2038
|
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
2002
2039
|
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", {
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
OmpBranchMessagesResultSchema,
|
|
17
17
|
type OmpBranchResult,
|
|
18
18
|
OmpBranchResultSchema,
|
|
19
|
+
OmpCancelSubagentResultSchema,
|
|
19
20
|
type OmpCompactionResult,
|
|
20
21
|
OmpCompactionResultSchema,
|
|
21
22
|
type OmpExtensionUiResponse,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
OmpSessionStateSchema,
|
|
42
43
|
type OmpSessionStats,
|
|
43
44
|
OmpSessionStatsSchema,
|
|
45
|
+
OmpSteerSubagentResultSchema,
|
|
44
46
|
type OmpSubagentMessagesResult,
|
|
45
47
|
OmpSubagentMessagesResultSchema,
|
|
46
48
|
type OmpSubagentSnapshot,
|
|
@@ -105,6 +107,9 @@ export interface OmpRuntimeSession {
|
|
|
105
107
|
subagentId?: string;
|
|
106
108
|
sessionFile?: string;
|
|
107
109
|
}): Promise<OmpSubagentMessagesResult>;
|
|
110
|
+
/** Resolves `false` when OMP no longer lists the subagent as running for this session. */
|
|
111
|
+
cancelSubagent(subagentId: string): Promise<boolean>;
|
|
112
|
+
steerSubagent(subagentId: string, message: string): Promise<void>;
|
|
108
113
|
prompt(
|
|
109
114
|
message: string,
|
|
110
115
|
images?: readonly OmpImage[],
|
|
@@ -466,6 +471,26 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
466
471
|
);
|
|
467
472
|
}
|
|
468
473
|
|
|
474
|
+
async cancelSubagent(subagentId: string): Promise<boolean> {
|
|
475
|
+
const safeId = validateBoundedText(subagentId, "subagent identifier", MAX_ID_LENGTH);
|
|
476
|
+
return OmpCancelSubagentResultSchema.parse(
|
|
477
|
+
await this.process.request({ type: "cancel_subagent", subagentId: safeId }),
|
|
478
|
+
).cancelled;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async steerSubagent(subagentId: string, message: string): Promise<void> {
|
|
482
|
+
const safeId = validateBoundedText(subagentId, "subagent identifier", MAX_ID_LENGTH);
|
|
483
|
+
const safeMessage = validateBoundedText(message, "subagent steering message", MAX_TEXT_LENGTH);
|
|
484
|
+
if (!safeMessage.trim()) throw new Error("Invalid OMP subagent steering message");
|
|
485
|
+
OmpSteerSubagentResultSchema.parse(
|
|
486
|
+
await this.process.request({
|
|
487
|
+
type: "steer_subagent",
|
|
488
|
+
subagentId: safeId,
|
|
489
|
+
message: safeMessage,
|
|
490
|
+
}),
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
469
494
|
async getBranchMessages(): Promise<Array<{ entryId: string; text: string }>> {
|
|
470
495
|
const result = OmpBranchMessagesResultSchema.parse(
|
|
471
496
|
await this.process.request({ type: "get_branch_messages" }),
|
|
@@ -5,6 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import type { OmpBrowserAuthorizationRegistry } from "../mcp-browser";
|
|
6
6
|
import type { OmpOperationalFailureReporter } from "../operational-failure-diagnostics";
|
|
7
7
|
import { probeOmpAvailability } from "../provider-diagnostics";
|
|
8
|
+
import type { OmpSubagentControlRegistry } from "../subagents";
|
|
8
9
|
import { mergeOmpInheritedEnvironmentNames } from "./config-normalization";
|
|
9
10
|
import { createOmpConnection, OmpNativeSessionReservations } from "./connection";
|
|
10
11
|
import type { OmpMcpConnector } from "./host-tools";
|
|
@@ -131,6 +132,7 @@ export interface OmpProviderOptions {
|
|
|
131
132
|
timeoutMs: number | undefined,
|
|
132
133
|
) => Promise<ProviderAvailabilityCompat>;
|
|
133
134
|
resolveHostInheritEnv?: () => Promise<readonly string[]>;
|
|
135
|
+
subagentControlRegistry?: OmpSubagentControlRegistry;
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
function stableJson(value: unknown): string {
|
|
@@ -235,6 +237,7 @@ export function createOmpProvider(options: OmpProviderOptions = {}): ProviderReg
|
|
|
235
237
|
options.reportOperationalFailure,
|
|
236
238
|
options.resolveHostInheritEnv,
|
|
237
239
|
sessionLimit,
|
|
240
|
+
options.subagentControlRegistry,
|
|
238
241
|
);
|
|
239
242
|
},
|
|
240
243
|
};
|
|
@@ -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" &&
|