@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.
@@ -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 browserAgentId = input.config.env.PASEO_AGENT_ID?.trim() || input.sessionId;
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
- browserAgentId,
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()) slot.removeBrowserAuthorization?.();
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: "unsupported",
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: "unsupported",
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 once the session has no live or background work. Nothing consumes it yet.
1094
- if (type === "session_settled") return;
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
- handleAutoEnd(
77
+ handleEnd(
77
78
  turn: ActiveTurn,
78
- event: Extract<OmpRpcEvent, { type: "auto_compaction_end" }>,
79
- ): boolean {
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 false;
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 !== "auto" ||
88
+ operation?.trigger !== trigger ||
87
89
  operation.turnId !== turn.turnId ||
88
90
  operation.generation !== turn.generation
89
91
  ) {
90
- return false;
92
+ return undefined;
91
93
  }
92
- if (event.action !== undefined && operation.action !== event.action) {
93
- this.retire("OMP emitted overlapping compactions");
94
- this.discardedEnds = 1;
95
- return false;
96
- }
97
- if (event.willRetry) {
98
- operation.retrying = true;
99
- return false;
100
- }
101
- if (operation.retrying) {
102
- operation.retrying = false;
103
- return false;
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.errorMessage
108
- ? "failed"
109
- : event.skipped
110
- ? "skipped"
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 true;
120
+ return state;
117
121
  }
118
122
 
119
123
  finish(
120
- state: "completed" | "failed" | "canceled" | "skipped",
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" &&