@omercnet/paseo-omp 0.2.1 → 0.3.0-next.101.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/CHANGELOG.md +26 -0
- package/README.md +25 -13
- package/SUPPORT.md +7 -3
- package/TESTING.md +21 -18
- package/client/composer-pill-settings.tsx +157 -0
- package/client/external-url.ts +15 -0
- package/client/mcp-authorization.tsx +169 -0
- package/client/mcp-popover.tsx +155 -0
- package/client/memory-panel.tsx +8 -3
- package/client/memory-popover.tsx +8 -4
- package/client/omp-config-surface.tsx +189 -29
- package/client/omp-plugin-manager.tsx +302 -131
- package/client/omp-store-picker.tsx +89 -0
- package/client/omp-store-state.ts +45 -0
- package/client/paseo-types.ts +9 -0
- package/client/provider-diagnostics-state.ts +18 -7
- package/client/quota-popover.tsx +8 -3
- package/client/quota-state.ts +16 -7
- package/client/sessions-popover.tsx +8 -3
- package/docs/alpha-release-checklist.md +6 -8
- package/docs/configuration.md +8 -4
- package/docs/core-provider-issue-audit.md +3 -2
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +35 -19
- package/index.client.tsx +339 -123
- package/index.server.ts +44 -14
- package/package.json +7 -8
- package/paseo-plugin.json +2 -2
- package/scripts/prepare-dependencies.mjs +24 -0
- package/server/mcp-browser.ts +95 -0
- package/server/memory.ts +2 -2
- package/server/omp-config.ts +16 -7
- package/server/omp-plugins.ts +70 -21
- package/server/omp-settings.ts +232 -24
- package/server/paths.ts +128 -11
- package/server/provider/catalog.ts +3 -4
- package/server/provider/connection.ts +248 -17
- package/server/provider/host-tools.ts +294 -26
- package/server/provider/omp-rpc.ts +499 -72
- package/server/provider/profile-providers.ts +249 -0
- package/server/provider/registration.ts +11 -0
- package/server/provider/security.ts +8 -10
- package/server/provider/session-descriptors.ts +340 -1
- package/server/provider/session.ts +716 -249
- package/server/provider/subsessions.ts +25 -2
- package/server/provider/timeline-projector.ts +104 -44
- package/server/provider-diagnostics.ts +122 -36
- package/server/quota.ts +3 -2
- package/server/sessions.ts +2 -2
- package/shared/composer-pill-settings.ts +28 -0
- package/shared/external-url.ts +21 -0
- package/shared/hub.ts +3 -3
- package/shared/mcp.ts +47 -0
- package/shared/memory.ts +2 -1
- package/shared/omp-config.ts +5 -1
- package/shared/omp-plugins.ts +74 -33
- package/shared/omp-settings.ts +8 -1
- package/shared/omp-store.ts +58 -0
- package/shared/provider-diagnostics.ts +12 -3
- package/shared/quota.ts +2 -1
- package/shared/sessions.ts +2 -1
|
@@ -2,12 +2,20 @@ import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { isAbsolute, join } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { ompDataDir } from "../paths";
|
|
5
6
|
import { isValidImagePayload } from "./image";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
boundedJsonBytes,
|
|
9
|
+
boundedJsonMetrics,
|
|
10
|
+
OmpCleanupFailure,
|
|
11
|
+
OmpPublicError,
|
|
12
|
+
utf8Bytes,
|
|
13
|
+
} from "./security";
|
|
7
14
|
import {
|
|
8
15
|
listOmpSessionDescriptors,
|
|
9
16
|
type OmpSessionDescriptor,
|
|
10
17
|
type OmpSessionListOptions,
|
|
18
|
+
readOmpPersistedSessionTranscript,
|
|
11
19
|
readOmpPersistedSubagentTranscript,
|
|
12
20
|
validateNativeSessionId,
|
|
13
21
|
} from "./session-descriptors";
|
|
@@ -54,7 +62,17 @@ const MAX_PENDING_ONE_WAY_WRITES = 256;
|
|
|
54
62
|
const MAX_PENDING_WRITE_BYTES = 8 * 1024 * 1024;
|
|
55
63
|
const MAX_LINE_PARTS = 4_096;
|
|
56
64
|
const MAX_ARRAY_ITEMS = 512;
|
|
57
|
-
|
|
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;
|
|
73
|
+
// Tool-intensive OMP turns legitimately exceed 64 blocks; transport byte/node budgets remain the
|
|
74
|
+
// primary resource bounds.
|
|
75
|
+
export const OMP_MAX_CONTENT_PARTS = 4_096;
|
|
58
76
|
const MAX_TODOS = 256;
|
|
59
77
|
const MAX_ENV_ENTRIES = 256;
|
|
60
78
|
const MAX_ENV_VALUE_LENGTH = 64 * 1024;
|
|
@@ -63,6 +81,9 @@ const MAX_PATH_LENGTH = 4_096;
|
|
|
63
81
|
const WINDOWS_DEFAULT_SYSTEM_ROOT = "C:\\Windows";
|
|
64
82
|
const MAX_TOKEN_COUNT = Number.MAX_SAFE_INTEGER;
|
|
65
83
|
const MAX_COST_USD = 1_000_000_000;
|
|
84
|
+
const MAX_RPC_ERROR_BYTES = 4_096;
|
|
85
|
+
const MAX_RPC_ERROR_CODE_BYTES = 256;
|
|
86
|
+
const PROMPT_SCHEDULING_FAILURE = "OMP prompt scheduling failed";
|
|
66
87
|
const MAX_CONTEXT_PERCENT = 1_000_000;
|
|
67
88
|
function boundedJsonString(maxBytes: number, minBytes = 0) {
|
|
68
89
|
return z.string().refine((value) => {
|
|
@@ -99,6 +120,76 @@ const IDENTIFIER = boundedString(MAX_ID_LENGTH, 1);
|
|
|
99
120
|
const NAME = boundedString(MAX_NAME_LENGTH, 1);
|
|
100
121
|
const OMP_PROVIDER_NAME = NAME.refine((provider) => !provider.includes("/"));
|
|
101
122
|
const TEXT = boundedString(MAX_TEXT_LENGTH);
|
|
123
|
+
const RAW_DISPLAY_TEXT = boundedString(MAX_IMAGE_DATA_LENGTH);
|
|
124
|
+
const DISPLAY_TRUNCATION_MARKER = "<truncated>";
|
|
125
|
+
|
|
126
|
+
function boundRawDisplayContent(value: unknown): unknown {
|
|
127
|
+
if (typeof value === "string") {
|
|
128
|
+
return utf8Bytes(value) <= MAX_IMAGE_DATA_LENGTH ? value : DISPLAY_TRUNCATION_MARKER;
|
|
129
|
+
}
|
|
130
|
+
if (!Array.isArray(value)) return value;
|
|
131
|
+
let totalBytes = 0;
|
|
132
|
+
for (const part of value) {
|
|
133
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) continue;
|
|
134
|
+
const record = part as Record<string, unknown>;
|
|
135
|
+
if (typeof record.text === "string") totalBytes += utf8Bytes(record.text);
|
|
136
|
+
if (typeof record.thinking === "string") totalBytes += utf8Bytes(record.thinking);
|
|
137
|
+
}
|
|
138
|
+
if (totalBytes <= MAX_IMAGE_DATA_LENGTH) return value;
|
|
139
|
+
|
|
140
|
+
let retainedMarker = false;
|
|
141
|
+
return value.map((part) => {
|
|
142
|
+
if (!part || typeof part !== "object" || Array.isArray(part)) return part;
|
|
143
|
+
const copy = { ...(part as Record<string, unknown>) };
|
|
144
|
+
for (const key of ["text", "thinking"] as const) {
|
|
145
|
+
if (typeof copy[key] !== "string") continue;
|
|
146
|
+
if (retainedMarker) delete copy[key];
|
|
147
|
+
else {
|
|
148
|
+
copy[key] = DISPLAY_TRUNCATION_MARKER;
|
|
149
|
+
retainedMarker = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return copy;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function sanitizeLiveMessageDisplay(value: unknown): unknown {
|
|
157
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
158
|
+
const message = value as Record<string, unknown>;
|
|
159
|
+
if (message.role === "assistant") {
|
|
160
|
+
const content = boundRawDisplayContent(message.content);
|
|
161
|
+
return content === message.content ? value : { ...message, content };
|
|
162
|
+
}
|
|
163
|
+
if (
|
|
164
|
+
message.role === "bashExecution" &&
|
|
165
|
+
typeof message.output === "string" &&
|
|
166
|
+
utf8Bytes(message.output) > MAX_IMAGE_DATA_LENGTH
|
|
167
|
+
) {
|
|
168
|
+
return { ...message, output: DISPLAY_TRUNCATION_MARKER };
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sanitizeLiveDisplayFrame(value: unknown): unknown {
|
|
174
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
175
|
+
const frame = value as Record<string, unknown>;
|
|
176
|
+
if (
|
|
177
|
+
frame.type === "message_start" ||
|
|
178
|
+
frame.type === "message_update" ||
|
|
179
|
+
frame.type === "message_end"
|
|
180
|
+
) {
|
|
181
|
+
const message = sanitizeLiveMessageDisplay(frame.message);
|
|
182
|
+
return message === frame.message ? value : { ...frame, message };
|
|
183
|
+
}
|
|
184
|
+
if (frame.type !== "agent_end" || !Array.isArray(frame.messages)) return value;
|
|
185
|
+
let changed = false;
|
|
186
|
+
const messages = frame.messages.map((message) => {
|
|
187
|
+
const sanitized = sanitizeLiveMessageDisplay(message);
|
|
188
|
+
if (sanitized !== message) changed = true;
|
|
189
|
+
return sanitized;
|
|
190
|
+
});
|
|
191
|
+
return changed ? { ...frame, messages } : value;
|
|
192
|
+
}
|
|
102
193
|
const OmpThinkingLevelSchema = z.enum(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
103
194
|
|
|
104
195
|
function isBoundedJson(
|
|
@@ -111,6 +202,148 @@ function isBoundedJson(
|
|
|
111
202
|
boundedJsonBytes(value, maxBytes, maxItems, maxBytes, maxNodes) !== Number.POSITIVE_INFINITY
|
|
112
203
|
);
|
|
113
204
|
}
|
|
205
|
+
function optionalMetadataMetrics(value: unknown) {
|
|
206
|
+
return boundedJsonMetrics(
|
|
207
|
+
value,
|
|
208
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
209
|
+
MAX_OPTIONAL_METADATA_ITEMS,
|
|
210
|
+
MAX_OPTIONAL_METADATA_BYTES,
|
|
211
|
+
MAX_OPTIONAL_METADATA_NODES,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function optionalMetadataIsBounded(value: unknown): boolean {
|
|
216
|
+
return optionalMetadataMetrics(value) !== undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function omitUnsafeOptionalDetails(value: unknown): unknown {
|
|
220
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
221
|
+
const record = value as Record<string, unknown>;
|
|
222
|
+
if (!Object.hasOwn(record, "details") || optionalMetadataIsBounded(record.details)) return value;
|
|
223
|
+
const { details: _details, ...safe } = record;
|
|
224
|
+
return safe;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const TASK_RESULT_STATUSES: Readonly<Record<string, true>> = {
|
|
228
|
+
pending: true,
|
|
229
|
+
running: true,
|
|
230
|
+
completed: true,
|
|
231
|
+
failed: true,
|
|
232
|
+
error: true,
|
|
233
|
+
aborted: true,
|
|
234
|
+
canceled: true,
|
|
235
|
+
cancelled: true,
|
|
236
|
+
};
|
|
237
|
+
const TASK_PROGRESS_STATUSES: Readonly<Record<string, true>> = {
|
|
238
|
+
pending: true,
|
|
239
|
+
running: true,
|
|
240
|
+
completed: true,
|
|
241
|
+
failed: true,
|
|
242
|
+
aborted: true,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
function boundedTaskId(value: unknown): value is string {
|
|
246
|
+
return typeof value === "string" && value.length > 0 && utf8Bytes(value) <= MAX_ID_LENGTH;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function taskCorrelationDetails(value: unknown): unknown {
|
|
250
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
251
|
+
const details = value as Record<string, unknown>;
|
|
252
|
+
if (!Array.isArray(details.results) || details.results.length > MAX_TASK_CORRELATION_ITEMS)
|
|
253
|
+
return;
|
|
254
|
+
const results: Record<string, unknown>[] = [];
|
|
255
|
+
for (const value of details.results) {
|
|
256
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
257
|
+
const result = value as Record<string, unknown>;
|
|
258
|
+
if (!boundedTaskId(result.id)) return;
|
|
259
|
+
const safe: Record<string, unknown> = { id: result.id };
|
|
260
|
+
if (typeof result.status === "string" && Object.hasOwn(TASK_RESULT_STATUSES, result.status)) {
|
|
261
|
+
safe.status = result.status;
|
|
262
|
+
}
|
|
263
|
+
if (typeof result.aborted === "boolean") safe.aborted = result.aborted;
|
|
264
|
+
if (typeof result.exitCode === "number" && Number.isFinite(result.exitCode)) {
|
|
265
|
+
safe.exitCode = result.exitCode;
|
|
266
|
+
}
|
|
267
|
+
if (
|
|
268
|
+
result.error !== undefined &&
|
|
269
|
+
boundedJsonMetrics(result.error, 4_096, 32, 4_096, 64) !== undefined
|
|
270
|
+
) {
|
|
271
|
+
safe.error = result.error;
|
|
272
|
+
}
|
|
273
|
+
results.push(safe);
|
|
274
|
+
}
|
|
275
|
+
let progress: Record<string, unknown>[] | undefined;
|
|
276
|
+
if (details.progress !== undefined) {
|
|
277
|
+
if (!Array.isArray(details.progress) || details.progress.length > MAX_TASK_CORRELATION_ITEMS) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
progress = [];
|
|
281
|
+
for (const value of details.progress) {
|
|
282
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return;
|
|
283
|
+
const item = value as Record<string, unknown>;
|
|
284
|
+
if (
|
|
285
|
+
!boundedTaskId(item.id) ||
|
|
286
|
+
typeof item.index !== "number" ||
|
|
287
|
+
!Number.isInteger(item.index) ||
|
|
288
|
+
item.index < 0 ||
|
|
289
|
+
item.index >= MAX_TASK_CORRELATION_ITEMS ||
|
|
290
|
+
typeof item.status !== "string" ||
|
|
291
|
+
!Object.hasOwn(TASK_PROGRESS_STATUSES, item.status)
|
|
292
|
+
) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
progress.push({ id: item.id, index: item.index, status: item.status });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const correlation = { results, ...(progress ? { progress } : {}) };
|
|
299
|
+
return boundedJsonMetrics(
|
|
300
|
+
correlation,
|
|
301
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
302
|
+
MAX_TASK_CORRELATION_ITEMS,
|
|
303
|
+
MAX_TASK_CORRELATION_BYTES,
|
|
304
|
+
MAX_TASK_CORRELATION_NODES,
|
|
305
|
+
)
|
|
306
|
+
? correlation
|
|
307
|
+
: undefined;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function omitOptionalDetails(value: unknown, preserveTaskCorrelation = false): unknown {
|
|
311
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
312
|
+
const record = value as Record<string, unknown>;
|
|
313
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
314
|
+
const { details: _details, ...structural } = record;
|
|
315
|
+
if (!preserveTaskCorrelation) return structural;
|
|
316
|
+
const correlation = taskCorrelationDetails(record.details);
|
|
317
|
+
return correlation === undefined ? structural : { ...structural, details: correlation };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function createOptionalMetadataSanitizer(): (value: unknown, taskResult?: boolean) => unknown {
|
|
321
|
+
let retainedBytes = 0;
|
|
322
|
+
let retainedNodes = 0;
|
|
323
|
+
return (value, taskResult = false) => {
|
|
324
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
325
|
+
const record = value as Record<string, unknown>;
|
|
326
|
+
if (!Object.hasOwn(record, "details")) return value;
|
|
327
|
+
const metrics = optionalMetadataMetrics(record.details);
|
|
328
|
+
if (
|
|
329
|
+
metrics &&
|
|
330
|
+
retainedBytes + metrics.bytes <= MAX_OPTIONAL_METADATA_BYTES &&
|
|
331
|
+
retainedNodes + metrics.nodes <= MAX_OPTIONAL_METADATA_NODES
|
|
332
|
+
) {
|
|
333
|
+
retainedBytes += metrics.bytes;
|
|
334
|
+
retainedNodes += metrics.nodes;
|
|
335
|
+
return value;
|
|
336
|
+
}
|
|
337
|
+
const correlation = taskResult ? taskCorrelationDetails(record.details) : undefined;
|
|
338
|
+
const { details: _details, ...safe } = record;
|
|
339
|
+
return correlation === undefined ? safe : { ...safe, details: correlation };
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const OmpOptionalMetadataSchema = z.preprocess(
|
|
344
|
+
(value) => (optionalMetadataIsBounded(value) ? value : undefined),
|
|
345
|
+
z.unknown().optional(),
|
|
346
|
+
);
|
|
114
347
|
|
|
115
348
|
const OmpContentPartSchema = z
|
|
116
349
|
.object({
|
|
@@ -140,13 +373,21 @@ const OmpContentPartSchema = z
|
|
|
140
373
|
context.addIssue({ code: "custom", message: "invalid image payload" });
|
|
141
374
|
}
|
|
142
375
|
});
|
|
376
|
+
const OmpAssistantContentPartSchema = OmpContentPartSchema.safeExtend({
|
|
377
|
+
text: RAW_DISPLAY_TEXT.optional(),
|
|
378
|
+
thinking: RAW_DISPLAY_TEXT.optional(),
|
|
379
|
+
});
|
|
380
|
+
const OmpAssistantDisplayContentSchema = z.preprocess(
|
|
381
|
+
boundRawDisplayContent,
|
|
382
|
+
z.union([RAW_DISPLAY_TEXT, z.array(OmpAssistantContentPartSchema).max(OMP_MAX_CONTENT_PARTS)]),
|
|
383
|
+
);
|
|
143
384
|
const OmpDisplayContentSchema = z.union([
|
|
144
385
|
TEXT,
|
|
145
|
-
z.array(OmpContentPartSchema).max(
|
|
386
|
+
z.array(OmpContentPartSchema).max(OMP_MAX_CONTENT_PARTS),
|
|
146
387
|
]);
|
|
147
388
|
const OmpImageArraySchema = z
|
|
148
389
|
.array(OmpContentPartSchema)
|
|
149
|
-
.max(
|
|
390
|
+
.max(OMP_MAX_CONTENT_PARTS)
|
|
150
391
|
.superRefine((parts, context) => {
|
|
151
392
|
if (parts.some((part) => part.type !== "image")) {
|
|
152
393
|
context.addIssue({ code: "custom", message: "invalid image collection" });
|
|
@@ -158,10 +399,7 @@ const OmpMessageIdentityShape = {
|
|
|
158
399
|
responseId: IDENTIFIER.optional(),
|
|
159
400
|
images: OmpImageArraySchema.optional(),
|
|
160
401
|
timestamp: z.number().finite().optional(),
|
|
161
|
-
details:
|
|
162
|
-
.unknown()
|
|
163
|
-
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096))
|
|
164
|
-
.optional(),
|
|
402
|
+
details: OmpOptionalMetadataSchema,
|
|
165
403
|
};
|
|
166
404
|
type OmpContentPart = z.infer<typeof OmpContentPartSchema>;
|
|
167
405
|
type OmpMessageIdentity = {
|
|
@@ -211,7 +449,7 @@ export type OmpMessage = OmpMessageIdentity &
|
|
|
211
449
|
const OmpMessageSchema: z.ZodType<OmpMessage> = z.union([
|
|
212
450
|
z.object({
|
|
213
451
|
role: z.literal("assistant"),
|
|
214
|
-
content:
|
|
452
|
+
content: OmpAssistantDisplayContentSchema.optional(),
|
|
215
453
|
...OmpMessageIdentityShape,
|
|
216
454
|
errorMessage: boundedString(4_096).nullable().optional(),
|
|
217
455
|
stopReason: boundedString(64).optional(),
|
|
@@ -234,7 +472,13 @@ const OmpMessageSchema: z.ZodType<OmpMessage> = z.union([
|
|
|
234
472
|
z.object({
|
|
235
473
|
role: z.literal("bashExecution"),
|
|
236
474
|
command: TEXT,
|
|
237
|
-
output:
|
|
475
|
+
output: z.preprocess(
|
|
476
|
+
(value) =>
|
|
477
|
+
typeof value === "string" && utf8Bytes(value) > MAX_IMAGE_DATA_LENGTH
|
|
478
|
+
? DISPLAY_TRUNCATION_MARKER
|
|
479
|
+
: value,
|
|
480
|
+
RAW_DISPLAY_TEXT.optional(),
|
|
481
|
+
),
|
|
238
482
|
exitCode: z.number().int().nullable().optional(),
|
|
239
483
|
cancelled: z.boolean().optional(),
|
|
240
484
|
truncated: z.boolean().optional(),
|
|
@@ -259,7 +503,7 @@ const OmpAssistantMessageEventSchema = z
|
|
|
259
503
|
.number()
|
|
260
504
|
.int()
|
|
261
505
|
.nonnegative()
|
|
262
|
-
.max(
|
|
506
|
+
.max(OMP_MAX_CONTENT_PARTS - 1)
|
|
263
507
|
.optional(),
|
|
264
508
|
delta: TEXT.optional(),
|
|
265
509
|
content: z
|
|
@@ -387,7 +631,8 @@ const OmpResponseFrameSchema = z.object({
|
|
|
387
631
|
id: IDENTIFIER,
|
|
388
632
|
success: z.boolean(),
|
|
389
633
|
data: z.unknown().optional(),
|
|
390
|
-
error: boundedString(
|
|
634
|
+
error: boundedString(MAX_RPC_ERROR_BYTES).optional(),
|
|
635
|
+
code: boundedString(MAX_RPC_ERROR_CODE_BYTES, 1).optional(),
|
|
391
636
|
});
|
|
392
637
|
const OmpChunkFrameSchema = z.object({
|
|
393
638
|
type: z.literal("rpc_chunk"),
|
|
@@ -401,6 +646,16 @@ const JsonObjectSchema = z.record(z.string(), z.unknown());
|
|
|
401
646
|
const BoundedToolPayloadSchema = z
|
|
402
647
|
.unknown()
|
|
403
648
|
.refine((value) => isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096));
|
|
649
|
+
const OmpToolResultPayloadSchema = z.preprocess(
|
|
650
|
+
omitUnsafeOptionalDetails,
|
|
651
|
+
z
|
|
652
|
+
.unknown()
|
|
653
|
+
.refine(
|
|
654
|
+
(value) =>
|
|
655
|
+
isBoundedJson(value, MAX_SEMANTIC_FRAME_BYTES, MAX_OPTIONAL_METADATA_ITEMS, 8_192) &&
|
|
656
|
+
isBoundedJson(omitOptionalDetails(value), MAX_SEMANTIC_FRAME_BYTES, 1_024, 4_096),
|
|
657
|
+
),
|
|
658
|
+
);
|
|
404
659
|
const OmpHostToolDefinitionSchema = z.object({
|
|
405
660
|
name: NAME,
|
|
406
661
|
label: NAME.optional(),
|
|
@@ -562,6 +817,7 @@ const OmpToolApprovalResponseSchema = z.union([
|
|
|
562
817
|
]);
|
|
563
818
|
const OmpAgentEndEnvelopeSchema = z.object({
|
|
564
819
|
type: z.literal("agent_end"),
|
|
820
|
+
requestId: IDENTIFIER.optional(),
|
|
565
821
|
messageCount: z.number().int().nonnegative().optional(),
|
|
566
822
|
isTerminal: z.boolean().optional(),
|
|
567
823
|
});
|
|
@@ -582,6 +838,7 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
582
838
|
z.object({ type: z.literal("agent_start") }),
|
|
583
839
|
z.object({
|
|
584
840
|
type: z.literal("agent_end"),
|
|
841
|
+
requestId: IDENTIFIER.optional(),
|
|
585
842
|
messages: z.array(OmpMessageSchema).max(MAX_ARRAY_ITEMS).optional(),
|
|
586
843
|
messageCount: z.number().int().nonnegative().optional(),
|
|
587
844
|
isTerminal: z.boolean().optional(),
|
|
@@ -606,13 +863,13 @@ const OmpAgentSessionEventSchema = z.discriminatedUnion("type", [
|
|
|
606
863
|
toolCallId: IDENTIFIER,
|
|
607
864
|
toolName: NAME,
|
|
608
865
|
args: BoundedToolPayloadSchema.optional(),
|
|
609
|
-
partialResult:
|
|
866
|
+
partialResult: OmpToolResultPayloadSchema,
|
|
610
867
|
}),
|
|
611
868
|
z.object({
|
|
612
869
|
type: z.literal("tool_execution_end"),
|
|
613
870
|
toolCallId: IDENTIFIER,
|
|
614
871
|
toolName: NAME,
|
|
615
|
-
result:
|
|
872
|
+
result: OmpToolResultPayloadSchema,
|
|
616
873
|
isError: z.boolean().optional(),
|
|
617
874
|
}),
|
|
618
875
|
OmpCompactionStartSchema,
|
|
@@ -863,6 +1120,102 @@ const OmpRuntimeEventSchema = z.discriminatedUnion("type", [
|
|
|
863
1120
|
OmpToolApprovalCancelSchema,
|
|
864
1121
|
z.object({ type: z.literal("advisor_yielded") }),
|
|
865
1122
|
]);
|
|
1123
|
+
type OptionalDetailsMapper = (value: unknown, taskResult?: boolean) => unknown;
|
|
1124
|
+
|
|
1125
|
+
function mapRecordField(
|
|
1126
|
+
record: Record<string, unknown>,
|
|
1127
|
+
key: string,
|
|
1128
|
+
map: OptionalDetailsMapper,
|
|
1129
|
+
taskResult = false,
|
|
1130
|
+
): Record<string, unknown> {
|
|
1131
|
+
if (!Object.hasOwn(record, key)) return record;
|
|
1132
|
+
const next = map(record[key], taskResult);
|
|
1133
|
+
return next === record[key] ? record : { ...record, [key]: next };
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function mapMessageList(value: unknown, map: OptionalDetailsMapper): unknown {
|
|
1137
|
+
if (!Array.isArray(value)) return value;
|
|
1138
|
+
let changed = false;
|
|
1139
|
+
const messages = value.map((message) => {
|
|
1140
|
+
const taskResult =
|
|
1141
|
+
message !== null &&
|
|
1142
|
+
typeof message === "object" &&
|
|
1143
|
+
!Array.isArray(message) &&
|
|
1144
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1145
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1146
|
+
const next = map(message, taskResult);
|
|
1147
|
+
changed ||= next !== message;
|
|
1148
|
+
return next;
|
|
1149
|
+
});
|
|
1150
|
+
return changed ? messages : value;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function mapAgentEventDetails(
|
|
1154
|
+
frame: Record<string, unknown>,
|
|
1155
|
+
map: OptionalDetailsMapper,
|
|
1156
|
+
): Record<string, unknown> {
|
|
1157
|
+
switch (frame.type) {
|
|
1158
|
+
case "message_start":
|
|
1159
|
+
case "message_update":
|
|
1160
|
+
case "message_end": {
|
|
1161
|
+
const message = frame.message;
|
|
1162
|
+
const taskResult =
|
|
1163
|
+
message !== null &&
|
|
1164
|
+
typeof message === "object" &&
|
|
1165
|
+
!Array.isArray(message) &&
|
|
1166
|
+
(message as Record<string, unknown>).role === "toolResult" &&
|
|
1167
|
+
(message as Record<string, unknown>).toolName === "task";
|
|
1168
|
+
return mapRecordField(frame, "message", map, taskResult);
|
|
1169
|
+
}
|
|
1170
|
+
case "tool_execution_update":
|
|
1171
|
+
return mapRecordField(frame, "partialResult", map, frame.toolName === "task");
|
|
1172
|
+
case "tool_execution_end":
|
|
1173
|
+
return mapRecordField(frame, "result", map, frame.toolName === "task");
|
|
1174
|
+
case "agent_end":
|
|
1175
|
+
return mapRecordField(frame, "messages", (messages) => mapMessageList(messages, map));
|
|
1176
|
+
default:
|
|
1177
|
+
return frame;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
function mapRuntimeFrameDetails(
|
|
1182
|
+
frame: Record<string, unknown>,
|
|
1183
|
+
map: OptionalDetailsMapper,
|
|
1184
|
+
): Record<string, unknown> {
|
|
1185
|
+
if (frame.type !== "subagent_event") return mapAgentEventDetails(frame, map);
|
|
1186
|
+
if (frame.payload === null || typeof frame.payload !== "object" || Array.isArray(frame.payload)) {
|
|
1187
|
+
return frame;
|
|
1188
|
+
}
|
|
1189
|
+
const payload = frame.payload as Record<string, unknown>;
|
|
1190
|
+
if (payload.event === null || typeof payload.event !== "object" || Array.isArray(payload.event)) {
|
|
1191
|
+
return frame;
|
|
1192
|
+
}
|
|
1193
|
+
const event = mapAgentEventDetails(payload.event as Record<string, unknown>, map);
|
|
1194
|
+
return event === payload.event ? frame : { ...frame, payload: { ...payload, event } };
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
function sanitizeMessageListMetadata(value: unknown): unknown {
|
|
1198
|
+
return mapMessageList(value, createOptionalMetadataSanitizer());
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function sanitizeHistoryResponseData(value: unknown): unknown {
|
|
1202
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1203
|
+
return mapRecordField(value as Record<string, unknown>, "messages", sanitizeMessageListMetadata);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function runtimeFrameCollectionLimit(frame: Record<string, unknown>): number {
|
|
1207
|
+
const type = frame.type;
|
|
1208
|
+
if (
|
|
1209
|
+
type === "message_start" ||
|
|
1210
|
+
type === "message_update" ||
|
|
1211
|
+
type === "message_end" ||
|
|
1212
|
+
type === "agent_end" ||
|
|
1213
|
+
type === "subagent_event"
|
|
1214
|
+
) {
|
|
1215
|
+
return OMP_MAX_CONTENT_PARTS;
|
|
1216
|
+
}
|
|
1217
|
+
return 1_024;
|
|
1218
|
+
}
|
|
866
1219
|
const OmpModelsResultSchema = z.object({
|
|
867
1220
|
models: z.array(OmpModelSchema).min(1).max(256),
|
|
868
1221
|
});
|
|
@@ -924,7 +1277,7 @@ export function parseOmpHostToolAgentResult(value: unknown): OmpHostToolResult["
|
|
|
924
1277
|
}
|
|
925
1278
|
export type OmpRpcEvent =
|
|
926
1279
|
| z.infer<typeof OmpRuntimeEventSchema>
|
|
927
|
-
| { type: "prompt_error"; id: string; error: string }
|
|
1280
|
+
| { type: "prompt_error"; id: string; error: string; code?: string }
|
|
928
1281
|
| { type: "process_exit"; error: string };
|
|
929
1282
|
export type OmpAgentSessionEvent = z.infer<typeof OmpAgentSessionEventSchema>;
|
|
930
1283
|
export type OmpSubagentSnapshot = z.infer<typeof OmpSubagentsResultSchema>["subagents"][number];
|
|
@@ -945,6 +1298,13 @@ export interface OmpPersistedSubagentMessages {
|
|
|
945
1298
|
byteLength: number;
|
|
946
1299
|
messages: OmpMessage[];
|
|
947
1300
|
}
|
|
1301
|
+
export interface OmpPersistedSessionMessages {
|
|
1302
|
+
sessionFile: string;
|
|
1303
|
+
nativeSessionId: string;
|
|
1304
|
+
byteLength: number;
|
|
1305
|
+
messages: OmpMessage[];
|
|
1306
|
+
imageReplayWarning?: true;
|
|
1307
|
+
}
|
|
948
1308
|
|
|
949
1309
|
export interface OmpStartOptions {
|
|
950
1310
|
cwd: string;
|
|
@@ -997,6 +1357,7 @@ export interface OmpRuntimeSession {
|
|
|
997
1357
|
message: string,
|
|
998
1358
|
images?: readonly OmpImage[],
|
|
999
1359
|
onAccepted?: () => void,
|
|
1360
|
+
onRequested?: (requestId: string) => void,
|
|
1000
1361
|
): Promise<{ requestId: string; agentInvoked?: boolean }>;
|
|
1001
1362
|
compact(customInstructions?: string): Promise<OmpCompactionResult>;
|
|
1002
1363
|
setAutoCompaction(enabled: boolean): Promise<void>;
|
|
@@ -1022,6 +1383,12 @@ export interface OmpRuntime {
|
|
|
1022
1383
|
readonly supportsPersistence: boolean;
|
|
1023
1384
|
startSession(options: OmpStartOptions): Promise<OmpRuntimeSession>;
|
|
1024
1385
|
listSessions(options: OmpSessionListOptions): Promise<OmpSessionDescriptor[]>;
|
|
1386
|
+
readPersistedSessionTranscript?(options: {
|
|
1387
|
+
sessionFile: string;
|
|
1388
|
+
sessionId: string;
|
|
1389
|
+
cwd: string;
|
|
1390
|
+
signal?: AbortSignal;
|
|
1391
|
+
}): Promise<OmpPersistedSessionMessages>;
|
|
1025
1392
|
readPersistedSubagentTranscript(options: {
|
|
1026
1393
|
parentSessionFile: string;
|
|
1027
1394
|
childTranscriptId: string;
|
|
@@ -1965,21 +2332,7 @@ class OmpRpcProcess {
|
|
|
1965
2332
|
this.recordProtocolViolation();
|
|
1966
2333
|
return;
|
|
1967
2334
|
}
|
|
1968
|
-
|
|
1969
|
-
if (this.receiveDegradedAgentEnd(decoded, true)) return;
|
|
1970
|
-
if (
|
|
1971
|
-
boundedJsonBytes(decoded, MAX_SEMANTIC_FRAME_BYTES, 1_024, MAX_IMAGE_DATA_LENGTH, 4_096) ===
|
|
1972
|
-
Number.POSITIVE_INFINITY
|
|
1973
|
-
) {
|
|
1974
|
-
this.recordProtocolViolation();
|
|
1975
|
-
return;
|
|
1976
|
-
}
|
|
1977
|
-
const frame = JsonObjectSchema.safeParse(decoded);
|
|
1978
|
-
if (!frame.success) {
|
|
1979
|
-
this.recordProtocolViolation();
|
|
1980
|
-
return;
|
|
1981
|
-
}
|
|
1982
|
-
this.receiveFrame(frame.data);
|
|
2335
|
+
this.receiveDecodedFrame(decoded, payload.byteLength);
|
|
1983
2336
|
}
|
|
1984
2337
|
|
|
1985
2338
|
private receiveChunk(frame: ChunkFrame): void {
|
|
@@ -2049,32 +2402,43 @@ class OmpRpcProcess {
|
|
|
2049
2402
|
this.recordProtocolViolation();
|
|
2050
2403
|
return;
|
|
2051
2404
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2405
|
+
this.receiveDecodedFrame(decodedFrame, reassembled.byteLength);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
private receiveDecodedFrame(value: unknown, rawByteLength: number): void {
|
|
2409
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2410
|
+
const frame = value as Record<string, unknown>;
|
|
2411
|
+
const pending = typeof frame.id === "string" ? this.pending.get(frame.id) : undefined;
|
|
2412
|
+
if (
|
|
2413
|
+
frame.type === "response" &&
|
|
2414
|
+
(pending?.command === "get_messages" || pending?.command === "get_subagent_messages")
|
|
2415
|
+
) {
|
|
2416
|
+
this.receiveResponse(frame);
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (rawByteLength > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2421
|
+
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2064
2422
|
return;
|
|
2065
2423
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2424
|
+
if (this.receiveKnownResponse(value)) return;
|
|
2425
|
+
const sanitized = sanitizeLiveDisplayFrame(value);
|
|
2426
|
+
const frame = JsonObjectSchema.safeParse(sanitized);
|
|
2427
|
+
if (!frame.success) {
|
|
2068
2428
|
this.recordProtocolViolation();
|
|
2069
2429
|
return;
|
|
2070
2430
|
}
|
|
2071
|
-
this.receiveFrame(
|
|
2431
|
+
this.receiveFrame(frame.data);
|
|
2072
2432
|
}
|
|
2073
2433
|
|
|
2074
2434
|
private receiveKnownResponse(value: unknown): boolean {
|
|
2075
2435
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2076
2436
|
const frame = value as Record<string, unknown>;
|
|
2077
|
-
if (
|
|
2437
|
+
if (
|
|
2438
|
+
frame.type !== "response" ||
|
|
2439
|
+
typeof frame.id !== "string" ||
|
|
2440
|
+
(!this.pending.has(frame.id) && !this.acceptedPromptIds.has(frame.id))
|
|
2441
|
+
) {
|
|
2078
2442
|
return false;
|
|
2079
2443
|
}
|
|
2080
2444
|
this.receiveResponse(frame);
|
|
@@ -2088,34 +2452,43 @@ class OmpRpcProcess {
|
|
|
2088
2452
|
if (!response.success) {
|
|
2089
2453
|
if (rawId && knownPending) {
|
|
2090
2454
|
this.takePending(rawId)?.reject(new Error("OMP RPC response is invalid"));
|
|
2091
|
-
} else {
|
|
2455
|
+
} else if (!rawId || !this.emitAcceptedPromptFailure(rawId, frame)) {
|
|
2092
2456
|
this.recordProtocolViolation();
|
|
2093
2457
|
}
|
|
2094
2458
|
return;
|
|
2095
2459
|
}
|
|
2096
2460
|
const pending = this.pending.get(response.data.id);
|
|
2097
2461
|
if (!pending) {
|
|
2098
|
-
if (!response.data.success
|
|
2099
|
-
this.
|
|
2100
|
-
type: "prompt_error",
|
|
2101
|
-
id: response.data.id,
|
|
2102
|
-
error: "OMP prompt scheduling failed",
|
|
2103
|
-
});
|
|
2462
|
+
if (!response.data.success) {
|
|
2463
|
+
this.emitAcceptedPromptFailure(response.data.id, response.data);
|
|
2104
2464
|
}
|
|
2105
2465
|
return;
|
|
2106
2466
|
}
|
|
2107
2467
|
const isBranchHistory = pending.command === "get_branch_messages";
|
|
2108
2468
|
const isHistory =
|
|
2109
2469
|
pending.command === "get_messages" || pending.command === "get_subagent_messages";
|
|
2470
|
+
const responseData = isHistory
|
|
2471
|
+
? sanitizeHistoryResponseData(response.data.data)
|
|
2472
|
+
: response.data.data;
|
|
2473
|
+
const boundedFrame =
|
|
2474
|
+
responseData === response.data.data ? frame : { ...frame, data: responseData };
|
|
2110
2475
|
const responseItemLimit = isBranchHistory ? 1_024 : isHistory ? 100_000 : MAX_ARRAY_ITEMS;
|
|
2111
2476
|
const responseByteLimit =
|
|
2112
2477
|
isBranchHistory || isHistory
|
|
2113
2478
|
? Math.min(MAX_REASSEMBLED_FRAME_BYTES, this.reassembledFrameLimit)
|
|
2114
2479
|
: 2 * 1024 * 1024;
|
|
2115
|
-
|
|
2480
|
+
// A model catalog contains up to 256 structured models, so its aggregate
|
|
2481
|
+
// node budget must exceed the small state/command-response budget.
|
|
2482
|
+
const responseNodeLimit = isBranchHistory
|
|
2483
|
+
? 4_096
|
|
2484
|
+
: isHistory
|
|
2485
|
+
? 400_000
|
|
2486
|
+
: pending.command === "get_available_models"
|
|
2487
|
+
? 16_384
|
|
2488
|
+
: 2_048;
|
|
2116
2489
|
if (
|
|
2117
2490
|
boundedJsonBytes(
|
|
2118
|
-
|
|
2491
|
+
boundedFrame,
|
|
2119
2492
|
responseByteLimit,
|
|
2120
2493
|
responseItemLimit,
|
|
2121
2494
|
MAX_IMAGE_DATA_LENGTH,
|
|
@@ -2131,7 +2504,7 @@ class OmpRpcProcess {
|
|
|
2131
2504
|
if (!settled) return;
|
|
2132
2505
|
if (response.data.success) {
|
|
2133
2506
|
try {
|
|
2134
|
-
settled.beforeResolve?.(
|
|
2507
|
+
settled.beforeResolve?.(responseData);
|
|
2135
2508
|
if (settled.command === "prompt") {
|
|
2136
2509
|
if (this.acceptedPromptIds.size >= MAX_PENDING_REQUESTS) {
|
|
2137
2510
|
const oldest = this.acceptedPromptIds.values().next().value;
|
|
@@ -2139,7 +2512,7 @@ class OmpRpcProcess {
|
|
|
2139
2512
|
}
|
|
2140
2513
|
this.acceptedPromptIds.add(response.data.id);
|
|
2141
2514
|
}
|
|
2142
|
-
settled.resolve(
|
|
2515
|
+
settled.resolve(responseData);
|
|
2143
2516
|
} catch {
|
|
2144
2517
|
settled.reject(new Error("OMP RPC response is invalid"));
|
|
2145
2518
|
}
|
|
@@ -2148,6 +2521,22 @@ class OmpRpcProcess {
|
|
|
2148
2521
|
}
|
|
2149
2522
|
}
|
|
2150
2523
|
|
|
2524
|
+
private emitAcceptedPromptFailure(id: string, frame: Record<string, unknown>): boolean {
|
|
2525
|
+
if (frame.success !== false || !this.acceptedPromptIds.delete(id)) return false;
|
|
2526
|
+
const error = typeof frame.error === "string" ? frame.error : undefined;
|
|
2527
|
+
const nativeError =
|
|
2528
|
+
error && utf8Bytes(error) <= MAX_RPC_ERROR_BYTES ? error : PROMPT_SCHEDULING_FAILURE;
|
|
2529
|
+
const code = typeof frame.code === "string" ? frame.code : undefined;
|
|
2530
|
+
const nativeCode = code && utf8Bytes(code) <= MAX_RPC_ERROR_CODE_BYTES ? code : undefined;
|
|
2531
|
+
this.emit({
|
|
2532
|
+
type: "prompt_error",
|
|
2533
|
+
id,
|
|
2534
|
+
error: nativeError,
|
|
2535
|
+
...(nativeCode ? { code: nativeCode } : {}),
|
|
2536
|
+
});
|
|
2537
|
+
return true;
|
|
2538
|
+
}
|
|
2539
|
+
|
|
2151
2540
|
private takePending(id: string): PendingRequest | undefined {
|
|
2152
2541
|
const pending = this.pending.get(id);
|
|
2153
2542
|
if (!pending) return undefined;
|
|
@@ -2172,21 +2561,27 @@ class OmpRpcProcess {
|
|
|
2172
2561
|
this.fail(new Error("OMP emitted invalid terminal metadata"));
|
|
2173
2562
|
return true;
|
|
2174
2563
|
}
|
|
2564
|
+
const structuralFrame = mapRuntimeFrameDetails(frame, omitOptionalDetails);
|
|
2175
2565
|
const messagesAreSafe =
|
|
2176
2566
|
frame.messages === undefined ||
|
|
2177
2567
|
(Array.isArray(frame.messages) &&
|
|
2178
2568
|
frame.messages.length <= MAX_ARRAY_ITEMS &&
|
|
2179
2569
|
boundedJsonBytes(
|
|
2180
|
-
|
|
2570
|
+
structuralFrame.messages as unknown[],
|
|
2181
2571
|
MAX_SEMANTIC_FRAME_BYTES,
|
|
2182
|
-
|
|
2183
|
-
|
|
2572
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2573
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2184
2574
|
4_096,
|
|
2185
2575
|
) !== Number.POSITIVE_INFINITY);
|
|
2186
2576
|
const payloadIsSafe =
|
|
2187
2577
|
messagesAreSafe &&
|
|
2188
|
-
boundedJsonBytes(
|
|
2189
|
-
|
|
2578
|
+
boundedJsonBytes(
|
|
2579
|
+
structuralFrame,
|
|
2580
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2581
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2582
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2583
|
+
4_096,
|
|
2584
|
+
) !== Number.POSITIVE_INFINITY;
|
|
2190
2585
|
if (onlyUnsafePayload && payloadIsSafe) return false;
|
|
2191
2586
|
if (envelope.data.isTerminal === false) {
|
|
2192
2587
|
this.fail(new Error("OMP emitted an invalid nonterminal agent_end payload"));
|
|
@@ -2213,16 +2608,22 @@ class OmpRpcProcess {
|
|
|
2213
2608
|
this.recordProtocolViolation();
|
|
2214
2609
|
return;
|
|
2215
2610
|
}
|
|
2216
|
-
|
|
2611
|
+
const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
|
|
2612
|
+
if (this.receiveDegradedAgentEnd(safeFrame, true)) return;
|
|
2217
2613
|
if (
|
|
2218
|
-
boundedJsonBytes(
|
|
2219
|
-
|
|
2614
|
+
boundedJsonBytes(
|
|
2615
|
+
mapRuntimeFrameDetails(safeFrame, omitOptionalDetails),
|
|
2616
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2617
|
+
runtimeFrameCollectionLimit(safeFrame),
|
|
2618
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2619
|
+
4_096,
|
|
2620
|
+
) === Number.POSITIVE_INFINITY
|
|
2220
2621
|
) {
|
|
2221
2622
|
this.recordProtocolViolation();
|
|
2222
2623
|
return;
|
|
2223
2624
|
}
|
|
2224
2625
|
if (type === "rpc_chunk") {
|
|
2225
|
-
const chunk = OmpChunkFrameSchema.safeParse(
|
|
2626
|
+
const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
|
|
2226
2627
|
if (!chunk.success) this.rejectChunk();
|
|
2227
2628
|
else this.receiveChunk(chunk.data);
|
|
2228
2629
|
return;
|
|
@@ -2240,7 +2641,7 @@ class OmpRpcProcess {
|
|
|
2240
2641
|
this.recordProtocolViolation();
|
|
2241
2642
|
return;
|
|
2242
2643
|
}
|
|
2243
|
-
const ready = OmpReadyFrameSchema.safeParse(
|
|
2644
|
+
const ready = OmpReadyFrameSchema.safeParse(safeFrame);
|
|
2244
2645
|
if (!ready.success) {
|
|
2245
2646
|
this.recordProtocolViolation();
|
|
2246
2647
|
} else {
|
|
@@ -2250,13 +2651,13 @@ class OmpRpcProcess {
|
|
|
2250
2651
|
return;
|
|
2251
2652
|
}
|
|
2252
2653
|
if (type === "response") {
|
|
2253
|
-
this.receiveResponse(
|
|
2654
|
+
this.receiveResponse(safeFrame);
|
|
2254
2655
|
return;
|
|
2255
2656
|
}
|
|
2256
|
-
const event = OmpRuntimeEventSchema.safeParse(
|
|
2657
|
+
const event = OmpRuntimeEventSchema.safeParse(safeFrame);
|
|
2257
2658
|
if (!event.success) {
|
|
2258
|
-
this.rejectMatchingToolApproval(
|
|
2259
|
-
if (type === "agent_end" && this.receiveDegradedAgentEnd(
|
|
2659
|
+
this.rejectMatchingToolApproval(safeFrame);
|
|
2660
|
+
if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
|
|
2260
2661
|
this.recordProtocolViolation();
|
|
2261
2662
|
return;
|
|
2262
2663
|
}
|
|
@@ -2595,6 +2996,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
2595
2996
|
message: string,
|
|
2596
2997
|
images: readonly OmpImage[] = [],
|
|
2597
2998
|
onAccepted?: () => void,
|
|
2999
|
+
onRequested?: (requestId: string) => void,
|
|
2598
3000
|
): Promise<{ requestId: string; agentInvoked?: boolean }> {
|
|
2599
3001
|
const safeMessage = validateBoundedText(message, "prompt", MAX_TEXT_LENGTH);
|
|
2600
3002
|
let acknowledgement: z.infer<typeof OmpPromptAckSchema> | undefined;
|
|
@@ -2606,6 +3008,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
2606
3008
|
onAccepted?.();
|
|
2607
3009
|
},
|
|
2608
3010
|
);
|
|
3011
|
+
onRequested?.(request.id);
|
|
2609
3012
|
await request.promise;
|
|
2610
3013
|
return { requestId: request.id, ...acknowledgement };
|
|
2611
3014
|
}
|
|
@@ -2664,6 +3067,27 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2664
3067
|
listOmpSessionDescriptors(options, this.options.environment ?? process.env),
|
|
2665
3068
|
);
|
|
2666
3069
|
}
|
|
3070
|
+
async readPersistedSessionTranscript(options: {
|
|
3071
|
+
sessionFile: string;
|
|
3072
|
+
sessionId: string;
|
|
3073
|
+
cwd: string;
|
|
3074
|
+
signal?: AbortSignal;
|
|
3075
|
+
}): Promise<OmpPersistedSessionMessages> {
|
|
3076
|
+
const transcript = await readOmpPersistedSessionTranscript(
|
|
3077
|
+
options.sessionFile,
|
|
3078
|
+
options.sessionId,
|
|
3079
|
+
options.cwd,
|
|
3080
|
+
options.signal,
|
|
3081
|
+
join(ompDataDir(this.options.environment ?? process.env), "blobs"),
|
|
3082
|
+
);
|
|
3083
|
+
return {
|
|
3084
|
+
...transcript,
|
|
3085
|
+
messages: z
|
|
3086
|
+
.array(OmpMessageSchema)
|
|
3087
|
+
.max(100_000)
|
|
3088
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
2667
3091
|
async readPersistedSubagentTranscript(options: {
|
|
2668
3092
|
parentSessionFile: string;
|
|
2669
3093
|
childTranscriptId: string;
|
|
@@ -2678,7 +3102,10 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2678
3102
|
);
|
|
2679
3103
|
return {
|
|
2680
3104
|
...transcript,
|
|
2681
|
-
messages: z
|
|
3105
|
+
messages: z
|
|
3106
|
+
.array(OmpMessageSchema)
|
|
3107
|
+
.max(100_000)
|
|
3108
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
2682
3109
|
};
|
|
2683
3110
|
}
|
|
2684
3111
|
|