@omercnet/paseo-omp 0.3.0-next.97.1 → 0.3.0-next.99.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "0.3.0-next.97.1",
3
+ "version": "0.3.0-next.99.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -58,6 +58,7 @@ const MAX_CONNECTION_SESSIONS = 32;
58
58
  const MAX_ACTIVE_OPERATIONS = 128;
59
59
  const MAX_PROVIDER_INPUT_BYTES = 2 * 1024 * 1024;
60
60
  const MAX_NESTED_OPTION_BYTES = 256 * 1024;
61
+ const MAX_ENV_ENTRIES = 256;
61
62
  const MAX_NATIVE_SESSION_RESERVATIONS = 256;
62
63
 
63
64
  function hasOwnEntries(value: unknown): boolean {
@@ -68,6 +69,19 @@ function hasOwnEntries(value: unknown): boolean {
68
69
  return false;
69
70
  }
70
71
 
72
+ function providerOptionsExceedPreflightLimits(value: unknown): boolean {
73
+ if (
74
+ boundedJsonBytes(value, MAX_NESTED_OPTION_BYTES, MAX_ENV_ENTRIES) === Number.POSITIVE_INFINITY
75
+ ) {
76
+ return true;
77
+ }
78
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
79
+ const unrelatedOptions = Object.fromEntries(
80
+ Object.entries(value).filter(([key]) => key !== "env" && key !== "inheritEnv"),
81
+ );
82
+ return boundedJsonBytes(unrelatedOptions, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY;
83
+ }
84
+
71
85
  function preflightProviderInput(input: unknown): void {
72
86
  if (!input || typeof input !== "object") throw new OmpPublicError("Invalid provider request");
73
87
  const record = input as Record<string, unknown>;
@@ -82,7 +96,16 @@ function preflightProviderInput(input: unknown): void {
82
96
  throw new OmpPublicError("Session persistence input is too large");
83
97
  }
84
98
  const config = record.config as Record<string, unknown> | undefined;
85
- for (const value of [config?.mcpServers, config?.providerOptions, config?.settings]) {
99
+ if (
100
+ (config?.env !== undefined &&
101
+ boundedJsonBytes(config.env, MAX_NESTED_OPTION_BYTES, MAX_ENV_ENTRIES) ===
102
+ Number.POSITIVE_INFINITY) ||
103
+ (config?.providerOptions !== undefined &&
104
+ providerOptionsExceedPreflightLimits(config.providerOptions))
105
+ ) {
106
+ throw new OmpPublicError("Session configuration is too large");
107
+ }
108
+ for (const value of [config?.mcpServers, config?.settings]) {
86
109
  if (
87
110
  value !== undefined &&
88
111
  boundedJsonBytes(value, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY
@@ -109,13 +132,17 @@ function preflightProviderInput(input: unknown): void {
109
132
  }
110
133
  }
111
134
  if (record.type === "catalog" || record.type === "sessions") {
112
- for (const value of [record.providerOptions, record.settings]) {
113
- if (
114
- value !== undefined &&
115
- boundedJsonBytes(value, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY
116
- ) {
117
- throw new OmpPublicError("Provider configuration is too large");
118
- }
135
+ if (
136
+ record.providerOptions !== undefined &&
137
+ providerOptionsExceedPreflightLimits(record.providerOptions)
138
+ ) {
139
+ throw new OmpPublicError("Provider configuration is too large");
140
+ }
141
+ if (
142
+ record.settings !== undefined &&
143
+ boundedJsonBytes(record.settings, MAX_NESTED_OPTION_BYTES) === Number.POSITIVE_INFINITY
144
+ ) {
145
+ throw new OmpPublicError("Provider configuration is too large");
119
146
  }
120
147
  }
121
148
  if (record.type === "session.prompt") {
@@ -4,7 +4,13 @@ import { isAbsolute, join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { ompDataDir } from "../paths";
6
6
  import { isValidImagePayload } from "./image";
7
- import { boundedJsonBytes, OmpCleanupFailure, OmpPublicError, utf8Bytes } from "./security";
7
+ import {
8
+ boundedJsonBytes,
9
+ boundedJsonMetrics,
10
+ OmpCleanupFailure,
11
+ OmpPublicError,
12
+ utf8Bytes,
13
+ } from "./security";
8
14
  import {
9
15
  listOmpSessionDescriptors,
10
16
  type OmpSessionDescriptor,
@@ -56,6 +62,14 @@ const MAX_PENDING_ONE_WAY_WRITES = 256;
56
62
  const MAX_PENDING_WRITE_BYTES = 8 * 1024 * 1024;
57
63
  const MAX_LINE_PARTS = 4_096;
58
64
  const MAX_ARRAY_ITEMS = 512;
65
+ // OMP read metadata can contain one source entry per displayed line. Bound this optional,
66
+ // opaque field separately so over-budget metadata can be omitted without losing completion events.
67
+ const MAX_OPTIONAL_METADATA_BYTES = MAX_TOOL_PAYLOAD_LENGTH;
68
+ const MAX_OPTIONAL_METADATA_ITEMS = 2_048;
69
+ const MAX_OPTIONAL_METADATA_NODES = 4_096;
70
+ const MAX_TASK_CORRELATION_BYTES = 256 * 1024;
71
+ const MAX_TASK_CORRELATION_ITEMS = 1_024;
72
+ const MAX_TASK_CORRELATION_NODES = 4_096;
59
73
  // Tool-intensive OMP turns legitimately exceed 64 blocks; transport byte/node budgets remain the
60
74
  // primary resource bounds.
61
75
  export const OMP_MAX_CONTENT_PARTS = 4_096;
@@ -118,6 +132,148 @@ function isBoundedJson(
118
132
  boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
119
133
  );
120
134
  }
135
+ function optionalMetadataMetrics(value: unknown) {
136
+ return boundedJsonMetrics(
137
+ value,
138
+ MAX_OPTIONAL_METADATA_BYTES,
139
+ MAX_OPTIONAL_METADATA_ITEMS,
140
+ MAX_OPTIONAL_METADATA_BYTES,
141
+ MAX_OPTIONAL_METADATA_NODES,
142
+ );
143
+ }
144
+
145
+ function optionalMetadataIsBounded(value: unknown): boolean {
146
+ return optionalMetadataMetrics(value) !== undefined;
147
+ }
148
+
149
+ function omitUnsafeOptionalDetails(value: unknown): unknown {
150
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
151
+ const record = value as Record<string, unknown>;
152
+ if (!Object.hasOwn(record, "details") || optionalMetadataIsBounded(record.details)) return value;
153
+ const { details: _details, ...safe } = record;
154
+ return safe;
155
+ }
156
+
157
+ const TASK_RESULT_STATUSES: Readonly<Record<string, true>> = {
158
+ pending: true,
159
+ running: true,
160
+ completed: true,
161
+ failed: true,
162
+ error: true,
163
+ aborted: true,
164
+ canceled: true,
165
+ cancelled: true,
166
+ };
167
+ const TASK_PROGRESS_STATUSES: Readonly<Record<string, true>> = {
168
+ pending: true,
169
+ running: true,
170
+ completed: true,
171
+ failed: true,
172
+ aborted: true,
173
+ };
174
+
175
+ function boundedTaskId(value: unknown): value is string {
176
+ return typeof value === "string" && value.length > 0 && utf8Bytes(value) <= MAX_ID_LENGTH;
177
+ }
178
+
179
+ function taskCorrelationDetails(value: unknown): unknown {
180
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
181
+ const details = value as Record<string, unknown>;
182
+ if (!Array.isArray(details.results) || details.results.length > MAX_TASK_CORRELATION_ITEMS)
183
+ return;
184
+ const results: Record<string, unknown>[] = [];
185
+ for (const value of details.results) {
186
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
187
+ const result = value as Record<string, unknown>;
188
+ if (!boundedTaskId(result.id)) return;
189
+ const safe: Record<string, unknown> = { id: result.id };
190
+ if (typeof result.status === "string" && Object.hasOwn(TASK_RESULT_STATUSES, result.status)) {
191
+ safe.status = result.status;
192
+ }
193
+ if (typeof result.aborted === "boolean") safe.aborted = result.aborted;
194
+ if (typeof result.exitCode === "number" && Number.isFinite(result.exitCode)) {
195
+ safe.exitCode = result.exitCode;
196
+ }
197
+ if (
198
+ result.error !== undefined &&
199
+ boundedJsonMetrics(result.error, 4_096, 32, 4_096, 64) !== undefined
200
+ ) {
201
+ safe.error = result.error;
202
+ }
203
+ results.push(safe);
204
+ }
205
+ let progress: Record<string, unknown>[] | undefined;
206
+ if (details.progress !== undefined) {
207
+ if (!Array.isArray(details.progress) || details.progress.length > MAX_TASK_CORRELATION_ITEMS) {
208
+ return;
209
+ }
210
+ progress = [];
211
+ for (const value of details.progress) {
212
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return;
213
+ const item = value as Record<string, unknown>;
214
+ if (
215
+ !boundedTaskId(item.id) ||
216
+ typeof item.index !== "number" ||
217
+ !Number.isInteger(item.index) ||
218
+ item.index < 0 ||
219
+ item.index >= MAX_TASK_CORRELATION_ITEMS ||
220
+ typeof item.status !== "string" ||
221
+ !Object.hasOwn(TASK_PROGRESS_STATUSES, item.status)
222
+ ) {
223
+ return;
224
+ }
225
+ progress.push({ id: item.id, index: item.index, status: item.status });
226
+ }
227
+ }
228
+ const correlation = { results, ...(progress ? { progress } : {}) };
229
+ return boundedJsonMetrics(
230
+ correlation,
231
+ MAX_TASK_CORRELATION_BYTES,
232
+ MAX_TASK_CORRELATION_ITEMS,
233
+ MAX_TASK_CORRELATION_BYTES,
234
+ MAX_TASK_CORRELATION_NODES,
235
+ )
236
+ ? correlation
237
+ : undefined;
238
+ }
239
+
240
+ function omitOptionalDetails(value: unknown, preserveTaskCorrelation = false): unknown {
241
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
242
+ const record = value as Record<string, unknown>;
243
+ if (!Object.hasOwn(record, "details")) return value;
244
+ const { details: _details, ...structural } = record;
245
+ if (!preserveTaskCorrelation) return structural;
246
+ const correlation = taskCorrelationDetails(record.details);
247
+ return correlation === undefined ? structural : { ...structural, details: correlation };
248
+ }
249
+
250
+ function createOptionalMetadataSanitizer(): (value: unknown, taskResult?: boolean) => unknown {
251
+ let retainedBytes = 0;
252
+ let retainedNodes = 0;
253
+ return (value, taskResult = false) => {
254
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
255
+ const record = value as Record<string, unknown>;
256
+ if (!Object.hasOwn(record, "details")) return value;
257
+ const metrics = optionalMetadataMetrics(record.details);
258
+ if (
259
+ metrics &&
260
+ retainedBytes + metrics.bytes <= MAX_OPTIONAL_METADATA_BYTES &&
261
+ retainedNodes + metrics.nodes <= MAX_OPTIONAL_METADATA_NODES
262
+ ) {
263
+ retainedBytes += metrics.bytes;
264
+ retainedNodes += metrics.nodes;
265
+ return value;
266
+ }
267
+ const correlation = taskResult ? taskCorrelationDetails(record.details) : undefined;
268
+ const { details: _details, ...safe } = record;
269
+ return correlation === undefined ? safe : { ...safe, details: correlation };
270
+ };
271
+ }
272
+
273
+ const OmpOptionalMetadataSchema = z.preprocess(
274
+ (value) => (optionalMetadataIsBounded(value) ? value : undefined),
275
+ z.unknown().optional(),
276
+ );
121
277
 
122
278
  const OmpContentPartSchema = z
123
279
  .object({
@@ -165,10 +321,7 @@ const OmpMessageIdentityShape = {
165
321
  responseId: IDENTIFIER.optional(),
166
322
  images: OmpImageArraySchema.optional(),
167
323
  timestamp: z.number().finite().optional(),
168
- details: z
169
- .unknown()
170
- .refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096))
171
- .optional(),
324
+ details: OmpOptionalMetadataSchema,
172
325
  };
173
326
  type OmpContentPart = z.infer<typeof OmpContentPartSchema>;
174
327
  type OmpMessageIdentity = {
@@ -409,6 +562,16 @@ const JsonObjectSchema = z.record(z.string(), z.unknown());
409
562
  const BoundedToolPayloadSchema = z
410
563
  .unknown()
411
564
  .refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096));
565
+ const OmpToolResultPayloadSchema = z.preprocess(
566
+ omitUnsafeOptionalDetails,
567
+ z
568
+ .unknown()
569
+ .refine(
570
+ (value) =>
571
+ isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, MAX_OPTIONAL_METADATA_ITEMS, 8_192) &&
572
+ isBoundedJson(omitOptionalDetails(value), MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096),
573
+ ),
574
+ );
412
575
  const OmpHostToolDefinitionSchema = z.object({
413
576
  name: NAME,
414
577
  label: NAME.optional(),
@@ -616,13 +779,13 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
616
779
  toolCallId: IDENTIFIER,
617
780
  toolName: NAME,
618
781
  args: BoundedToolPayloadSchema.optional(),
619
- partialResult: BoundedToolPayloadSchema,
782
+ partialResult: OmpToolResultPayloadSchema,
620
783
  }),
621
784
  z.object({
622
785
  type: z.literal("tool_execution_end"),
623
786
  toolCallId: IDENTIFIER,
624
787
  toolName: NAME,
625
- result: BoundedToolPayloadSchema,
788
+ result: OmpToolResultPayloadSchema,
626
789
  isError: z.boolean().optional(),
627
790
  }),
628
791
  OmpCompactionStartSchema,
@@ -873,6 +1036,102 @@ const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
873
1036
  OmpToolApprovalCancelSchema,
874
1037
  z.object({ type: z.literal("advisor_yielded") }),
875
1038
  ]);
1039
+ type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
1040
+
1041
+ function mapRecordField(
1042
+ record: Record<string, unknown>,
1043
+ key: string,
1044
+ map: OptionalDetailsMapper,
1045
+ taskResult = false,
1046
+ ): Record<string, unknown> {
1047
+ if (!Object.hasOwn(record, key)) return record;
1048
+ const next = map(record[key], taskResult);
1049
+ return next === record[key] ? record : { ...record, [key]: next };
1050
+ }
1051
+
1052
+ function mapMessageList(value: unknown, map: OptionalDetailsMapper): unknown {
1053
+ if (!Array.isArray(value)) return value;
1054
+ let changed = false;
1055
+ const messages = value.map((message) => {
1056
+ const taskResult =
1057
+ message !== null &&
1058
+ typeof message === "object" &&
1059
+ !Array.isArray(message) &&
1060
+ (message as Record<string, unknown>).role === "toolResult" &&
1061
+ (message as Record<string, unknown>).toolName === "task";
1062
+ const next = map(message, taskResult);
1063
+ changed ||= next !== message;
1064
+ return next;
1065
+ });
1066
+ return changed ? messages : value;
1067
+ }
1068
+
1069
+ function mapAgentEventDetails(
1070
+ frame: Record<string, unknown>,
1071
+ map: OptionalDetailsMapper,
1072
+ ): Record<string, unknown> {
1073
+ switch (frame.type) {
1074
+ case "message_start":
1075
+ case "message_update":
1076
+ case "message_end": {
1077
+ const message = frame.message;
1078
+ const taskResult =
1079
+ message !== null &&
1080
+ typeof message === "object" &&
1081
+ !Array.isArray(message) &&
1082
+ (message as Record<string, unknown>).role === "toolResult" &&
1083
+ (message as Record<string, unknown>).toolName === "task";
1084
+ return mapRecordField(frame, "message", map, taskResult);
1085
+ }
1086
+ case "tool_execution_update":
1087
+ return mapRecordField(frame, "partialResult", map, frame.toolName === "task");
1088
+ case "tool_execution_end":
1089
+ return mapRecordField(frame, "result", map, frame.toolName === "task");
1090
+ case "agent_end":
1091
+ return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
1092
+ default:
1093
+ return frame;
1094
+ }
1095
+ }
1096
+
1097
+ function mapRuntimeFrameDetails(
1098
+ frame: Record<string, unknown>,
1099
+ map: OptionalDetailsMapper,
1100
+ ): Record<string, unknown> {
1101
+ if (frame.type !== "subagent_event") return mapAgentEventDetails(frame, map);
1102
+ if (frame.payload === null || typeof frame.payload !== "object" || Array.isArray(frame.payload)) {
1103
+ return frame;
1104
+ }
1105
+ const payload = frame.payload as Record<string, unknown>;
1106
+ if (payload.event === null || typeof payload.event !== "object" || Array.isArray(payload.event)) {
1107
+ return frame;
1108
+ }
1109
+ const event = mapAgentEventDetails(payload.event as Record<string, unknown>, map);
1110
+ return event === payload.event ? frame : { ...frame, payload: { ...payload, event } };
1111
+ }
1112
+
1113
+ function sanitizeMessageListMetadata(value: unknown): unknown {
1114
+ return mapMessageList(value, createOptionalMetadataSanitizer());
1115
+ }
1116
+
1117
+ function sanitizeHistoryResponseData(value: unknown): unknown {
1118
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
1119
+ return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
1120
+ }
1121
+
1122
+ function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
1123
+ const type = frame.type;
1124
+ if (
1125
+ type === "message_start" ||
1126
+ type === "message_update" ||
1127
+ type === "message_end" ||
1128
+ type === "agent_end" ||
1129
+ type === "subagent_event"
1130
+ ) {
1131
+ return OMP_MAX_CONTENT_PARTS;
1132
+ }
1133
+ return 1_024;
1134
+ }
876
1135
  const OmpModelsResultSchema = z.object({
877
1136
  models: z.array(OmpModelSchema).min(1).max(256),
878
1137
  });
@@ -1989,14 +2248,6 @@ class OmpRpcProcess {
1989
2248
  return;
1990
2249
  }
1991
2250
  if (this.receiveKnownResponse(decoded)) return;
1992
- if (this.receiveDegradedAgentEnd(decoded, true)) return;
1993
- if (
1994
- boundedJsonBytes(decoded, MAX_SEMANTIC_FRAME_BYTES, 1_024, MAX_IMAGE_DATA_LENGTH, 4_096) ===
1995
- Number.POSITIVE_INFINITY
1996
- ) {
1997
- this.recordProtocolViolation();
1998
- return;
1999
- }
2000
2251
  const frame = JsonObjectSchema.safeParse(decoded);
2001
2252
  if (!frame.success) {
2002
2253
  this.recordProtocolViolation();
@@ -2073,19 +2324,6 @@ class OmpRpcProcess {
2073
2324
  return;
2074
2325
  }
2075
2326
  if (this.receiveKnownResponse(decodedFrame)) return;
2076
- if (this.receiveDegradedAgentEnd(decodedFrame, true)) return;
2077
- if (
2078
- boundedJsonBytes(
2079
- decodedFrame,
2080
- MAX_SEMANTIC_FRAME_BYTES,
2081
- 1_024,
2082
- MAX_IMAGE_DATA_LENGTH,
2083
- 4_096,
2084
- ) === Number.POSITIVE_INFINITY
2085
- ) {
2086
- this.recordProtocolViolation();
2087
- return;
2088
- }
2089
2327
  const frameObject = JsonObjectSchema.safeParse(decodedFrame);
2090
2328
  if (!frameObject.success) {
2091
2329
  this.recordProtocolViolation();
@@ -2130,6 +2368,11 @@ class OmpRpcProcess {
2130
2368
  const isBranchHistory = pending.command === "get_branch_messages";
2131
2369
  const isHistory =
2132
2370
  pending.command === "get_messages" || pending.command === "get_subagent_messages";
2371
+ const responseData = isHistory
2372
+ ? sanitizeHistoryResponseData(response.data.data)
2373
+ : response.data.data;
2374
+ const boundedFrame =
2375
+ responseData === response.data.data ? frame : { ...frame, data: responseData };
2133
2376
  const responseItemLimit = isBranchHistory ? 1_024 : isHistory ? 100_000 : MAX_ARRAY_ITEMS;
2134
2377
  const responseByteLimit =
2135
2378
  isBranchHistory || isHistory
@@ -2146,7 +2389,7 @@ class OmpRpcProcess {
2146
2389
  : 2_048;
2147
2390
  if (
2148
2391
  boundedJsonBytes(
2149
- frame,
2392
+ boundedFrame,
2150
2393
  responseByteLimit,
2151
2394
  responseItemLimit,
2152
2395
  MAX_IMAGE_DATA_LENGTH,
@@ -2162,7 +2405,7 @@ class OmpRpcProcess {
2162
2405
  if (!settled) return;
2163
2406
  if (response.data.success) {
2164
2407
  try {
2165
- settled.beforeResolve?.(response.data.data);
2408
+ settled.beforeResolve?.(responseData);
2166
2409
  if (settled.command === "prompt") {
2167
2410
  if (this.acceptedPromptIds.size >= MAX_PENDING_REQUESTS) {
2168
2411
  const oldest = this.acceptedPromptIds.values().next().value;
@@ -2170,7 +2413,7 @@ class OmpRpcProcess {
2170
2413
  }
2171
2414
  this.acceptedPromptIds.add(response.data.id);
2172
2415
  }
2173
- settled.resolve(response.data.data);
2416
+ settled.resolve(responseData);
2174
2417
  } catch {
2175
2418
  settled.reject(new Error("OMP RPC response is invalid"));
2176
2419
  }
@@ -2219,21 +2462,27 @@ class OmpRpcProcess {
2219
2462
  this.fail(new Error("OMP emitted invalid terminal metadata"));
2220
2463
  return true;
2221
2464
  }
2465
+ const structuralFrame = mapRuntimeFrameDetails(frame, omitOptionalDetails);
2222
2466
  const messagesAreSafe =
2223
2467
  frame.messages === undefined ||
2224
2468
  (Array.isArray(frame.messages) &&
2225
2469
  frame.messages.length <= MAX_ARRAY_ITEMS &&
2226
2470
  boundedJsonBytes(
2227
- frame.messages,
2471
+ structuralFrame.messages as unknown[],
2228
2472
  MAX_SEMANTIC_FRAME_BYTES,
2229
- MAX_ARRAY_ITEMS,
2473
+ OMP_MAX_CONTENT_PARTS,
2230
2474
  MAX_TEXT_LENGTH,
2231
2475
  4_096,
2232
2476
  ) !== Number.POSITIVE_INFINITY);
2233
2477
  const payloadIsSafe =
2234
2478
  messagesAreSafe &&
2235
- boundedJsonBytes(frame, MAX_SEMANTIC_FRAME_BYTES, 1_024, MAX_IMAGE_DATA_LENGTH, 4_096) !==
2236
- Number.POSITIVE_INFINITY;
2479
+ boundedJsonBytes(
2480
+ structuralFrame,
2481
+ MAX_SEMANTIC_FRAME_BYTES,
2482
+ OMP_MAX_CONTENT_PARTS,
2483
+ MAX_IMAGE_DATA_LENGTH,
2484
+ 4_096,
2485
+ ) !== Number.POSITIVE_INFINITY;
2237
2486
  if (onlyUnsafePayload && payloadIsSafe) return false;
2238
2487
  if (envelope.data.isTerminal === false) {
2239
2488
  this.fail(new Error("OMP emitted an invalid nonterminal agent_end payload"));
@@ -2260,16 +2509,22 @@ class OmpRpcProcess {
2260
2509
  this.recordProtocolViolation();
2261
2510
  return;
2262
2511
  }
2263
- if (this.receiveDegradedAgentEnd(frame, true)) return;
2512
+ const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
2513
+ if (this.receiveDegradedAgentEnd(safeFrame, true)) return;
2264
2514
  if (
2265
- boundedJsonBytes(frame, MAX_SEMANTIC_FRAME_BYTES, 1_024, MAX_IMAGE_DATA_LENGTH, 4_096) ===
2266
- Number.POSITIVE_INFINITY
2515
+ boundedJsonBytes(
2516
+ mapRuntimeFrameDetails(safeFrame, omitOptionalDetails),
2517
+ MAX_SEMANTIC_FRAME_BYTES,
2518
+ runtimeFrameCollectionLimit(safeFrame),
2519
+ MAX_IMAGE_DATA_LENGTH,
2520
+ 4_096,
2521
+ ) === Number.POSITIVE_INFINITY
2267
2522
  ) {
2268
2523
  this.recordProtocolViolation();
2269
2524
  return;
2270
2525
  }
2271
2526
  if (type === "rpc_chunk") {
2272
- const chunk = OmpChunkFrameSchema.safeParse(frame);
2527
+ const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
2273
2528
  if (!chunk.success) this.rejectChunk();
2274
2529
  else this.receiveChunk(chunk.data);
2275
2530
  return;
@@ -2287,7 +2542,7 @@ class OmpRpcProcess {
2287
2542
  this.recordProtocolViolation();
2288
2543
  return;
2289
2544
  }
2290
- const ready = OmpReadyFrameSchema.safeParse(frame);
2545
+ const ready = OmpReadyFrameSchema.safeParse(safeFrame);
2291
2546
  if (!ready.success) {
2292
2547
  this.recordProtocolViolation();
2293
2548
  } else {
@@ -2297,13 +2552,13 @@ class OmpRpcProcess {
2297
2552
  return;
2298
2553
  }
2299
2554
  if (type === "response") {
2300
- this.receiveResponse(frame);
2555
+ this.receiveResponse(safeFrame);
2301
2556
  return;
2302
2557
  }
2303
- const event = OmpRuntimeEventSchema.safeParse(frame);
2558
+ const event = OmpRuntimeEventSchema.safeParse(safeFrame);
2304
2559
  if (!event.success) {
2305
- this.rejectMatchingToolApproval(frame);
2306
- if (type === "agent_end" && this.receiveDegradedAgentEnd(frame, false)) return;
2560
+ this.rejectMatchingToolApproval(safeFrame);
2561
+ if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
2307
2562
  this.recordProtocolViolation();
2308
2563
  return;
2309
2564
  }
@@ -2728,7 +2983,10 @@ export class OmpRpcRuntime implements OmpRuntime {
2728
2983
  );
2729
2984
  return {
2730
2985
  ...transcript,
2731
- messages: z.array(OmpMessageSchema).max(100_000).parse(transcript.messages),
2986
+ messages: z
2987
+ .array(OmpMessageSchema)
2988
+ .max(100_000)
2989
+ .parse(sanitizeMessageListMetadata(transcript.messages)),
2732
2990
  };
2733
2991
  }
2734
2992
  async readPersistedSubagentTranscript(options: {
@@ -2745,7 +3003,10 @@ export class OmpRpcRuntime implements OmpRuntime {
2745
3003
  );
2746
3004
  return {
2747
3005
  ...transcript,
2748
- messages: z.array(OmpMessageSchema).max(100_000).parse(transcript.messages),
3006
+ messages: z
3007
+ .array(OmpMessageSchema)
3008
+ .max(100_000)
3009
+ .parse(sanitizeMessageListMetadata(transcript.messages)),
2749
3010
  };
2750
3011
  }
2751
3012
 
@@ -94,6 +94,18 @@ const TaskResultDetailsSchema = z.object({
94
94
  exitCode: z.number().optional(),
95
95
  error: z.unknown().optional(),
96
96
  aborted: z.boolean().optional(),
97
+ status: z
98
+ .enum([
99
+ "pending",
100
+ "running",
101
+ "completed",
102
+ "failed",
103
+ "error",
104
+ "aborted",
105
+ "canceled",
106
+ "cancelled",
107
+ ])
108
+ .optional(),
97
109
  }),
98
110
  )
99
111
  .max(MAX_CHILDREN),
@@ -192,8 +204,15 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
192
204
  const details = taskResultDetails(message);
193
205
  const results = details?.results ?? [];
194
206
  for (const result of results) {
207
+ const canceled =
208
+ result.aborted === true ||
209
+ result.status === "aborted" ||
210
+ result.status === "canceled" ||
211
+ result.status === "cancelled";
195
212
  const failed =
196
213
  message.isError === true ||
214
+ result.status === "failed" ||
215
+ result.status === "error" ||
197
216
  Boolean(result.error) ||
198
217
  (typeof result.exitCode === "number" && result.exitCode !== 0);
199
218
  children.push({
@@ -201,7 +220,7 @@ function replayChildren(messages: readonly OmpMessage[]): ReplayChildRef[] {
201
220
  agent: result.agent ?? call?.title,
202
221
  description: call?.description,
203
222
  parentToolCallId: message.toolCallId,
204
- status: result.aborted === true ? "canceled" : failed ? "failed" : "completed",
223
+ status: canceled ? "canceled" : failed ? "failed" : "completed",
205
224
  });
206
225
  }
207
226
  const resultIds = new Set(results.map((result) => result.id));