@omercnet/paseo-omp 0.4.0-next.114.1 → 0.4.0-next.120.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.
@@ -1,9 +1,30 @@
1
1
  import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
- import { isAbsolute, join } from "node:path";
3
+ import { join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { ompDataDir } from "../paths";
6
6
  import { isValidImagePayload } from "./image";
7
+ import {
8
+ buildOmpSpawnRequest,
9
+ type OmpSpawnRequest,
10
+ type OmpStartOptions,
11
+ } from "./omp-rpc-environment";
12
+ import {
13
+ isConfirmedNoProcessSpawnFailure,
14
+ PROCESS_STOP_TIMEOUT_MS,
15
+ type ProcessTreeCleanup,
16
+ stopWindowsTree,
17
+ terminatePosixProcessTree,
18
+ waitMs,
19
+ } from "./omp-rpc-process";
20
+ import {
21
+ MAX_ID_LENGTH,
22
+ MAX_NAME_LENGTH,
23
+ MAX_PATH_LENGTH,
24
+ MAX_TEXT_LENGTH,
25
+ OmpThinkingLevelSchema,
26
+ validateBoundedText,
27
+ } from "./omp-rpc-values";
7
28
  import {
8
29
  boundedJsonBytes,
9
30
  boundedJsonMetrics,
@@ -17,13 +38,10 @@ import {
17
38
  type OmpSessionListOptions,
18
39
  readOmpPersistedSessionTranscript,
19
40
  readOmpPersistedSubagentTranscript,
20
- validateNativeSessionId,
21
41
  } from "./session-descriptors";
22
- import type { OmpOutputRedaction } from "./settings";
23
42
 
24
43
  const READY_TIMEOUT_MS = 20_000;
25
44
  const REQUEST_TIMEOUT_MS = 60_000;
26
- const PROCESS_STOP_TIMEOUT_MS = 750;
27
45
  const CHUNK_STALE_MS = 30_000;
28
46
  const MAX_PHYSICAL_FRAME_BYTES = 1024 * 1024;
29
47
  const MAX_CHUNK_BYTES = 256 * 1024;
@@ -31,13 +49,8 @@ const MAX_ENCODED_CHUNK_BYTES = Math.ceil(MAX_CHUNK_BYTES / 3) * 4;
31
49
  const MAX_REASSEMBLED_FRAME_BYTES = 64 * 1024 * 1024;
32
50
  const MAX_SEMANTIC_FRAME_BYTES = 12 * 1024 * 1024;
33
51
  const MAX_CHUNK_COUNT = MAX_REASSEMBLED_FRAME_BYTES / MAX_CHUNK_BYTES;
34
- const MAX_ID_LENGTH = 256;
35
- const MAX_NAME_LENGTH = 256;
36
- const MAX_MODEL_SELECTOR_BYTES = MAX_NAME_LENGTH * 2 + 1;
37
52
  const MAX_CONFIG_EVENT_TEXT_BYTES = 64 * 1024;
38
- const MAX_TEXT_LENGTH = 1024 * 1024;
39
53
  const MAX_STREAM_TEXT_LENGTH = 4 * 1024 * 1024;
40
- const MAX_SYSTEM_PROMPT_LENGTH = 64 * 1024;
41
54
  const MAX_IMAGE_DATA_LENGTH = 8 * 1024 * 1024;
42
55
  const MAX_TOOL_PAYLOAD_LENGTH = 256 * 1024;
43
56
  const MAX_ACTIVE_TOOLS = 64;
@@ -75,18 +88,111 @@ const MAX_MODEL_CATALOG_ITEMS = 4_096;
75
88
  // primary resource bounds.
76
89
  export const OMP_MAX_CONTENT_PARTS = 4_096;
77
90
  const MAX_TODOS = 256;
78
- const MAX_ENV_ENTRIES = 256;
79
- const MAX_ENV_VALUE_LENGTH = 64 * 1024;
80
- const MAX_ENV_TOTAL_LENGTH = 1024 * 1024;
81
- const MAX_PATH_LENGTH = 4_096;
82
- const WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
83
91
  const MAX_TOKEN_COUNT = Number.MAX_SAFE_INTEGER;
84
92
  const MAX_COST_USD = 1_000_000_000;
85
93
  const MAX_RPC_ERROR_BYTES = 4_096;
86
94
  const MAX_RPC_ERROR_CODE_BYTES = 256;
87
95
  const PROMPT_SCHEDULING_FAILURE = "OMP prompt scheduling failed";
88
96
  const PROTOCOL_VIOLATION_COALESCE_MS = 10_000;
89
-
97
+ const MAX_REPLAY_MESSAGES = 100_000;
98
+ const MAX_REPLAY_BYTES = 64 * 1024 * 1024;
99
+ const MAX_REPLAY_NODES = 400_000;
100
+ const OMP_MESSAGE_PAGE_LIMIT = 256;
101
+ const MAX_MESSAGE_PAGES = 512;
102
+ const MAX_MESSAGE_PAGE_BUSY_RETRIES = 4;
103
+ const MAX_MESSAGE_PAGE_STALE_RESTARTS = 2;
104
+ const MESSAGE_PAGE_RETRY_BASE_MS = 50;
105
+
106
+ export const OMP_MESSAGE_REPLAY_POLICIES = {
107
+ assistant: "render",
108
+ user: "render",
109
+ developer: "ignore",
110
+ toolResult: "render",
111
+ bashExecution: "normalize",
112
+ pythonExecution: "normalize",
113
+ custom: "normalize",
114
+ hookMessage: "normalize",
115
+ branchSummary: "ignore",
116
+ compactionSummary: "ignore",
117
+ fileMention: "ignore",
118
+ } as const;
119
+
120
+ export const OMP_SESSION_EVENT_POLICIES = {
121
+ agent_start: "lifecycle",
122
+ agent_end: "lifecycle",
123
+ turn_start: "lifecycle",
124
+ turn_end: "lifecycle",
125
+ message_start: "normalize",
126
+ message_update: "normalize",
127
+ message_end: "normalize",
128
+ tool_execution_start: "normalize",
129
+ tool_execution_update: "normalize",
130
+ tool_stream_update: "normalize",
131
+ tool_execution_end: "normalize",
132
+ auto_compaction_start: "normalize",
133
+ auto_compaction_end: "normalize",
134
+ auto_retry_start: "normalize",
135
+ auto_retry_end: "normalize",
136
+ retry_fallback_applied: "normalize",
137
+ retry_fallback_succeeded: "normalize",
138
+ model_changed: "normalize",
139
+ config_warnings_changed: "ignore",
140
+ advisor_cost_changed: "ignore",
141
+ advisor_yielded: "normalize",
142
+ ttsr_triggered: "ignore",
143
+ todo_reminder: "normalize",
144
+ todo_auto_clear: "normalize",
145
+ irc_message: "normalize",
146
+ notice: "normalize",
147
+ thinking_level_changed: "normalize",
148
+ goal_updated: "normalize",
149
+ } as const;
150
+
151
+ export const OMP_RPC_COMMAND_POLICIES = {
152
+ negotiate_protocol: "implemented",
153
+ prompt: "implemented",
154
+ steer: "implemented",
155
+ follow_up: "implemented",
156
+ abort: "implemented",
157
+ abort_and_prompt: "unsupported",
158
+ new_session: "unsupported",
159
+ get_state: "implemented",
160
+ set_fast_mode: "unsupported",
161
+ get_available_commands: "implemented",
162
+ set_todos: "unsupported",
163
+ set_host_tools: "implemented",
164
+ set_host_uri_schemes: "unsupported",
165
+ set_subagent_subscription: "implemented",
166
+ get_subagents: "implemented",
167
+ get_subagent_messages: "implemented",
168
+ set_model: "implemented",
169
+ cycle_model: "unsupported",
170
+ get_available_models: "implemented",
171
+ set_thinking_level: "implemented",
172
+ cycle_thinking_level: "unsupported",
173
+ set_steering_mode: "unsupported",
174
+ set_follow_up_mode: "unsupported",
175
+ set_interrupt_mode: "unsupported",
176
+ compact: "implemented",
177
+ set_auto_compaction: "implemented",
178
+ set_auto_retry: "unsupported",
179
+ abort_retry: "unsupported",
180
+ bash: "unsupported",
181
+ abort_bash: "unsupported",
182
+ get_session_stats: "implemented",
183
+ export_html: "unsupported",
184
+ switch_session: "unsupported",
185
+ branch: "implemented",
186
+ get_branch_messages: "implemented",
187
+ get_last_assistant_text: "unsupported",
188
+ set_session_name: "unsupported",
189
+ handoff: "implemented",
190
+ get_messages: "implemented",
191
+ get_messages_page: "implemented",
192
+ get_login_providers: "unsupported",
193
+ login: "unsupported",
194
+ } as const;
195
+ export const OMP_RPC_RESPONSE_POLICIES = OMP_RPC_COMMAND_POLICIES;
90
196
  export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
91
197
  "duplicate-ready",
92
198
  "frame-limit",
@@ -156,6 +262,7 @@ export const OMP_PROTOCOL_EVENT_TYPES = [
156
262
  "tool_execution_start",
157
263
  "tool_execution_update",
158
264
  "tool_execution_end",
265
+ "tool_stream_update",
159
266
  "compaction_start",
160
267
  "compaction_end",
161
268
  "subagent_lifecycle",
@@ -163,6 +270,10 @@ export const OMP_PROTOCOL_EVENT_TYPES = [
163
270
  "subagent_event",
164
271
  "todo_reminder",
165
272
  "model_changed",
273
+ "config_warnings_changed",
274
+ "advisor_cost_changed",
275
+ "ttsr_triggered",
276
+ "irc_message",
166
277
  "thinking_level_changed",
167
278
  "goal_updated",
168
279
  "auto_retry_start",
@@ -292,8 +403,11 @@ function eventSchemaClassification(
292
403
  case "message_update":
293
404
  case "message_end":
294
405
  return { reason: "message-event-schema", expected: "valid-message-event" };
406
+ case "irc_message":
407
+ return { reason: "message-event-schema", expected: "valid-message-event" };
295
408
  case "tool_execution_start":
296
409
  case "tool_execution_update":
410
+ case "tool_stream_update":
297
411
  case "tool_execution_end":
298
412
  case "host_tool_call":
299
413
  case "host_tool_cancel":
@@ -312,6 +426,9 @@ function eventSchemaClassification(
312
426
  case "available_commands_update":
313
427
  case "retry_fallback_applied":
314
428
  case "retry_fallback_succeeded":
429
+ case "config_warnings_changed":
430
+ case "advisor_cost_changed":
431
+ case "ttsr_triggered":
315
432
  return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
316
433
  case "extension_ui_request":
317
434
  return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
@@ -408,6 +525,13 @@ function boundedString(maxBytes: number, minBytes = 0) {
408
525
  return bytes >= minBytes && bytes <= maxBytes;
409
526
  });
410
527
  }
528
+ function isSafeOpaqueCursor(value: string): boolean {
529
+ for (let index = 0; index < value.length; index += 1) {
530
+ const code = value.charCodeAt(index);
531
+ if (code <= 0x1f || code === 0x7f) return false;
532
+ }
533
+ return true;
534
+ }
411
535
  function boundedOptionalStringArray(maxItems: number, maxBytes: number) {
412
536
  return z.unknown().transform((value): string[] | undefined => {
413
537
  if (!Array.isArray(value)) return undefined;
@@ -465,7 +589,7 @@ function sanitizeLiveMessageDisplay(value: unknown): unknown {
465
589
  return content === message.content ? value : { ...message, content };
466
590
  }
467
591
  if (
468
- message.role === "bashExecution" &&
592
+ (message.role === "bashExecution" || message.role === "pythonExecution") &&
469
593
  typeof message.output === "string" &&
470
594
  utf8Bytes(message.output) > MAX_IMAGE_DATA_LENGTH
471
595
  ) {
@@ -494,7 +618,6 @@ function sanitizeLiveDisplayFrame(value: unknown): unknown {
494
618
  });
495
619
  return changed ? { ...frame, messages } : value;
496
620
  }
497
- const OmpThinkingLevelSchema = z.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
498
621
  const OmpCurrentThinkingLevelSchema = boundedString(32).optional().catch(undefined);
499
622
 
500
623
  function isBoundedJson(
@@ -507,6 +630,178 @@ function isBoundedJson(
507
630
  boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
508
631
  );
509
632
  }
633
+ const MAX_RESPONSE_DEPTH = 16;
634
+ export const OMP_RPC_BOUND_DIMENSIONS = [
635
+ "bytes",
636
+ "items",
637
+ "nodes",
638
+ "depth",
639
+ "string-bytes",
640
+ ] as const;
641
+ export type OmpRpcBoundDimension = (typeof OMP_RPC_BOUND_DIMENSIONS)[number];
642
+ export const OMP_RPC_DIAGNOSTIC_COMMANDS = [
643
+ "negotiate_protocol",
644
+ "get_state",
645
+ "get_session_stats",
646
+ "get_available_models",
647
+ "get_available_commands",
648
+ "set_subagent_subscription",
649
+ "get_subagents",
650
+ "get_subagent_messages",
651
+ "prompt",
652
+ "compact",
653
+ "set_auto_compaction",
654
+ "set_model",
655
+ "set_thinking_level",
656
+ "handoff",
657
+ "get_branch_messages",
658
+ "branch",
659
+ "get_messages_page",
660
+ "get_messages",
661
+ "abort",
662
+ "set_host_tools",
663
+ ] as const;
664
+ export type OmpRpcDiagnosticCommand = (typeof OMP_RPC_DIAGNOSTIC_COMMANDS)[number];
665
+ type JsonBoundViolation = {
666
+ dimension: OmpRpcBoundDimension;
667
+ actual: number;
668
+ limit: number;
669
+ };
670
+ type JsonBoundInspection = { bytes: number; nodes: number; violation?: JsonBoundViolation };
671
+
672
+ function rpcDiagnosticCommand(value: string): OmpRpcDiagnosticCommand | undefined {
673
+ return (OMP_RPC_DIAGNOSTIC_COMMANDS as readonly string[]).includes(value)
674
+ ? (value as OmpRpcDiagnosticCommand)
675
+ : undefined;
676
+ }
677
+
678
+ function encodedJsonStringBytes(value: string): number {
679
+ let bytes = 2;
680
+ for (let index = 0; index < value.length; index += 1) {
681
+ const code = value.charCodeAt(index);
682
+ if (code === 0x22 || code === 0x5c) {
683
+ bytes += 2;
684
+ } else if (code <= 0x1f) {
685
+ bytes +=
686
+ code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d ? 2 : 6;
687
+ } else if (code >= 0xd800 && code <= 0xdbff) {
688
+ const low = value.charCodeAt(index + 1);
689
+ if (low >= 0xdc00 && low <= 0xdfff) {
690
+ bytes += 4;
691
+ index += 1;
692
+ } else {
693
+ bytes += 6;
694
+ }
695
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
696
+ bytes += 6;
697
+ } else {
698
+ bytes += code <= 0x7f ? 1 : code <= 0x7ff ? 2 : 3;
699
+ }
700
+ }
701
+ return bytes;
702
+ }
703
+
704
+ function inspectJsonBounds(
705
+ value: unknown,
706
+ maxBytes: number,
707
+ maxItems: number,
708
+ maxStringBytes: number,
709
+ maxNodes: number,
710
+ ): JsonBoundInspection {
711
+ const stack: Array<{ value: unknown; depth: number }> = [{ value, depth: 0 }];
712
+ let nodes = 0;
713
+ let bytes = 0;
714
+ const addBytes = (count: number): JsonBoundViolation | undefined => {
715
+ bytes += count;
716
+ return bytes > maxBytes ? { dimension: "bytes", actual: bytes, limit: maxBytes } : undefined;
717
+ };
718
+ while (stack.length > 0) {
719
+ const current = stack.pop();
720
+ if (!current) break;
721
+ nodes += 1;
722
+ if (nodes > maxNodes)
723
+ return { bytes, nodes, violation: { dimension: "nodes", actual: nodes, limit: maxNodes } };
724
+ if (current.depth > MAX_RESPONSE_DEPTH) {
725
+ return {
726
+ bytes,
727
+ nodes,
728
+ violation: { dimension: "depth", actual: current.depth, limit: MAX_RESPONSE_DEPTH },
729
+ };
730
+ }
731
+ const item = current.value;
732
+ if (item === null) {
733
+ const violation = addBytes(4);
734
+ if (violation) return { bytes, nodes, violation };
735
+ continue;
736
+ }
737
+ if (typeof item === "boolean") {
738
+ const violation = addBytes(item ? 4 : 5);
739
+ if (violation) return { bytes, nodes, violation };
740
+ continue;
741
+ }
742
+ if (typeof item === "number") {
743
+ const violation = addBytes((JSON.stringify(item) ?? "null").length);
744
+ if (violation) return { bytes, nodes, violation };
745
+ continue;
746
+ }
747
+ if (typeof item === "string") {
748
+ const itemBytes = utf8Bytes(item);
749
+ if (itemBytes > maxStringBytes) {
750
+ return {
751
+ bytes,
752
+ nodes,
753
+ violation: { dimension: "string-bytes", actual: itemBytes, limit: maxStringBytes },
754
+ };
755
+ }
756
+ const violation = addBytes(encodedJsonStringBytes(item));
757
+ if (violation) return { bytes, nodes, violation };
758
+ continue;
759
+ }
760
+ if (typeof item !== "object") continue;
761
+ if (Array.isArray(item)) {
762
+ if (item.length > maxItems) {
763
+ return {
764
+ bytes,
765
+ nodes,
766
+ violation: { dimension: "items", actual: item.length, limit: maxItems },
767
+ };
768
+ }
769
+ const violation = addBytes(2 + Math.max(0, item.length - 1));
770
+ if (violation) return { bytes, nodes, violation };
771
+ for (let index = item.length - 1; index >= 0; index -= 1) {
772
+ stack.push({ value: item[index], depth: current.depth + 1 });
773
+ }
774
+ continue;
775
+ }
776
+ const entries = Object.entries(item).filter(([, child]) => child !== undefined);
777
+ if (entries.length > maxItems) {
778
+ return {
779
+ bytes,
780
+ nodes,
781
+ violation: { dimension: "items", actual: entries.length, limit: maxItems },
782
+ };
783
+ }
784
+ let violation = addBytes(2 + Math.max(0, entries.length - 1) + entries.length);
785
+ if (violation) return { bytes, nodes, violation };
786
+ for (let index = entries.length - 1; index >= 0; index -= 1) {
787
+ const [key, child] = entries[index] as [string, unknown];
788
+ violation = addBytes(encodedJsonStringBytes(key));
789
+ if (violation) return { bytes, nodes, violation };
790
+ stack.push({ value: child, depth: current.depth + 1 });
791
+ }
792
+ }
793
+ return { bytes, nodes };
794
+ }
795
+
796
+ function jsonBoundViolation(
797
+ value: unknown,
798
+ maxBytes: number,
799
+ maxItems: number,
800
+ maxStringBytes: number,
801
+ maxNodes: number,
802
+ ): JsonBoundViolation | undefined {
803
+ return inspectJsonBounds(value, maxBytes, maxItems, maxStringBytes, maxNodes).violation;
804
+ }
510
805
  function optionalMetadataMetrics(value: unknown) {
511
806
  return boundedJsonMetrics(
512
807
  value,
@@ -706,52 +1001,46 @@ const OmpMessageIdentityShape = {
706
1001
  timestamp: z.number().finite().optional(),
707
1002
  details: OmpOptionalMetadataSchema,
708
1003
  };
709
- type OmpContentPart = z.infer<typeof OmpContentPartSchema>;
710
- type OmpMessageIdentity = {
711
- id?: string;
712
- entryId?: string;
713
- responseId?: string;
714
- images?: OmpContentPart[];
715
- timestamp?: number;
716
- details?: unknown;
717
- display?: boolean;
718
- customType?: string;
719
- content?: unknown;
720
- command?: string;
721
- output?: string;
722
- exitCode?: number | null;
723
- cancelled?: boolean;
724
- truncated?: boolean;
1004
+ const OmpOpaqueMessagePayloadSchema = z
1005
+ .unknown()
1006
+ .refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192));
1007
+ const OmpExecutionOutputSchema = z.preprocess(
1008
+ (value) =>
1009
+ typeof value === "string" && utf8Bytes(value) > MAX_IMAGE_DATA_LENGTH
1010
+ ? DISPLAY_TRUNCATION_MARKER
1011
+ : value,
1012
+ RAW_DISPLAY_TEXT.optional(),
1013
+ );
1014
+ const OmpExecutionMessageShape = {
1015
+ output: OmpExecutionOutputSchema,
1016
+ exitCode: z.number().int().nullable().optional(),
1017
+ cancelled: z.boolean().optional(),
1018
+ truncated: z.boolean().optional(),
1019
+ excludeFromContext: z.boolean().optional(),
1020
+ meta: OmpOptionalMetadataSchema,
725
1021
  };
726
- export type OmpMessage = OmpMessageIdentity &
727
- (
728
- | {
729
- role: "assistant";
730
- content?: string | OmpContentPart[];
731
- errorMessage?: string | null;
732
- stopReason?: string;
733
- }
734
- | { role: "user"; content: string | OmpContentPart[] }
735
- | {
736
- role: "toolResult";
737
- toolCallId: string;
738
- toolName: string;
739
- content: unknown;
740
- details?: unknown;
741
- isError?: boolean;
742
- }
743
- | {
744
- role: "bashExecution";
745
- command: string;
746
- output?: string;
747
- exitCode?: number | null;
748
- cancelled?: boolean;
749
- truncated?: boolean;
750
- }
751
- | { role: "custom"; customType?: string; content?: unknown; display?: boolean }
752
- );
1022
+ const OmpCustomMessageShape = {
1023
+ customType: NAME.optional(),
1024
+ content: OmpDisplayContentSchema.optional(),
1025
+ display: z.boolean().optional(),
1026
+ attribution: OmpOpaqueMessagePayloadSchema.optional(),
1027
+ };
1028
+ const OmpFileMentionSchema = z
1029
+ .object({
1030
+ path: boundedString(MAX_PATH_LENGTH),
1031
+ content: TEXT,
1032
+ lineCount: z.number().int().nonnegative().safe().optional(),
1033
+ byteSize: z.number().int().nonnegative().safe().optional(),
1034
+ skippedReason: z.enum(["tooLarge", "binary"]).optional(),
1035
+ image: OmpContentPartSchema.optional(),
1036
+ })
1037
+ .superRefine((file, context) => {
1038
+ if (file.image && file.image.type !== "image") {
1039
+ context.addIssue({ code: "custom", message: "invalid file mention image" });
1040
+ }
1041
+ });
753
1042
 
754
- const OmpMessageSchema: z.ZodType<OmpMessage> = z.union([
1043
+ const OmpMessageSchema = z.discriminatedUnion("role", [
755
1044
  z.object({
756
1045
  role: z.literal("assistant"),
757
1046
  content: OmpAssistantDisplayContentSchema.optional(),
@@ -764,42 +1053,70 @@ const OmpMessageSchema: z.ZodType<OmpMessage> = z.union([
764
1053
  content: OmpDisplayContentSchema,
765
1054
  ...OmpMessageIdentityShape,
766
1055
  }),
1056
+ z.object({
1057
+ role: z.literal("developer"),
1058
+ content: OmpDisplayContentSchema,
1059
+ attribution: OmpOpaqueMessagePayloadSchema.optional(),
1060
+ synthetic: z.boolean().optional(),
1061
+ userInitiated: z.boolean().optional(),
1062
+ providerPayload: OmpOpaqueMessagePayloadSchema.optional(),
1063
+ ...OmpMessageIdentityShape,
1064
+ }),
767
1065
  z.object({
768
1066
  role: z.literal("toolResult"),
769
1067
  toolCallId: IDENTIFIER,
770
1068
  toolName: NAME,
771
- content: z
772
- .unknown()
773
- .refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192)),
1069
+ content: OmpOpaqueMessagePayloadSchema,
774
1070
  isError: z.boolean().optional(),
1071
+ attribution: OmpOpaqueMessagePayloadSchema.optional(),
1072
+ prunedAt: z.number().finite().nonnegative().optional(),
1073
+ providerMetadata: OmpOpaqueMessagePayloadSchema.optional(),
1074
+ useless: z.boolean().optional(),
775
1075
  ...OmpMessageIdentityShape,
776
1076
  }),
777
1077
  z.object({
778
1078
  role: z.literal("bashExecution"),
779
1079
  command: TEXT,
780
- output: z.preprocess(
781
- (value) =>
782
- typeof value === "string" && utf8Bytes(value) > MAX_IMAGE_DATA_LENGTH
783
- ? DISPLAY_TRUNCATION_MARKER
784
- : value,
785
- RAW_DISPLAY_TEXT.optional(),
786
- ),
787
- exitCode: z.number().int().nullable().optional(),
788
- cancelled: z.boolean().optional(),
789
- truncated: z.boolean().optional(),
1080
+ ...OmpExecutionMessageShape,
790
1081
  ...OmpMessageIdentityShape,
791
1082
  }),
792
1083
  z.object({
793
- role: z.literal("custom"),
794
- customType: NAME.optional(),
795
- content: z
796
- .unknown()
797
- .refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 8_192))
798
- .optional(),
799
- display: z.boolean().optional(),
1084
+ role: z.literal("pythonExecution"),
1085
+ code: TEXT,
1086
+ ...OmpExecutionMessageShape,
1087
+ ...OmpMessageIdentityShape,
1088
+ }),
1089
+ z.object({ role: z.literal("custom"), ...OmpCustomMessageShape, ...OmpMessageIdentityShape }),
1090
+ z.object({
1091
+ role: z.literal("hookMessage"),
1092
+ ...OmpCustomMessageShape,
1093
+ ...OmpMessageIdentityShape,
1094
+ }),
1095
+ z.object({
1096
+ role: z.literal("branchSummary"),
1097
+ summary: TEXT,
1098
+ fromId: IDENTIFIER,
1099
+ ...OmpMessageIdentityShape,
1100
+ }),
1101
+ z.object({
1102
+ role: z.literal("compactionSummary"),
1103
+ summary: TEXT,
1104
+ shortSummary: TEXT.optional(),
1105
+ tokensBefore: z.number().finite().nonnegative(),
1106
+ tokensAfter: z.number().finite().nonnegative().optional(),
1107
+ method: NAME.optional(),
1108
+ providerPayload: OmpOpaqueMessagePayloadSchema.optional(),
1109
+ blocks: z.array(OmpContentPartSchema).max(OMP_MAX_CONTENT_PARTS).optional(),
1110
+ warning: boundedString(64 * 1024).optional(),
1111
+ ...OmpMessageIdentityShape,
1112
+ }),
1113
+ z.object({
1114
+ role: z.literal("fileMention"),
1115
+ files: z.array(OmpFileMentionSchema).max(MAX_ARRAY_ITEMS),
800
1116
  ...OmpMessageIdentityShape,
801
1117
  }),
802
1118
  ]);
1119
+ export type OmpMessage = z.infer<typeof OmpMessageSchema>;
803
1120
 
804
1121
  const OmpAssistantMessageEventSchema = z
805
1122
  .object({
@@ -948,6 +1265,7 @@ const OmpReadyFrameSchema = z.object({
948
1265
  const OmpResponseFrameSchema = z.object({
949
1266
  type: z.literal("response"),
950
1267
  id: IDENTIFIER,
1268
+ command: boundedString(64, 1).optional(),
951
1269
  success: z.boolean(),
952
1270
  data: z.unknown().optional(),
953
1271
  error: boundedString(MAX_RPC_ERROR_BYTES).optional(),
@@ -1153,7 +1471,7 @@ const OmpCompactionEndSchema = z.object({
1153
1471
  errorMessage: boundedString(4_096).optional(),
1154
1472
  skipped: z.boolean().optional(),
1155
1473
  });
1156
- const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
1474
+ const OmpCoreAgentEventSchema = z.discriminatedUnion("type", [
1157
1475
  z.object({ type: z.literal("agent_start") }),
1158
1476
  z.object({
1159
1477
  type: z.literal("agent_end"),
@@ -1184,6 +1502,12 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
1184
1502
  args: BoundedToolPayloadSchema.optional(),
1185
1503
  partialResult: OmpToolResultPayloadSchema,
1186
1504
  }),
1505
+ z.object({
1506
+ type: z.literal("tool_stream_update"),
1507
+ toolCallId: IDENTIFIER,
1508
+ toolName: NAME,
1509
+ update: BoundedToolPayloadSchema,
1510
+ }),
1187
1511
  z.object({
1188
1512
  type: z.literal("tool_execution_end"),
1189
1513
  toolCallId: IDENTIFIER,
@@ -1210,6 +1534,99 @@ const OmpGoalModeStateSchema = z.object({
1210
1534
  reason: boundedString(4_096).optional(),
1211
1535
  goal: OmpGoalSchema.optional(),
1212
1536
  });
1537
+ const OmpTodoReminderEventSchema = z.object({
1538
+ type: z.literal("todo_reminder"),
1539
+ todos: z
1540
+ .array(
1541
+ z.object({
1542
+ id: IDENTIFIER.optional(),
1543
+ content: boundedString(16_384),
1544
+ status: z.enum(["pending", "in_progress", "blocked", "completed", "abandoned"]),
1545
+ }),
1546
+ )
1547
+ .max(MAX_TODOS),
1548
+ attempt: z.number().int().nonnegative().safe().optional(),
1549
+ maxAttempts: z.number().int().nonnegative().safe().optional(),
1550
+ });
1551
+ const OmpThinkingLevelChangedEventSchema = z.object({
1552
+ type: z.literal("thinking_level_changed"),
1553
+ thinkingLevel: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
1554
+ configured: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
1555
+ resolved: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
1556
+ });
1557
+ const OmpAutoRetryStartEventSchema = z.object({
1558
+ type: z.literal("auto_retry_start"),
1559
+ attempt: z.number().int().nonnegative().safe(),
1560
+ maxAttempts: z.number().int().positive().safe(),
1561
+ delayMs: z.number().int().nonnegative().safe(),
1562
+ errorMessage: boundedString(64 * 1024),
1563
+ errorId: z.number().int().safe().optional(),
1564
+ });
1565
+ const OmpAutoRetryEndEventSchema = z.object({
1566
+ type: z.literal("auto_retry_end"),
1567
+ success: z.boolean(),
1568
+ attempt: z.number().int().nonnegative().safe(),
1569
+ finalError: boundedString(64 * 1024).optional(),
1570
+ retryErrors: BoundedToolPayloadSchema.optional(),
1571
+ recoveredErrors: BoundedToolPayloadSchema.optional(),
1572
+ });
1573
+ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
1574
+ ...OmpCoreAgentEventSchema.options,
1575
+ z.object({ type: z.literal("config_warnings_changed") }),
1576
+ z.object({ type: z.literal("advisor_cost_changed") }),
1577
+ z.object({ type: z.literal("advisor_yielded") }),
1578
+ z.object({
1579
+ type: z.literal("ttsr_triggered"),
1580
+ rules: z.array(BoundedToolPayloadSchema).max(MAX_ARRAY_ITEMS),
1581
+ }),
1582
+ OmpTodoReminderEventSchema,
1583
+ z.object({ type: z.literal("todo_auto_clear") }),
1584
+ z.object({
1585
+ type: z.literal("irc_message"),
1586
+ message: OmpMessageSchema.refine((message) => message.role === "custom"),
1587
+ }),
1588
+ z.object({ type: z.literal("model_changed") }),
1589
+ OmpThinkingLevelChangedEventSchema,
1590
+ z.object({
1591
+ type: z.literal("goal_updated"),
1592
+ goal: OmpGoalSchema.nullable().optional(),
1593
+ state: OmpGoalModeStateSchema.optional(),
1594
+ }),
1595
+ OmpAutoRetryStartEventSchema,
1596
+ OmpAutoRetryEndEventSchema,
1597
+ z.object({
1598
+ type: z.literal("retry_fallback_applied"),
1599
+ from: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1600
+ to: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1601
+ role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1602
+ }),
1603
+ z.object({
1604
+ type: z.literal("retry_fallback_succeeded"),
1605
+ model: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1606
+ role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1607
+ }),
1608
+ z.object({
1609
+ type: z.literal("auto_compaction_start"),
1610
+ reason: boundedString(4_096),
1611
+ action: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1612
+ }),
1613
+ z.object({
1614
+ type: z.literal("auto_compaction_end"),
1615
+ action: NAME.optional(),
1616
+ result: OmpCompactionResultSchema.nullable().optional(),
1617
+ aborted: z.boolean().optional(),
1618
+ willRetry: z.boolean().optional(),
1619
+ errorMessage: boundedString(64 * 1024).optional(),
1620
+ skipped: z.boolean().optional(),
1621
+ }),
1622
+ z.object({
1623
+ type: z.literal("notice"),
1624
+ id: IDENTIFIER.optional(),
1625
+ level: z.enum(["info", "warning", "error"]),
1626
+ message: boundedString(64 * 1024),
1627
+ source: boundedString(MAX_NAME_LENGTH).optional(),
1628
+ }),
1629
+ ]);
1213
1630
  const OmpSubagentStatusSchema = z.enum([
1214
1631
  "pending",
1215
1632
  "running",
@@ -1352,80 +1769,10 @@ const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
1352
1769
  type: z.literal("subagent_event"),
1353
1770
  payload: z.object({ id: IDENTIFIER, event: OmpAgentSessionEventSchema }),
1354
1771
  }),
1355
- z.object({
1356
- type: z.literal("todo_reminder"),
1357
- todos: z
1358
- .array(
1359
- z.object({
1360
- id: IDENTIFIER.optional(),
1361
- content: boundedString(16_384),
1362
- status: z.enum(["pending", "in_progress", "blocked", "completed", "abandoned"]),
1363
- }),
1364
- )
1365
- .max(MAX_TODOS),
1366
- }),
1367
- z.object({ type: z.literal("model_changed") }),
1368
- z.object({
1369
- type: z.literal("thinking_level_changed"),
1370
- thinkingLevel: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES).optional(),
1371
- }),
1372
- z.object({
1373
- type: z.literal("goal_updated"),
1374
- goal: OmpGoalSchema.nullable().optional(),
1375
- state: OmpGoalModeStateSchema.optional(),
1376
- }),
1377
- z.object({
1378
- type: z.literal("auto_retry_start"),
1379
- attempt: z.number().int().nonnegative().safe(),
1380
- maxAttempts: z.number().int().positive().safe(),
1381
- delayMs: z.number().int().nonnegative().safe(),
1382
- errorMessage: boundedString(64 * 1024),
1383
- errorId: z.number().int().safe().optional(),
1384
- }),
1385
- z.object({
1386
- type: z.literal("auto_retry_end"),
1387
- success: z.boolean(),
1388
- attempt: z.number().int().nonnegative().safe(),
1389
- finalError: boundedString(64 * 1024).optional(),
1390
- recoveredErrors: BoundedToolPayloadSchema.optional(),
1391
- }),
1392
- z.object({
1393
- type: z.literal("retry_fallback_applied"),
1394
- from: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1395
- to: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1396
- role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1397
- }),
1398
- z.object({
1399
- type: z.literal("retry_fallback_succeeded"),
1400
- model: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1401
- role: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1402
- }),
1403
- z.object({ type: z.literal("todo_auto_clear") }),
1404
- z.object({
1405
- type: z.literal("auto_compaction_start"),
1406
- reason: boundedString(4_096),
1407
- action: boundedString(MAX_CONFIG_EVENT_TEXT_BYTES),
1408
- }),
1409
- z.object({
1410
- type: z.literal("auto_compaction_end"),
1411
- action: NAME.optional(),
1412
- result: OmpCompactionResultSchema.nullable().optional(),
1413
- aborted: z.boolean().optional(),
1414
- willRetry: z.boolean().optional(),
1415
- errorMessage: boundedString(64 * 1024).optional(),
1416
- skipped: z.boolean().optional(),
1417
- }),
1418
1772
  z.object({
1419
1773
  type: z.literal("available_commands_update"),
1420
1774
  commands: z.array(OmpAvailableCommandSchema).max(MAX_ARRAY_ITEMS),
1421
1775
  }),
1422
- z.object({
1423
- type: z.literal("notice"),
1424
- id: IDENTIFIER.optional(),
1425
- level: z.enum(["info", "warning", "error"]),
1426
- message: boundedString(64 * 1024),
1427
- source: boundedString(MAX_NAME_LENGTH).optional(),
1428
- }),
1429
1776
  z.object({ type: z.literal("command_output"), text: TEXT.optional() }),
1430
1777
  OmpExtensionUiRequestSchema,
1431
1778
  z.object({
@@ -1437,7 +1784,6 @@ const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
1437
1784
  OmpHostToolCancelSchema,
1438
1785
  OmpToolApprovalRequestSchema,
1439
1786
  OmpToolApprovalCancelSchema,
1440
- z.object({ type: z.literal("advisor_yielded") }),
1441
1787
  ]);
1442
1788
  type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
1443
1789
 
@@ -1492,6 +1838,8 @@ function mapAgentEventDetails(
1492
1838
  return mapRecordField(frame, "result", map, frame.toolName === "task");
1493
1839
  case "agent_end":
1494
1840
  return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
1841
+ case "irc_message":
1842
+ return mapRecordField(frame, "message", map);
1495
1843
  default:
1496
1844
  return frame;
1497
1845
  }
@@ -1522,6 +1870,25 @@ function sanitizeHistoryResponseData(value: unknown): unknown {
1522
1870
  return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
1523
1871
  }
1524
1872
 
1873
+ function projectSessionStateResponseData(value: unknown): unknown {
1874
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
1875
+ const source = value as Record<string, unknown>;
1876
+ const projected: Record<string, unknown> = {};
1877
+ for (const key of [
1878
+ "model",
1879
+ "thinkingLevel",
1880
+ "isStreaming",
1881
+ "isCompacting",
1882
+ "sessionId",
1883
+ "autoCompactionEnabled",
1884
+ "contextUsage",
1885
+ "sessionFile",
1886
+ ] as const) {
1887
+ if (Object.hasOwn(source, key)) projected[key] = source[key];
1888
+ }
1889
+ return projected;
1890
+ }
1891
+
1525
1892
  function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
1526
1893
  const type = frame.type;
1527
1894
  if (
@@ -1547,7 +1914,12 @@ const OmpBranchMessagesResultSchema = z.object({
1547
1914
  });
1548
1915
  const OmpBranchResultSchema = z.object({ text: TEXT, cancelled: z.boolean() });
1549
1916
  const OmpMessagesResultSchema = z.object({
1550
- messages: z.array(OmpMessageSchema).max(100_000),
1917
+ messages: z.array(OmpMessageSchema).max(MAX_REPLAY_MESSAGES),
1918
+ });
1919
+ const OmpMessagesPageResultSchema = z.object({
1920
+ messages: z.array(OmpMessageSchema).max(OMP_MESSAGE_PAGE_LIMIT),
1921
+ nextCursor: boundedString(2_048, 1).refine(isSafeOpaqueCursor).optional(),
1922
+ totalMessages: z.number().int().nonnegative().max(MAX_REPLAY_MESSAGES),
1551
1923
  });
1552
1924
  const OmpSubagentsResultSchema = z.object({
1553
1925
  subagents: z
@@ -1573,7 +1945,7 @@ const OmpSubagentMessagesResultSchema = z.object({
1573
1945
  fromByte: z.number().int().nonnegative(),
1574
1946
  nextByte: z.number().int().nonnegative(),
1575
1947
  reset: z.boolean(),
1576
- messages: z.array(OmpMessageSchema).max(100_000),
1948
+ messages: z.array(OmpMessageSchema).max(MAX_REPLAY_MESSAGES),
1577
1949
  });
1578
1950
  const ProtocolNegotiationResultSchema = z.object({
1579
1951
  protocolVersion: z.literal(2),
@@ -1626,29 +1998,6 @@ export interface OmpPersistedSessionMessages {
1626
1998
  imageReplayWarning?: true;
1627
1999
  }
1628
2000
 
1629
- export interface OmpStartOptions {
1630
- cwd: string;
1631
- env?: Readonly<Record<string, string>>;
1632
- outputRedaction?: OmpOutputRedaction;
1633
- inheritEnv?: readonly string[];
1634
- /** Server-owned environment source; tests provide isolated roots instead of ambient process.env. */
1635
- environment?: NodeJS.ProcessEnv;
1636
- command?: readonly string[];
1637
- model?: string;
1638
- mode?: "full" | "write" | "ask";
1639
- thinkingOption?: string;
1640
- systemPrompt?: string;
1641
- roleModels?: Readonly<{ smol?: string; slow?: string; plan?: string }>;
1642
- tools?: readonly string[];
1643
- sessionDir?: string;
1644
- readyTimeoutMs?: number;
1645
- requestTimeoutMs?: number;
1646
- /** Resume this exact native OMP session; never use this to start a new conversation. */
1647
- resumeSessionId?: string;
1648
- noSession?: boolean;
1649
- signal?: AbortSignal;
1650
- }
1651
-
1652
2001
  export type OmpAvailableCommand = z.infer<typeof OmpAvailableCommandSchema>;
1653
2002
  export type OmpImage = { type: "image"; data: string; mimeType: string };
1654
2003
  export type OmpExtensionUiResponse =
@@ -1691,7 +2040,7 @@ export interface OmpRuntimeSession {
1691
2040
  getBranchMessages(): Promise<Array<{ entryId: string; text: string }>>;
1692
2041
  branch(entryId: string): Promise<OmpBranchResult>;
1693
2042
  readonly canReplayHistory: boolean;
1694
- getMessages(): Promise<OmpMessage[]>;
2043
+ getMessages(signal?: AbortSignal): Promise<OmpMessage[]>;
1695
2044
  abort(): Promise<void>;
1696
2045
  setHostTools(tools: readonly OmpHostToolDefinition[]): Promise<string[]>;
1697
2046
  sendHostToolResult(result: OmpHostToolResult): void;
@@ -1712,20 +2061,12 @@ export interface OmpRuntime {
1712
2061
  readPersistedSubagentTranscript(options: {
1713
2062
  parentSessionFile: string;
1714
2063
  childTranscriptId: string;
2064
+ sessionFile?: string;
1715
2065
  cwd: string;
1716
2066
  signal?: AbortSignal;
1717
2067
  }): Promise<OmpPersistedSubagentMessages>;
1718
2068
  }
1719
2069
 
1720
- export interface OmpSpawnRequest {
1721
- command: string;
1722
- args: string[];
1723
- cwd: string;
1724
- env: NodeJS.ProcessEnv;
1725
- detached: boolean;
1726
- inheritedRedactionValues: string[];
1727
- }
1728
-
1729
2070
  export interface OmpRpcRuntimeOptions {
1730
2071
  spawnProcess?: (request: OmpSpawnRequest) => ChildProcessWithoutNullStreams;
1731
2072
  terminateProcessTree?: (pid: number) => Promise<boolean | "uncertain">;
@@ -1737,13 +2078,30 @@ export interface OmpRpcRuntimeOptions {
1737
2078
  ) => OmpSessionDescriptor[] | Promise<OmpSessionDescriptor[]>;
1738
2079
  }
1739
2080
 
2081
+ export type OmpRpcRequestRejectionCode = "session_busy" | "stale_cursor" | "unsupported_command";
2082
+
1740
2083
  export class OmpRpcRequestRejectedError extends Error {
1741
- constructor() {
2084
+ constructor(
2085
+ readonly command?: OmpRpcDiagnosticCommand,
2086
+ readonly code?: OmpRpcRequestRejectionCode,
2087
+ ) {
1742
2088
  super("OMP RPC request failed");
1743
2089
  this.name = "OmpRpcRequestRejectedError";
1744
2090
  }
1745
2091
  }
1746
2092
 
2093
+ export class OmpRpcResponseLimitError extends Error {
2094
+ constructor(
2095
+ readonly command: OmpRpcDiagnosticCommand | undefined,
2096
+ readonly bound: OmpRpcBoundDimension,
2097
+ readonly actual: number,
2098
+ readonly limit: number,
2099
+ ) {
2100
+ super("OMP RPC response exceeded command limits");
2101
+ this.name = "OmpRpcResponseLimitError";
2102
+ }
2103
+ }
2104
+
1747
2105
  type PendingRequest = {
1748
2106
  resolve(value: unknown): void;
1749
2107
  reject(error: Error): void;
@@ -1765,339 +2123,6 @@ type ChunkState = {
1765
2123
  timer: TimerHandle;
1766
2124
  };
1767
2125
 
1768
- // The daemon contributes only process/runtime discovery variables plus provider authentication
1769
- // families. Session-scoped values are explicit host input and are overlaid after rejecting loader
1770
- // and executable-resolution controls; this keeps provider credentials available without copying
1771
- const INHERITED_RUNTIME_ENV: Readonly<Record<string, true>> = {
1772
- ALL_PROXY: true,
1773
- APPDATA: true,
1774
- COLORTERM: true,
1775
- HOME: true,
1776
- HTTPS_PROXY: true,
1777
- HTTP_PROXY: true,
1778
- LANG: true,
1779
- LC_ALL: true,
1780
- LC_CTYPE: true,
1781
- LOCALAPPDATA: true,
1782
- LOGNAME: true,
1783
- NO_PROXY: true,
1784
- OMP_PROFILE: true,
1785
- PATH: true,
1786
- PATHEXT: true,
1787
- PI_CODING_AGENT_DIR: true,
1788
- PI_CONFIG_DIR: true,
1789
- PI_PROFILE: true,
1790
- SHELL: true,
1791
- SSH_AUTH_SOCK: true,
1792
- SSL_CERT_DIR: true,
1793
- SSL_CERT_FILE: true,
1794
- SYSTEMROOT: true,
1795
- TEMP: true,
1796
- TMP: true,
1797
- TMPDIR: true,
1798
- TZ: true,
1799
- USER: true,
1800
- USERPROFILE: true,
1801
- XDG_CACHE_HOME: true,
1802
- XDG_CONFIG_HOME: true,
1803
- XDG_DATA_HOME: true,
1804
- XDG_STATE_HOME: true,
1805
- XDG_RUNTIME_DIR: true,
1806
- };
1807
- const INHERITED_PROVIDER_AUTH_ENV: Readonly<Record<string, true>> = {
1808
- AI_GATEWAY_API_KEY: true,
1809
- AIAND_API_KEY: true,
1810
- ALIBABA_CODING_PLAN_API_KEY: true,
1811
- ALIBABA_TOKEN_PLAN_API_KEY: true,
1812
- ANTHROPIC_API_KEY: true,
1813
- ANTHROPIC_FOUNDRY_API_KEY: true,
1814
- ANTHROPIC_OAUTH_TOKEN: true,
1815
- AWS_ACCESS_KEY_ID: true,
1816
- AWS_DEFAULT_REGION: true,
1817
- AWS_PROFILE: true,
1818
- AWS_REGION: true,
1819
- AWS_SECRET_ACCESS_KEY: true,
1820
- AWS_SESSION_TOKEN: true,
1821
- AZURE_CLIENT_ID: true,
1822
- AZURE_CLIENT_SECRET: true,
1823
- AZURE_OPENAI_API_KEY: true,
1824
- AZURE_OPENAI_ENDPOINT: true,
1825
- AZURE_TENANT_ID: true,
1826
- BAILIAN_TOKEN_PLAN_API_KEY: true,
1827
- BASETEN_API_KEY: true,
1828
- CEREBRAS_API_KEY: true,
1829
- CHARM_HYPER_API_KEY: true,
1830
- CLINE_API_KEY: true,
1831
- CLOUDFLARE_AI_GATEWAY_API_KEY: true,
1832
- COHERE_API_KEY: true,
1833
- COMMAND_CODE_API_KEY: true,
1834
- COREWEAVE_API_KEY: true,
1835
- CURSOR_ACCESS_TOKEN: true,
1836
- DEEPINFRA_API_KEY: true,
1837
- DEEPSEEK_API_KEY: true,
1838
- DEVIN_API_KEY: true,
1839
- FIREPASS_API_KEY: true,
1840
- FIREWORKS_API_KEY: true,
1841
- FUGU_API_KEY: true,
1842
- GEMINI_API_KEY: true,
1843
- GMI_API_KEY: true,
1844
- GOOGLE_API_KEY: true,
1845
- GOOGLE_APPLICATION_CREDENTIALS: true,
1846
- GROQ_API_KEY: true,
1847
- HF_TOKEN: true,
1848
- HUGGINGFACE_HUB_TOKEN: true,
1849
- LLAMA_CPP_API_KEY: true,
1850
- LM_STUDIO_API_KEY: true,
1851
- META_API_KEY: true,
1852
- MINIMAX_API_KEY: true,
1853
- MINIMAX_CODE_API_KEY: true,
1854
- MINIMAX_CODE_CN_API_KEY: true,
1855
- MISTRAL_API_KEY: true,
1856
- MODEL_API_KEY: true,
1857
- MOONSHOT_API_KEY: true,
1858
- NANO_GPT_API_KEY: true,
1859
- NVIDIA_API_KEY: true,
1860
- NOVITA_API_KEY: true,
1861
- OLLAMA_API_KEY: true,
1862
- OLLAMA_CLOUD_API_KEY: true,
1863
- OLLAMA_HOST: true,
1864
- OMP_AUTH_BROKER_TOKEN: true,
1865
- OMP_AUTH_BROKER_URL: true,
1866
- OPENCODE_API_KEY: true,
1867
- OPENAI_API_KEY: true,
1868
- OPENAI_CODEX_OAUTH_TOKEN: true,
1869
- OPENROUTER_API_KEY: true,
1870
- PLEXUS_API_KEY: true,
1871
- QIANFAN_API_KEY: true,
1872
- QWEN_OAUTH_TOKEN: true,
1873
- QWEN_PORTAL_API_KEY: true,
1874
- SAKANA_API_KEY: true,
1875
- SILICONFLOW_API_KEY: true,
1876
- SILICONFLOW_CN_API_KEY: true,
1877
- SYNTHETIC_API_KEY: true,
1878
- TOGETHER_API_KEY: true,
1879
- UMANS_AI_CODING_PLAN_API_KEY: true,
1880
- VENICE_API_KEY: true,
1881
- VLLM_API_KEY: true,
1882
- WAFER_SERVERLESS_API_KEY: true,
1883
- WANDB_API_KEY: true,
1884
- XAI_API_KEY: true,
1885
- XAI_OAUTH_TOKEN: true,
1886
- XIAOMI_API_KEY: true,
1887
- XIAOMI_TOKEN_PLAN_AMS_API_KEY: true,
1888
- XIAOMI_TOKEN_PLAN_CN_API_KEY: true,
1889
- XIAOMI_TOKEN_PLAN_SGP_API_KEY: true,
1890
- YOLO_AUTO_API_KEY: true,
1891
- ZAI_API_KEY: true,
1892
- ZENMUX_API_KEY: true,
1893
- ZHIPU_API_KEY: true,
1894
- };
1895
- const BLOCKED_SESSION_ENV =
1896
- /^(?:BASH_ENV|BUN_INSTALL.*|BUN_OPTIONS|CLASSPATH|CLAUDE_BASH_NO_CI|CLAUDE_BASH_NO_LOGIN|CLAUDE_CODE_SHELL_PREFIX|DYLD_.*|EDITOR|ELECTRON_RUN_AS_NODE|ENV|GEM_HOME|GEM_PATH|GIT_CONFIG.*|GIT_SSH_COMMAND|HOME|JAVA_TOOL_OPTIONS|LD_.*|NODE_OPTIONS|NODE_PATH|NPM_CONFIG_.*|OMP_AUTORESEARCH_DB_DIR|OMP_COMMAND|OMP_GITHUB_CACHE_DB|OMP_PROFILE|OMP_WORKTREE_DIR|PATH|PATHEXT|PERL5LIB|PERL5OPT|PI_BASH_NO_CI|PI_BASH_NO_LOGIN|PI_CODING_AGENT_DIR|PI_CODING_AGENT_SESSION_DIR|PI_CONFIG_DIR|PI_CONFIG_FILES|PI_GIT_COMMON_DIR|PI_PACKAGE_DIR|PI_PROFILE|PI_PROJECT_DIR|PI_SESSION_ID|PI_SHELL_PREFIX|PI_SUBPROCESS_CMD|PI_WORKTREE_DIR|PWD|PYTHONHOME|PYTHONINSPECT|PYTHONPATH|PYTHONSTARTUP|RUBYLIB|RUBYOPT|SHELL|SYSTEMROOT|USERPROFILE|VISUAL|XDG_CACHE_HOME|XDG_CONFIG_HOME|XDG_DATA_HOME|XDG_RUNTIME_DIR|XDG_STATE_HOME|_JAVA_OPTIONS)$/u;
1897
- const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
1898
-
1899
- function validateBoundedText(value: unknown, field: string, maxBytes: number): string {
1900
- if (
1901
- typeof value !== "string" ||
1902
- utf8Bytes(value) === 0 ||
1903
- utf8Bytes(value) > maxBytes ||
1904
- value.includes("\0")
1905
- ) {
1906
- throw new Error(`Invalid OMP ${field}`);
1907
- }
1908
- return value;
1909
- }
1910
-
1911
- function inheritedEnvironmentNames(inheritEnv: readonly string[] | undefined): ReadonlySet<string> {
1912
- if (inheritEnv === undefined) return new Set();
1913
- if (!Array.isArray(inheritEnv)) {
1914
- throw new Error("OMP inherited environment names are invalid");
1915
- }
1916
- if (inheritEnv.length > MAX_ENV_ENTRIES) {
1917
- throw new Error("OMP inherited environment has too many entries");
1918
- }
1919
- const names = new Set<string>();
1920
- for (const name of inheritEnv) {
1921
- if (typeof name !== "string" || !ENV_NAME.test(name)) {
1922
- throw new Error("OMP inherited environment contains an invalid name");
1923
- }
1924
- if (BLOCKED_SESSION_ENV.test(name.toUpperCase())) {
1925
- throw new Error("OMP inherited environment contains a forbidden variable");
1926
- }
1927
- names.add(name);
1928
- }
1929
- return names;
1930
- }
1931
-
1932
- function buildOmpEnvironment(
1933
- sessionEnv: Readonly<Record<string, string>> | undefined,
1934
- inheritEnv: readonly string[] | undefined,
1935
- sourceEnv: NodeJS.ProcessEnv,
1936
- ): { env: NodeJS.ProcessEnv; inheritedRedactionValues: string[] } {
1937
- if (
1938
- sessionEnv !== undefined &&
1939
- (sessionEnv === null || typeof sessionEnv !== "object" || Array.isArray(sessionEnv))
1940
- ) {
1941
- throw new Error("OMP session environment is invalid");
1942
- }
1943
- const explicitlyInherited = inheritedEnvironmentNames(inheritEnv);
1944
- const explicitlyInheritedNormalized = new Set(
1945
- [...explicitlyInherited].map((name) => name.toUpperCase()),
1946
- );
1947
- const explicitNames = new Set(Object.keys(sessionEnv ?? {}).map((name) => name.toUpperCase()));
1948
- const env: NodeJS.ProcessEnv = {};
1949
- const inheritedRedactionValues: string[] = [];
1950
- let totalBytes = 0;
1951
- for (const [name, value] of Object.entries(sourceEnv)) {
1952
- if (value === undefined || name.toUpperCase() === "OMP_COMMAND") continue;
1953
- const normalizedName = name.toUpperCase();
1954
- const isRuntime =
1955
- process.platform === "win32"
1956
- ? normalizedName in INHERITED_RUNTIME_ENV
1957
- : name in INHERITED_RUNTIME_ENV;
1958
- const isProviderAuth =
1959
- process.platform === "win32"
1960
- ? normalizedName in INHERITED_PROVIDER_AUTH_ENV
1961
- : name in INHERITED_PROVIDER_AUTH_ENV;
1962
- const isExplicitlyInherited =
1963
- process.platform === "win32"
1964
- ? explicitlyInheritedNormalized.has(normalizedName)
1965
- : explicitlyInherited.has(name);
1966
- if (!isRuntime && !isProviderAuth && !isExplicitlyInherited) continue;
1967
- if (explicitNames.has(normalizedName)) continue;
1968
- const valueBytes = utf8Bytes(value);
1969
- if (!ENV_NAME.test(name) || valueBytes > MAX_ENV_VALUE_LENGTH || value.includes("\0")) {
1970
- if (isExplicitlyInherited) {
1971
- throw new Error("OMP inherited environment contains an invalid value");
1972
- }
1973
- continue;
1974
- }
1975
- if (isExplicitlyInherited && valueBytes > 0 && valueBytes < 4) {
1976
- throw new OmpPublicError("OMP inherited environment value is too short for safe redaction");
1977
- }
1978
- totalBytes += utf8Bytes(name) + valueBytes;
1979
- if (totalBytes > MAX_ENV_TOTAL_LENGTH) {
1980
- throw new Error("OMP inherited environment is too large");
1981
- }
1982
- env[name] = value;
1983
- if (isExplicitlyInherited && valueBytes > 0) inheritedRedactionValues.push(value);
1984
- }
1985
- let entryCount = 0;
1986
- for (const name in sessionEnv ?? {}) {
1987
- if (!Object.hasOwn(sessionEnv ?? {}, name)) continue;
1988
- entryCount += 1;
1989
- if (entryCount > MAX_ENV_ENTRIES)
1990
- throw new Error("OMP session environment has too many entries");
1991
- const value = (sessionEnv as Readonly<Record<string, string>>)[name];
1992
- const normalizedName = name.toUpperCase();
1993
- if (!ENV_NAME.test(name) || BLOCKED_SESSION_ENV.test(normalizedName)) {
1994
- throw new Error("OMP session environment contains a forbidden variable");
1995
- }
1996
- if (
1997
- typeof value !== "string" ||
1998
- utf8Bytes(value) > MAX_ENV_VALUE_LENGTH ||
1999
- value.includes("\0")
2000
- ) {
2001
- throw new Error("OMP session environment contains an invalid value");
2002
- }
2003
- const valueBytes = utf8Bytes(value);
2004
- totalBytes += utf8Bytes(name) + valueBytes;
2005
- if (totalBytes > MAX_ENV_TOTAL_LENGTH) throw new Error("OMP session environment is too large");
2006
- for (const inheritedName of Object.keys(env)) {
2007
- if (inheritedName !== name && inheritedName.toUpperCase() === normalizedName) {
2008
- delete env[inheritedName];
2009
- }
2010
- }
2011
- env[name] = value;
2012
- }
2013
- return { env, inheritedRedactionValues };
2014
- }
2015
-
2016
- export function buildOmpSpawnRequest(
2017
- options: OmpStartOptions,
2018
- sourceEnv: NodeJS.ProcessEnv = process.env,
2019
- ): OmpSpawnRequest {
2020
- const environmentSource = options.environment ?? sourceEnv;
2021
- const cwd = validateBoundedText(options.cwd, "working directory", MAX_PATH_LENGTH);
2022
- if (!isAbsolute(cwd)) throw new Error("OMP working directory must be absolute");
2023
- const commandPrefix = options.command ?? [environmentSource.OMP_COMMAND ?? "omp"];
2024
- if (commandPrefix.length === 0) throw new Error("Invalid OMP command");
2025
- const [rawCommand, ...rawPrefixArgs] = commandPrefix;
2026
- const command = validateBoundedText(rawCommand, "command", MAX_PATH_LENGTH);
2027
- const args = rawPrefixArgs.map((argument) =>
2028
- validateBoundedText(argument, "command argument", MAX_PATH_LENGTH),
2029
- );
2030
- if (/[\r\n]/u.test(command)) throw new Error("Invalid OMP command");
2031
- const mode = options.mode ?? "full";
2032
- if (mode !== "full" && mode !== "write" && mode !== "ask") throw new Error("Invalid OMP mode");
2033
- const approvalMode = mode === "full" ? "yolo" : mode === "write" ? "write" : "always-ask";
2034
- if (!args.some((argument) => argument === "--mode" || argument.startsWith("--mode="))) {
2035
- args.push("--mode", "rpc-ui");
2036
- }
2037
- args.push("--approval-mode", approvalMode);
2038
- if (options.tools) {
2039
- if (options.tools.length === 0) args.push("--no-tools");
2040
- else args.push("--tools", options.tools.join(","));
2041
- }
2042
- if (options.model !== undefined) {
2043
- args.push("--model", validateBoundedText(options.model, "model", MAX_MODEL_SELECTOR_BYTES));
2044
- }
2045
- if (options.thinkingOption !== undefined) {
2046
- const thinking = OmpThinkingLevelSchema.safeParse(options.thinkingOption);
2047
- if (!thinking.success) throw new Error("Invalid OMP thinking option");
2048
- args.push("--thinking", thinking.data);
2049
- }
2050
- if (options.roleModels?.smol) {
2051
- args.push(
2052
- "--smol",
2053
- validateBoundedText(options.roleModels.smol, "smol model", MAX_MODEL_SELECTOR_BYTES),
2054
- );
2055
- }
2056
- if (options.roleModels?.slow) {
2057
- args.push(
2058
- "--slow",
2059
- validateBoundedText(options.roleModels.slow, "slow model", MAX_MODEL_SELECTOR_BYTES),
2060
- );
2061
- }
2062
- if (options.roleModels?.plan) {
2063
- args.push(
2064
- "--plan",
2065
- validateBoundedText(options.roleModels.plan, "plan model", MAX_MODEL_SELECTOR_BYTES),
2066
- );
2067
- }
2068
- if (options.sessionDir !== undefined) {
2069
- args.push(
2070
- "--session-dir",
2071
- validateBoundedText(options.sessionDir, "session directory", MAX_PATH_LENGTH),
2072
- );
2073
- }
2074
- if (options.resumeSessionId !== undefined) {
2075
- args.push("--resume", validateNativeSessionId(options.resumeSessionId));
2076
- }
2077
- if (options.noSession) args.push("--no-session");
2078
- const systemPrompt = options.systemPrompt?.trim();
2079
- if (systemPrompt) {
2080
- args.push(
2081
- "--append-system-prompt",
2082
- validateBoundedText(systemPrompt, "system prompt", MAX_SYSTEM_PROMPT_LENGTH),
2083
- );
2084
- }
2085
- const { env, inheritedRedactionValues } = buildOmpEnvironment(
2086
- options.env,
2087
- options.inheritEnv,
2088
- environmentSource,
2089
- );
2090
- env.OMP_NO_WEBP = "1";
2091
- return {
2092
- command,
2093
- args,
2094
- cwd,
2095
- env,
2096
- inheritedRedactionValues,
2097
- detached: process.platform !== "win32",
2098
- };
2099
- }
2100
-
2101
2126
  function waitWithTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
2102
2127
  const result = Promise.withResolvers<T>();
2103
2128
  const timer = setTimeout(() => result.reject(new Error(message)), timeoutMs);
@@ -2114,118 +2139,6 @@ function waitWithTimeout<T>(promise: Promise<T>, timeoutMs: number, message: str
2114
2139
  return result.promise;
2115
2140
  }
2116
2141
 
2117
- function waitMs(ms: number): Promise<void> {
2118
- const result = Promise.withResolvers<void>();
2119
- setTimeout(result.resolve, ms);
2120
- return result.promise;
2121
- }
2122
-
2123
- function processIsGone(error: unknown): boolean {
2124
- return (error as NodeJS.ErrnoException)?.code === "ESRCH";
2125
- }
2126
-
2127
- function isConfirmedNoProcessSpawnFailure(error: unknown): boolean {
2128
- const code = (error as NodeJS.ErrnoException)?.code;
2129
- return code === "ENOENT" || code === "EACCES" || code === "EPERM";
2130
- }
2131
-
2132
- /**
2133
- * Terminates the detached process group created for OMP. This covers descendants that remain in
2134
- * that group after the leader exits; descendants that deliberately re-parent into another process
2135
- * group are outside this transport's containment boundary.
2136
- */
2137
- export async function terminatePosixProcessTree(
2138
- pid: number,
2139
- graceMs: number,
2140
- signalProcess: (pid: number, signal: NodeJS.Signals | 0) => void = process.kill,
2141
- wait: (ms: number) => Promise<void> = waitMs,
2142
- ): Promise<boolean> {
2143
- try {
2144
- signalProcess(-pid, 0);
2145
- } catch (error) {
2146
- return processIsGone(error);
2147
- }
2148
- try {
2149
- signalProcess(-pid, "SIGTERM");
2150
- } catch (error) {
2151
- if (!processIsGone(error)) return false;
2152
- }
2153
- await wait(graceMs);
2154
- try {
2155
- signalProcess(-pid, 0);
2156
- } catch (error) {
2157
- return processIsGone(error);
2158
- }
2159
- try {
2160
- signalProcess(-pid, "SIGKILL");
2161
- } catch (error) {
2162
- if (!processIsGone(error)) return false;
2163
- }
2164
- await wait(graceMs);
2165
- try {
2166
- signalProcess(-pid, 0);
2167
- return false;
2168
- } catch (error) {
2169
- return processIsGone(error);
2170
- }
2171
- }
2172
-
2173
- type ProcessTreeCleanup = "verified" | "uncertain" | "failed";
2174
-
2175
- async function stopWindowsTree(pid: number): Promise<ProcessTreeCleanup> {
2176
- const result = Promise.withResolvers<ProcessTreeCleanup>();
2177
- const systemRoot = process.env.SystemRoot ?? WINDOWS_DEFAULT_SYSTEM_ROOT;
2178
- let taskkill: ChildProcessWithoutNullStreams;
2179
- try {
2180
- taskkill = spawn(
2181
- join(systemRoot, "System32", "taskkill.exe"),
2182
- ["/PID", String(pid), "/T", "/F"],
2183
- {
2184
- stdio: ["pipe", "pipe", "pipe"],
2185
- windowsHide: true,
2186
- env: { SystemRoot: systemRoot },
2187
- },
2188
- );
2189
- } catch {
2190
- return "failed";
2191
- }
2192
- taskkill.stdout.resume();
2193
- taskkill.stderr.resume();
2194
- let settled = false;
2195
- let deadline: TimerHandle | undefined;
2196
- let finalDeadline: TimerHandle | undefined;
2197
- const finish = (outcome: ProcessTreeCleanup) => {
2198
- if (settled) return;
2199
- settled = true;
2200
- clearTimeout(deadline);
2201
- clearTimeout(finalDeadline);
2202
- result.resolve(outcome);
2203
- };
2204
- deadline = setTimeout(() => {
2205
- taskkill.kill("SIGKILL");
2206
- finalDeadline = setTimeout(() => finish("failed"), PROCESS_STOP_TIMEOUT_MS);
2207
- }, PROCESS_STOP_TIMEOUT_MS);
2208
- taskkill.once("error", () => finish("failed"));
2209
- taskkill.once("close", (code, signal) => {
2210
- finish(
2211
- code === 0 && signal === null
2212
- ? "verified"
2213
- : code === 128 && signal === null
2214
- ? "uncertain"
2215
- : "failed",
2216
- );
2217
- });
2218
- return result.promise;
2219
- }
2220
-
2221
- export async function terminateSpawnedProcessTree(
2222
- pid: number,
2223
- platform: NodeJS.Platform = process.platform,
2224
- ): Promise<boolean> {
2225
- if (platform === "win32") return (await stopWindowsTree(pid)) === "verified";
2226
- return await terminatePosixProcessTree(pid, PROCESS_STOP_TIMEOUT_MS);
2227
- }
2228
-
2229
2142
  class OmpRpcProcess {
2230
2143
  readonly ready: Promise<ReadyFrame>;
2231
2144
  readonly inheritedRedactionValues: readonly string[];
@@ -2618,8 +2531,13 @@ class OmpRpcProcess {
2618
2531
  } else {
2619
2532
  this.appendLinePart(part);
2620
2533
  if (this.fatalError) return;
2621
- if (this.discardingLine) this.resetDiscardedLine();
2622
- else this.completeLine();
2534
+ if (this.discardingLine) {
2535
+ if (this.discardedLineBytes > MAX_SEMANTIC_FRAME_BYTES) {
2536
+ this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
2537
+ return;
2538
+ }
2539
+ this.resetDiscardedLine();
2540
+ } else this.completeLine();
2623
2541
  }
2624
2542
  start = index + 1;
2625
2543
  }
@@ -2674,10 +2592,6 @@ class OmpRpcProcess {
2674
2592
  this.lineParts = [];
2675
2593
  this.lineBytes = 0;
2676
2594
  const payload = line.at(-1) === 13 ? line.subarray(0, -1) : line;
2677
- if (payload.byteLength > MAX_SEMANTIC_FRAME_BYTES) {
2678
- this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
2679
- return;
2680
- }
2681
2595
  let decoded: unknown;
2682
2596
  try {
2683
2597
  decoded = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload));
@@ -2699,7 +2613,10 @@ class OmpRpcProcess {
2699
2613
  frame.byteLength > MAX_SEMANTIC_FRAME_BYTES &&
2700
2614
  ![...this.pending.values()].some(
2701
2615
  (pending) =>
2702
- pending.command === "get_messages" || pending.command === "get_subagent_messages",
2616
+ pending.command === "get_messages" ||
2617
+ pending.command === "get_messages_page" ||
2618
+ pending.command === "get_subagent_messages" ||
2619
+ pending.command === "get_state",
2703
2620
  )
2704
2621
  ) {
2705
2622
  this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
@@ -2805,12 +2722,16 @@ class OmpRpcProcess {
2805
2722
  const pending = typeof frame.id === "string" ? this.pending.get(frame.id) : undefined;
2806
2723
  if (
2807
2724
  frame.type === "response" &&
2808
- (pending?.command === "get_messages" || pending?.command === "get_subagent_messages")
2725
+ (pending?.command === "get_messages" ||
2726
+ pending?.command === "get_messages_page" ||
2727
+ pending?.command === "get_subagent_messages" ||
2728
+ pending?.command === "get_state")
2809
2729
  ) {
2810
2730
  this.receiveResponse(frame);
2811
2731
  return;
2812
2732
  }
2813
2733
  }
2734
+ if (this.receiveUnsupportedCommandResponse(value)) return;
2814
2735
  if (rawByteLength > MAX_SEMANTIC_FRAME_BYTES) {
2815
2736
  this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
2816
2737
  return;
@@ -2845,6 +2766,30 @@ class OmpRpcProcess {
2845
2766
  return true;
2846
2767
  }
2847
2768
 
2769
+ private receiveUnsupportedCommandResponse(value: unknown): boolean {
2770
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2771
+ const frame = value as Record<string, unknown>;
2772
+ if (
2773
+ frame.type !== "response" ||
2774
+ frame.id !== undefined ||
2775
+ frame.success !== false ||
2776
+ typeof frame.command !== "string" ||
2777
+ frame.error !== `Unknown command: ${frame.command}`
2778
+ ) {
2779
+ return false;
2780
+ }
2781
+ const matches = [...this.pending.entries()].filter(
2782
+ ([, pending]) => pending.command === frame.command,
2783
+ );
2784
+ if (matches.length === 0) return false;
2785
+ const [id, pending] = matches[0] ?? [];
2786
+ if (!id || !pending) return false;
2787
+ this.takePending(id)?.reject(
2788
+ new OmpRpcRequestRejectedError(rpcDiagnosticCommand(pending.command), "unsupported_command"),
2789
+ );
2790
+ return true;
2791
+ }
2792
+
2848
2793
  private receiveResponse(frame: Record<string, unknown>): void {
2849
2794
  const rawId = typeof frame.id === "string" ? frame.id : undefined;
2850
2795
  const knownPending = rawId ? this.pending.get(rawId) : undefined;
@@ -2877,10 +2822,14 @@ class OmpRpcProcess {
2877
2822
  }
2878
2823
  const isBranchHistory = pending.command === "get_branch_messages";
2879
2824
  const isHistory =
2880
- pending.command === "get_messages" || pending.command === "get_subagent_messages";
2881
- const responseData = isHistory
2882
- ? sanitizeHistoryResponseData(response.data.data)
2883
- : response.data.data;
2825
+ pending.command === "get_messages" ||
2826
+ pending.command === "get_messages_page" ||
2827
+ pending.command === "get_subagent_messages";
2828
+ const rawResponseData =
2829
+ pending.command === "get_state"
2830
+ ? projectSessionStateResponseData(response.data.data)
2831
+ : response.data.data;
2832
+ const responseData = isHistory ? sanitizeHistoryResponseData(rawResponseData) : rawResponseData;
2884
2833
  const boundedFrame =
2885
2834
  responseData === response.data.data ? frame : { ...frame, data: responseData };
2886
2835
  const responseItemLimit = isBranchHistory
@@ -2903,17 +2852,21 @@ class OmpRpcProcess {
2903
2852
  : pending.command === "get_available_models"
2904
2853
  ? 16_384
2905
2854
  : 2_048;
2906
- if (
2907
- boundedJsonBytes(
2908
- boundedFrame,
2909
- responseByteLimit,
2910
- responseItemLimit,
2911
- MAX_IMAGE_DATA_LENGTH,
2912
- responseNodeLimit,
2913
- ) === Number.POSITIVE_INFINITY
2914
- ) {
2855
+ const violation = jsonBoundViolation(
2856
+ boundedFrame,
2857
+ responseByteLimit,
2858
+ responseItemLimit,
2859
+ MAX_IMAGE_DATA_LENGTH,
2860
+ responseNodeLimit,
2861
+ );
2862
+ if (violation) {
2915
2863
  this.takePending(response.data.id)?.reject(
2916
- new Error("OMP RPC response exceeded command limits"),
2864
+ new OmpRpcResponseLimitError(
2865
+ rpcDiagnosticCommand(pending.command),
2866
+ violation.dimension,
2867
+ violation.actual,
2868
+ violation.limit,
2869
+ ),
2917
2870
  );
2918
2871
  return;
2919
2872
  }
@@ -2934,7 +2887,16 @@ class OmpRpcProcess {
2934
2887
  settled.reject(new Error("OMP RPC response is invalid"));
2935
2888
  }
2936
2889
  } else {
2937
- settled.reject(new OmpRpcRequestRejectedError());
2890
+ const responseCode = response.data.code;
2891
+ const code: OmpRpcRequestRejectionCode | undefined =
2892
+ responseCode === "session_busy" ||
2893
+ responseCode === "stale_cursor" ||
2894
+ responseCode === "unsupported_command"
2895
+ ? responseCode
2896
+ : response.data.error === `Unknown command: ${pending.command}`
2897
+ ? "unsupported_command"
2898
+ : undefined;
2899
+ settled.reject(new OmpRpcRequestRejectedError(rpcDiagnosticCommand(pending.command), code));
2938
2900
  }
2939
2901
  }
2940
2902
 
@@ -3189,7 +3151,7 @@ class OmpRpcProcess {
3189
3151
  this.activeToolCallIds.add(event.toolCallId);
3190
3152
  return true;
3191
3153
  }
3192
- if (event.type === "tool_execution_update") {
3154
+ if (event.type === "tool_execution_update" || event.type === "tool_stream_update") {
3193
3155
  return this.activeToolCallIds.has(event.toolCallId);
3194
3156
  }
3195
3157
  if (event.type === "tool_execution_end") {
@@ -3400,6 +3362,7 @@ class OmpRpcSession implements OmpRuntimeSession {
3400
3362
  readonly inheritedRedactionValues: readonly string[],
3401
3363
  readonly supportsTypedToolApprovals: boolean,
3402
3364
  ) {}
3365
+ private messagePagingSupport: "unknown" | "supported" | "unsupported" = "unknown";
3403
3366
 
3404
3367
  onEvent(listener: (event: OmpRpcEvent) => void): () => void {
3405
3368
  return this.process.onEvent(listener);
@@ -3413,6 +3376,195 @@ class OmpRpcSession implements OmpRuntimeSession {
3413
3376
  return OmpSessionStatsSchema.parse(await this.process.request({ type: "get_session_stats" }));
3414
3377
  }
3415
3378
 
3379
+ async getMessages(signal?: AbortSignal): Promise<OmpMessage[]> {
3380
+ if (!this.canReplayHistory) {
3381
+ throw new Error("OMP history replay requires negotiated RPC protocol v2");
3382
+ }
3383
+ if (this.messagePagingSupport === "unsupported") {
3384
+ const legacy = OmpMessagesResultSchema.parse(
3385
+ await this.requestWithSignal({ type: "get_messages" }, signal),
3386
+ );
3387
+ return legacy.messages;
3388
+ }
3389
+ let staleRestarts = 0;
3390
+ let busyRetries = 0;
3391
+ while (true) {
3392
+ const messages: OmpMessage[] = [];
3393
+ const seenCursors = new Set<string>();
3394
+ const seenMessageIdentities = new Set<string>();
3395
+ let pageCount = 0;
3396
+ let cursor: string | undefined;
3397
+ let totalMessages: number | undefined;
3398
+ let aggregateBytes = 0;
3399
+ let aggregateNodes = 0;
3400
+ try {
3401
+ while (true) {
3402
+ signal?.throwIfAborted();
3403
+ let rawPage: unknown;
3404
+ try {
3405
+ rawPage = await this.requestWithSignal(
3406
+ {
3407
+ type: "get_messages_page",
3408
+ ...(cursor ? { cursor } : {}),
3409
+ limit: OMP_MESSAGE_PAGE_LIMIT,
3410
+ },
3411
+ signal,
3412
+ );
3413
+ } catch (error) {
3414
+ if (
3415
+ error instanceof OmpRpcRequestRejectedError &&
3416
+ error.code === "unsupported_command"
3417
+ ) {
3418
+ this.messagePagingSupport = "unsupported";
3419
+ const legacy = OmpMessagesResultSchema.parse(
3420
+ await this.requestWithSignal({ type: "get_messages" }, signal),
3421
+ );
3422
+ return legacy.messages;
3423
+ }
3424
+ if (
3425
+ error instanceof OmpRpcRequestRejectedError &&
3426
+ error.code === "session_busy" &&
3427
+ busyRetries < MAX_MESSAGE_PAGE_BUSY_RETRIES
3428
+ ) {
3429
+ const delayMs = MESSAGE_PAGE_RETRY_BASE_MS * 2 ** busyRetries;
3430
+ busyRetries += 1;
3431
+ await this.waitForReplayRetry(delayMs, signal);
3432
+ continue;
3433
+ }
3434
+ throw error;
3435
+ }
3436
+ pageCount += 1;
3437
+ if (pageCount > MAX_MESSAGE_PAGES) {
3438
+ throw new OmpRpcResponseLimitError(
3439
+ "get_messages_page",
3440
+ "items",
3441
+ pageCount,
3442
+ MAX_MESSAGE_PAGES,
3443
+ );
3444
+ }
3445
+ const page = OmpMessagesPageResultSchema.parse(rawPage);
3446
+ this.messagePagingSupport = "supported";
3447
+ if (totalMessages !== undefined && page.totalMessages !== totalMessages) {
3448
+ throw new Error("OMP message pagination returned an inconsistent total");
3449
+ }
3450
+ totalMessages ??= page.totalMessages;
3451
+ const nextMessageCount = messages.length + page.messages.length;
3452
+ if (nextMessageCount > totalMessages || nextMessageCount > MAX_REPLAY_MESSAGES) {
3453
+ throw new OmpRpcResponseLimitError(
3454
+ "get_messages_page",
3455
+ "items",
3456
+ nextMessageCount,
3457
+ Math.min(totalMessages, MAX_REPLAY_MESSAGES),
3458
+ );
3459
+ }
3460
+ const pageIdentities = new Set<string>();
3461
+ for (const message of page.messages) {
3462
+ const identity = message.entryId ?? message.id ?? message.responseId;
3463
+ if (!identity) continue;
3464
+ if (seenMessageIdentities.has(identity)) {
3465
+ throw new Error("OMP message pagination repeated a message identity");
3466
+ }
3467
+ pageIdentities.add(identity);
3468
+ }
3469
+ const inspection = inspectJsonBounds(
3470
+ page.messages,
3471
+ MAX_REPLAY_BYTES,
3472
+ MAX_REPLAY_MESSAGES,
3473
+ MAX_IMAGE_DATA_LENGTH,
3474
+ MAX_REPLAY_NODES,
3475
+ );
3476
+ if (inspection.violation) {
3477
+ throw new OmpRpcResponseLimitError(
3478
+ "get_messages_page",
3479
+ inspection.violation.dimension,
3480
+ inspection.violation.actual,
3481
+ inspection.violation.limit,
3482
+ );
3483
+ }
3484
+ aggregateBytes += inspection.bytes;
3485
+ aggregateNodes += inspection.nodes;
3486
+ if (aggregateBytes > MAX_REPLAY_BYTES) {
3487
+ throw new OmpRpcResponseLimitError(
3488
+ "get_messages_page",
3489
+ "bytes",
3490
+ aggregateBytes,
3491
+ MAX_REPLAY_BYTES,
3492
+ );
3493
+ }
3494
+ if (aggregateNodes > MAX_REPLAY_NODES) {
3495
+ throw new OmpRpcResponseLimitError(
3496
+ "get_messages_page",
3497
+ "nodes",
3498
+ aggregateNodes,
3499
+ MAX_REPLAY_NODES,
3500
+ );
3501
+ }
3502
+ for (const identity of pageIdentities) seenMessageIdentities.add(identity);
3503
+ messages.push(...page.messages);
3504
+ const nextCursor = page.nextCursor;
3505
+ if (!nextCursor) {
3506
+ if (messages.length !== totalMessages) {
3507
+ throw new Error("OMP message pagination ended before the advertised total");
3508
+ }
3509
+ return messages;
3510
+ }
3511
+ if (page.messages.length === 0 || messages.length >= totalMessages) {
3512
+ throw new Error("OMP message pagination did not make progress");
3513
+ }
3514
+ if (seenCursors.has(nextCursor)) {
3515
+ throw new Error("OMP message pagination repeated a cursor");
3516
+ }
3517
+ seenCursors.add(nextCursor);
3518
+ cursor = nextCursor;
3519
+ }
3520
+ } catch (error) {
3521
+ if (
3522
+ error instanceof OmpRpcRequestRejectedError &&
3523
+ error.code === "stale_cursor" &&
3524
+ staleRestarts < MAX_MESSAGE_PAGE_STALE_RESTARTS
3525
+ ) {
3526
+ staleRestarts += 1;
3527
+ continue;
3528
+ }
3529
+ throw error;
3530
+ }
3531
+ }
3532
+ }
3533
+
3534
+ private async requestWithSignal(
3535
+ command: Record<string, unknown>,
3536
+ signal?: AbortSignal,
3537
+ ): Promise<unknown> {
3538
+ signal?.throwIfAborted();
3539
+ const request = this.process.request(command);
3540
+ if (!signal) return request;
3541
+ const aborted = Promise.withResolvers<never>();
3542
+ const onAbort = () => aborted.reject(signal.reason);
3543
+ signal.addEventListener("abort", onAbort, { once: true });
3544
+ try {
3545
+ return await Promise.race([request, aborted.promise]);
3546
+ } finally {
3547
+ signal.removeEventListener("abort", onAbort);
3548
+ void request.catch(() => undefined);
3549
+ }
3550
+ }
3551
+
3552
+ private async waitForReplayRetry(delayMs: number, signal?: AbortSignal): Promise<void> {
3553
+ signal?.throwIfAborted();
3554
+ if (!signal) {
3555
+ await waitMs(delayMs);
3556
+ return;
3557
+ }
3558
+ const aborted = Promise.withResolvers<never>();
3559
+ const onAbort = () => aborted.reject(signal.reason);
3560
+ signal.addEventListener("abort", onAbort, { once: true });
3561
+ try {
3562
+ await Promise.race([waitMs(delayMs), aborted.promise]);
3563
+ } finally {
3564
+ signal.removeEventListener("abort", onAbort);
3565
+ }
3566
+ }
3567
+
3416
3568
  async compact(customInstructions?: string): Promise<OmpCompactionResult> {
3417
3569
  const instructions =
3418
3570
  customInstructions === undefined
@@ -3508,16 +3660,6 @@ class OmpRpcSession implements OmpRuntimeSession {
3508
3660
  return result.data;
3509
3661
  }
3510
3662
 
3511
- async getMessages(): Promise<OmpMessage[]> {
3512
- if (!this.canReplayHistory) {
3513
- throw new Error("OMP history replay requires negotiated RPC protocol v2");
3514
- }
3515
- const result = OmpMessagesResultSchema.parse(
3516
- await this.process.request({ type: "get_messages" }),
3517
- );
3518
- return result.messages;
3519
- }
3520
-
3521
3663
  async setHostTools(tools: readonly OmpHostToolDefinition[]): Promise<string[]> {
3522
3664
  const safeTools = z.array(OmpHostToolDefinitionSchema).max(MAX_HOST_TOOLS).parse(tools);
3523
3665
  if (safeTools.length === 0) return [];
@@ -3627,13 +3769,14 @@ export class OmpRpcRuntime implements OmpRuntime {
3627
3769
  ...transcript,
3628
3770
  messages: z
3629
3771
  .array(OmpMessageSchema)
3630
- .max(100_000)
3772
+ .max(MAX_REPLAY_MESSAGES)
3631
3773
  .parse(sanitizeMessageListMetadata(transcript.messages)),
3632
3774
  };
3633
3775
  }
3634
3776
  async readPersistedSubagentTranscript(options: {
3635
3777
  parentSessionFile: string;
3636
3778
  childTranscriptId: string;
3779
+ sessionFile?: string;
3637
3780
  cwd: string;
3638
3781
  signal?: AbortSignal;
3639
3782
  }): Promise<OmpPersistedSubagentMessages> {
@@ -3642,12 +3785,13 @@ export class OmpRpcRuntime implements OmpRuntime {
3642
3785
  options.childTranscriptId,
3643
3786
  options.cwd,
3644
3787
  options.signal,
3788
+ options.sessionFile,
3645
3789
  );
3646
3790
  return {
3647
3791
  ...transcript,
3648
3792
  messages: z
3649
3793
  .array(OmpMessageSchema)
3650
- .max(100_000)
3794
+ .max(MAX_REPLAY_MESSAGES)
3651
3795
  .parse(sanitizeMessageListMetadata(transcript.messages)),
3652
3796
  };
3653
3797
  }