@omercnet/paseo-omp 0.2.1 → 0.3.0-next.100.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 +498 -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 +306 -1
- package/server/provider/session.ts +704 -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,12 @@ 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
|
+
}
|
|
948
1307
|
|
|
949
1308
|
export interface OmpStartOptions {
|
|
950
1309
|
cwd: string;
|
|
@@ -997,6 +1356,7 @@ export interface OmpRuntimeSession {
|
|
|
997
1356
|
message: string,
|
|
998
1357
|
images?: readonly OmpImage[],
|
|
999
1358
|
onAccepted?: () => void,
|
|
1359
|
+
onRequested?: (requestId: string) => void,
|
|
1000
1360
|
): Promise<{ requestId: string; agentInvoked?: boolean }>;
|
|
1001
1361
|
compact(customInstructions?: string): Promise<OmpCompactionResult>;
|
|
1002
1362
|
setAutoCompaction(enabled: boolean): Promise<void>;
|
|
@@ -1022,6 +1382,12 @@ export interface OmpRuntime {
|
|
|
1022
1382
|
readonly supportsPersistence: boolean;
|
|
1023
1383
|
startSession(options: OmpStartOptions): Promise<OmpRuntimeSession>;
|
|
1024
1384
|
listSessions(options: OmpSessionListOptions): Promise<OmpSessionDescriptor[]>;
|
|
1385
|
+
readPersistedSessionTranscript?(options: {
|
|
1386
|
+
sessionFile: string;
|
|
1387
|
+
sessionId: string;
|
|
1388
|
+
cwd: string;
|
|
1389
|
+
signal?: AbortSignal;
|
|
1390
|
+
}): Promise<OmpPersistedSessionMessages>;
|
|
1025
1391
|
readPersistedSubagentTranscript(options: {
|
|
1026
1392
|
parentSessionFile: string;
|
|
1027
1393
|
childTranscriptId: string;
|
|
@@ -1965,21 +2331,7 @@ class OmpRpcProcess {
|
|
|
1965
2331
|
this.recordProtocolViolation();
|
|
1966
2332
|
return;
|
|
1967
2333
|
}
|
|
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);
|
|
2334
|
+
this.receiveDecodedFrame(decoded, payload.byteLength);
|
|
1983
2335
|
}
|
|
1984
2336
|
|
|
1985
2337
|
private receiveChunk(frame: ChunkFrame): void {
|
|
@@ -2049,32 +2401,43 @@ class OmpRpcProcess {
|
|
|
2049
2401
|
this.recordProtocolViolation();
|
|
2050
2402
|
return;
|
|
2051
2403
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2404
|
+
this.receiveDecodedFrame(decodedFrame, reassembled.byteLength);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
private receiveDecodedFrame(value: unknown, rawByteLength: number): void {
|
|
2408
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2409
|
+
const frame = value as Record<string, unknown>;
|
|
2410
|
+
const pending = typeof frame.id === "string" ? this.pending.get(frame.id) : undefined;
|
|
2411
|
+
if (
|
|
2412
|
+
frame.type === "response" &&
|
|
2413
|
+
(pending?.command === "get_messages" || pending?.command === "get_subagent_messages")
|
|
2414
|
+
) {
|
|
2415
|
+
this.receiveResponse(frame);
|
|
2416
|
+
return;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
if (rawByteLength > MAX_SEMANTIC_FRAME_BYTES) {
|
|
2420
|
+
this.fail(new Error("OMP RPC frame exceeds the semantic byte limit"));
|
|
2064
2421
|
return;
|
|
2065
2422
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2423
|
+
if (this.receiveKnownResponse(value)) return;
|
|
2424
|
+
const sanitized = sanitizeLiveDisplayFrame(value);
|
|
2425
|
+
const frame = JsonObjectSchema.safeParse(sanitized);
|
|
2426
|
+
if (!frame.success) {
|
|
2068
2427
|
this.recordProtocolViolation();
|
|
2069
2428
|
return;
|
|
2070
2429
|
}
|
|
2071
|
-
this.receiveFrame(
|
|
2430
|
+
this.receiveFrame(frame.data);
|
|
2072
2431
|
}
|
|
2073
2432
|
|
|
2074
2433
|
private receiveKnownResponse(value: unknown): boolean {
|
|
2075
2434
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
2076
2435
|
const frame = value as Record<string, unknown>;
|
|
2077
|
-
if (
|
|
2436
|
+
if (
|
|
2437
|
+
frame.type !== "response" ||
|
|
2438
|
+
typeof frame.id !== "string" ||
|
|
2439
|
+
(!this.pending.has(frame.id) && !this.acceptedPromptIds.has(frame.id))
|
|
2440
|
+
) {
|
|
2078
2441
|
return false;
|
|
2079
2442
|
}
|
|
2080
2443
|
this.receiveResponse(frame);
|
|
@@ -2088,34 +2451,43 @@ class OmpRpcProcess {
|
|
|
2088
2451
|
if (!response.success) {
|
|
2089
2452
|
if (rawId && knownPending) {
|
|
2090
2453
|
this.takePending(rawId)?.reject(new Error("OMP RPC response is invalid"));
|
|
2091
|
-
} else {
|
|
2454
|
+
} else if (!rawId || !this.emitAcceptedPromptFailure(rawId, frame)) {
|
|
2092
2455
|
this.recordProtocolViolation();
|
|
2093
2456
|
}
|
|
2094
2457
|
return;
|
|
2095
2458
|
}
|
|
2096
2459
|
const pending = this.pending.get(response.data.id);
|
|
2097
2460
|
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
|
-
});
|
|
2461
|
+
if (!response.data.success) {
|
|
2462
|
+
this.emitAcceptedPromptFailure(response.data.id, response.data);
|
|
2104
2463
|
}
|
|
2105
2464
|
return;
|
|
2106
2465
|
}
|
|
2107
2466
|
const isBranchHistory = pending.command === "get_branch_messages";
|
|
2108
2467
|
const isHistory =
|
|
2109
2468
|
pending.command === "get_messages" || pending.command === "get_subagent_messages";
|
|
2469
|
+
const responseData = isHistory
|
|
2470
|
+
? sanitizeHistoryResponseData(response.data.data)
|
|
2471
|
+
: response.data.data;
|
|
2472
|
+
const boundedFrame =
|
|
2473
|
+
responseData === response.data.data ? frame : { ...frame, data: responseData };
|
|
2110
2474
|
const responseItemLimit = isBranchHistory ? 1_024 : isHistory ? 100_000 : MAX_ARRAY_ITEMS;
|
|
2111
2475
|
const responseByteLimit =
|
|
2112
2476
|
isBranchHistory || isHistory
|
|
2113
2477
|
? Math.min(MAX_REASSEMBLED_FRAME_BYTES, this.reassembledFrameLimit)
|
|
2114
2478
|
: 2 * 1024 * 1024;
|
|
2115
|
-
|
|
2479
|
+
// A model catalog contains up to 256 structured models, so its aggregate
|
|
2480
|
+
// node budget must exceed the small state/command-response budget.
|
|
2481
|
+
const responseNodeLimit = isBranchHistory
|
|
2482
|
+
? 4_096
|
|
2483
|
+
: isHistory
|
|
2484
|
+
? 400_000
|
|
2485
|
+
: pending.command === "get_available_models"
|
|
2486
|
+
? 16_384
|
|
2487
|
+
: 2_048;
|
|
2116
2488
|
if (
|
|
2117
2489
|
boundedJsonBytes(
|
|
2118
|
-
|
|
2490
|
+
boundedFrame,
|
|
2119
2491
|
responseByteLimit,
|
|
2120
2492
|
responseItemLimit,
|
|
2121
2493
|
MAX_IMAGE_DATA_LENGTH,
|
|
@@ -2131,7 +2503,7 @@ class OmpRpcProcess {
|
|
|
2131
2503
|
if (!settled) return;
|
|
2132
2504
|
if (response.data.success) {
|
|
2133
2505
|
try {
|
|
2134
|
-
settled.beforeResolve?.(
|
|
2506
|
+
settled.beforeResolve?.(responseData);
|
|
2135
2507
|
if (settled.command === "prompt") {
|
|
2136
2508
|
if (this.acceptedPromptIds.size >= MAX_PENDING_REQUESTS) {
|
|
2137
2509
|
const oldest = this.acceptedPromptIds.values().next().value;
|
|
@@ -2139,7 +2511,7 @@ class OmpRpcProcess {
|
|
|
2139
2511
|
}
|
|
2140
2512
|
this.acceptedPromptIds.add(response.data.id);
|
|
2141
2513
|
}
|
|
2142
|
-
settled.resolve(
|
|
2514
|
+
settled.resolve(responseData);
|
|
2143
2515
|
} catch {
|
|
2144
2516
|
settled.reject(new Error("OMP RPC response is invalid"));
|
|
2145
2517
|
}
|
|
@@ -2148,6 +2520,22 @@ class OmpRpcProcess {
|
|
|
2148
2520
|
}
|
|
2149
2521
|
}
|
|
2150
2522
|
|
|
2523
|
+
private emitAcceptedPromptFailure(id: string, frame: Record<string, unknown>): boolean {
|
|
2524
|
+
if (frame.success !== false || !this.acceptedPromptIds.delete(id)) return false;
|
|
2525
|
+
const error = typeof frame.error === "string" ? frame.error : undefined;
|
|
2526
|
+
const nativeError =
|
|
2527
|
+
error && utf8Bytes(error) <= MAX_RPC_ERROR_BYTES ? error : PROMPT_SCHEDULING_FAILURE;
|
|
2528
|
+
const code = typeof frame.code === "string" ? frame.code : undefined;
|
|
2529
|
+
const nativeCode = code && utf8Bytes(code) <= MAX_RPC_ERROR_CODE_BYTES ? code : undefined;
|
|
2530
|
+
this.emit({
|
|
2531
|
+
type: "prompt_error",
|
|
2532
|
+
id,
|
|
2533
|
+
error: nativeError,
|
|
2534
|
+
...(nativeCode ? { code: nativeCode } : {}),
|
|
2535
|
+
});
|
|
2536
|
+
return true;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2151
2539
|
private takePending(id: string): PendingRequest | undefined {
|
|
2152
2540
|
const pending = this.pending.get(id);
|
|
2153
2541
|
if (!pending) return undefined;
|
|
@@ -2172,21 +2560,27 @@ class OmpRpcProcess {
|
|
|
2172
2560
|
this.fail(new Error("OMP emitted invalid terminal metadata"));
|
|
2173
2561
|
return true;
|
|
2174
2562
|
}
|
|
2563
|
+
const structuralFrame = mapRuntimeFrameDetails(frame, omitOptionalDetails);
|
|
2175
2564
|
const messagesAreSafe =
|
|
2176
2565
|
frame.messages === undefined ||
|
|
2177
2566
|
(Array.isArray(frame.messages) &&
|
|
2178
2567
|
frame.messages.length <= MAX_ARRAY_ITEMS &&
|
|
2179
2568
|
boundedJsonBytes(
|
|
2180
|
-
|
|
2569
|
+
structuralFrame.messages as unknown[],
|
|
2181
2570
|
MAX_SEMANTIC_FRAME_BYTES,
|
|
2182
|
-
|
|
2183
|
-
|
|
2571
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2572
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2184
2573
|
4_096,
|
|
2185
2574
|
) !== Number.POSITIVE_INFINITY);
|
|
2186
2575
|
const payloadIsSafe =
|
|
2187
2576
|
messagesAreSafe &&
|
|
2188
|
-
boundedJsonBytes(
|
|
2189
|
-
|
|
2577
|
+
boundedJsonBytes(
|
|
2578
|
+
structuralFrame,
|
|
2579
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2580
|
+
OMP_MAX_CONTENT_PARTS,
|
|
2581
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2582
|
+
4_096,
|
|
2583
|
+
) !== Number.POSITIVE_INFINITY;
|
|
2190
2584
|
if (onlyUnsafePayload && payloadIsSafe) return false;
|
|
2191
2585
|
if (envelope.data.isTerminal === false) {
|
|
2192
2586
|
this.fail(new Error("OMP emitted an invalid nonterminal agent_end payload"));
|
|
@@ -2213,16 +2607,22 @@ class OmpRpcProcess {
|
|
|
2213
2607
|
this.recordProtocolViolation();
|
|
2214
2608
|
return;
|
|
2215
2609
|
}
|
|
2216
|
-
|
|
2610
|
+
const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
|
|
2611
|
+
if (this.receiveDegradedAgentEnd(safeFrame, true)) return;
|
|
2217
2612
|
if (
|
|
2218
|
-
boundedJsonBytes(
|
|
2219
|
-
|
|
2613
|
+
boundedJsonBytes(
|
|
2614
|
+
mapRuntimeFrameDetails(safeFrame, omitOptionalDetails),
|
|
2615
|
+
MAX_SEMANTIC_FRAME_BYTES,
|
|
2616
|
+
runtimeFrameCollectionLimit(safeFrame),
|
|
2617
|
+
MAX_IMAGE_DATA_LENGTH,
|
|
2618
|
+
4_096,
|
|
2619
|
+
) === Number.POSITIVE_INFINITY
|
|
2220
2620
|
) {
|
|
2221
2621
|
this.recordProtocolViolation();
|
|
2222
2622
|
return;
|
|
2223
2623
|
}
|
|
2224
2624
|
if (type === "rpc_chunk") {
|
|
2225
|
-
const chunk = OmpChunkFrameSchema.safeParse(
|
|
2625
|
+
const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
|
|
2226
2626
|
if (!chunk.success) this.rejectChunk();
|
|
2227
2627
|
else this.receiveChunk(chunk.data);
|
|
2228
2628
|
return;
|
|
@@ -2240,7 +2640,7 @@ class OmpRpcProcess {
|
|
|
2240
2640
|
this.recordProtocolViolation();
|
|
2241
2641
|
return;
|
|
2242
2642
|
}
|
|
2243
|
-
const ready = OmpReadyFrameSchema.safeParse(
|
|
2643
|
+
const ready = OmpReadyFrameSchema.safeParse(safeFrame);
|
|
2244
2644
|
if (!ready.success) {
|
|
2245
2645
|
this.recordProtocolViolation();
|
|
2246
2646
|
} else {
|
|
@@ -2250,13 +2650,13 @@ class OmpRpcProcess {
|
|
|
2250
2650
|
return;
|
|
2251
2651
|
}
|
|
2252
2652
|
if (type === "response") {
|
|
2253
|
-
this.receiveResponse(
|
|
2653
|
+
this.receiveResponse(safeFrame);
|
|
2254
2654
|
return;
|
|
2255
2655
|
}
|
|
2256
|
-
const event = OmpRuntimeEventSchema.safeParse(
|
|
2656
|
+
const event = OmpRuntimeEventSchema.safeParse(safeFrame);
|
|
2257
2657
|
if (!event.success) {
|
|
2258
|
-
this.rejectMatchingToolApproval(
|
|
2259
|
-
if (type === "agent_end" && this.receiveDegradedAgentEnd(
|
|
2658
|
+
this.rejectMatchingToolApproval(safeFrame);
|
|
2659
|
+
if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
|
|
2260
2660
|
this.recordProtocolViolation();
|
|
2261
2661
|
return;
|
|
2262
2662
|
}
|
|
@@ -2595,6 +2995,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
2595
2995
|
message: string,
|
|
2596
2996
|
images: readonly OmpImage[] = [],
|
|
2597
2997
|
onAccepted?: () => void,
|
|
2998
|
+
onRequested?: (requestId: string) => void,
|
|
2598
2999
|
): Promise<{ requestId: string; agentInvoked?: boolean }> {
|
|
2599
3000
|
const safeMessage = validateBoundedText(message, "prompt", MAX_TEXT_LENGTH);
|
|
2600
3001
|
let acknowledgement: z.infer<typeof OmpPromptAckSchema> | undefined;
|
|
@@ -2606,6 +3007,7 @@ class OmpRpcSession implements OmpRuntimeSession {
|
|
|
2606
3007
|
onAccepted?.();
|
|
2607
3008
|
},
|
|
2608
3009
|
);
|
|
3010
|
+
onRequested?.(request.id);
|
|
2609
3011
|
await request.promise;
|
|
2610
3012
|
return { requestId: request.id, ...acknowledgement };
|
|
2611
3013
|
}
|
|
@@ -2664,6 +3066,27 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2664
3066
|
listOmpSessionDescriptors(options, this.options.environment ?? process.env),
|
|
2665
3067
|
);
|
|
2666
3068
|
}
|
|
3069
|
+
async readPersistedSessionTranscript(options: {
|
|
3070
|
+
sessionFile: string;
|
|
3071
|
+
sessionId: string;
|
|
3072
|
+
cwd: string;
|
|
3073
|
+
signal?: AbortSignal;
|
|
3074
|
+
}): Promise<OmpPersistedSessionMessages> {
|
|
3075
|
+
const transcript = await readOmpPersistedSessionTranscript(
|
|
3076
|
+
options.sessionFile,
|
|
3077
|
+
options.sessionId,
|
|
3078
|
+
options.cwd,
|
|
3079
|
+
options.signal,
|
|
3080
|
+
join(ompDataDir(this.options.environment ?? process.env), "blobs"),
|
|
3081
|
+
);
|
|
3082
|
+
return {
|
|
3083
|
+
...transcript,
|
|
3084
|
+
messages: z
|
|
3085
|
+
.array(OmpMessageSchema)
|
|
3086
|
+
.max(100_000)
|
|
3087
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
3088
|
+
};
|
|
3089
|
+
}
|
|
2667
3090
|
async readPersistedSubagentTranscript(options: {
|
|
2668
3091
|
parentSessionFile: string;
|
|
2669
3092
|
childTranscriptId: string;
|
|
@@ -2678,7 +3101,10 @@ export class OmpRpcRuntime implements OmpRuntime {
|
|
|
2678
3101
|
);
|
|
2679
3102
|
return {
|
|
2680
3103
|
...transcript,
|
|
2681
|
-
messages: z
|
|
3104
|
+
messages: z
|
|
3105
|
+
.array(OmpMessageSchema)
|
|
3106
|
+
.max(100_000)
|
|
3107
|
+
.parse(sanitizeMessageListMetadata(transcript.messages)),
|
|
2682
3108
|
};
|
|
2683
3109
|
}
|
|
2684
3110
|
|