@omercnet/paseo-omp 0.3.0-next.108.1 → 0.3.0-next.111.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/README.md +19 -5
- package/client/omp-config-surface.tsx +163 -14
- package/client/omp-config-views.ts +24 -0
- package/client/support-diagnostics-state.ts +45 -0
- package/index.server.ts +24 -2
- package/package.json +2 -8
- package/paseo-plugin.json +1 -1
- package/server/operational-failure-diagnostics.ts +76 -0
- package/server/package-version.ts +2 -0
- package/server/protocol-violation-diagnostics.ts +169 -0
- package/server/provider/connection.ts +25 -0
- package/server/provider/host-tools.ts +63 -10
- package/server/provider/omp-rpc.ts +469 -44
- package/server/provider/profile-providers.ts +7 -2
- package/server/provider/registration.ts +12 -2
- package/server/provider/session.ts +70 -3
- package/server/support-diagnostics.ts +284 -0
- package/shared/support-diagnostics.ts +32 -0
- package/CHANGELOG.md +0 -113
- package/SUPPORT.md +0 -44
- package/TESTING.md +0 -150
- package/docs/alpha-release-checklist.md +0 -68
- package/docs/configuration.md +0 -126
- package/docs/core-provider-issue-audit.md +0 -109
- package/docs/images/mcp-authorization-compact.png +0 -0
- package/docs/images/mcp-controls-wide.png +0 -0
- package/docs/images/model-role-picker-compact.webp +0 -0
- package/docs/images/model-role-picker-desktop.webp +0 -0
- package/docs/images/plugin-manager.png +0 -0
- package/docs/images/workspace-settings.png +0 -0
- package/docs/installation.md +0 -89
- package/tsconfig.json +0 -16
|
@@ -87,30 +87,296 @@ const MAX_RPC_ERROR_CODE_BYTES = 256;
|
|
|
87
87
|
const PROMPT_SCHEDULING_FAILURE = "OMP prompt scheduling failed";
|
|
88
88
|
const PROTOCOL_VIOLATION_COALESCE_MS = 10_000;
|
|
89
89
|
|
|
90
|
-
export
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
export const OMP_PROTOCOL_VIOLATION_CATEGORIES = [
|
|
91
|
+
"duplicate-ready",
|
|
92
|
+
"frame-limit",
|
|
93
|
+
"incomplete-frame",
|
|
94
|
+
"interleaved-chunk",
|
|
95
|
+
"invalid-chunk",
|
|
96
|
+
"invalid-envelope",
|
|
97
|
+
"invalid-event",
|
|
98
|
+
"invalid-event-state",
|
|
99
|
+
"invalid-json",
|
|
100
|
+
"invalid-ready",
|
|
101
|
+
"invalid-response",
|
|
102
|
+
"remote-frame-error",
|
|
103
|
+
] as const;
|
|
104
|
+
export type OmpProtocolViolationCategory = (typeof OMP_PROTOCOL_VIOLATION_CATEGORIES)[number];
|
|
105
|
+
export const OMP_PROTOCOL_VIOLATION_REASONS = [
|
|
106
|
+
"output-ended-mid-frame",
|
|
107
|
+
"physical-frame-limit",
|
|
108
|
+
"semantic-frame-limit",
|
|
109
|
+
"json-decode",
|
|
110
|
+
"chunk-json-decode",
|
|
111
|
+
"object-envelope",
|
|
112
|
+
"missing-frame-type",
|
|
113
|
+
"response-schema",
|
|
114
|
+
"chunk-schema",
|
|
115
|
+
"chunk-metadata",
|
|
116
|
+
"chunk-size",
|
|
117
|
+
"chunk-start-index",
|
|
118
|
+
"chunk-sequence",
|
|
119
|
+
"chunk-byte-count",
|
|
120
|
+
"chunk-timeout",
|
|
121
|
+
"frame-interleaved-with-chunk",
|
|
122
|
+
"remote-frame-error",
|
|
123
|
+
"ready-already-received",
|
|
124
|
+
"ready-schema",
|
|
125
|
+
"notice-level-type",
|
|
126
|
+
"notice-message-type",
|
|
127
|
+
"notice-schema",
|
|
128
|
+
"event-schema",
|
|
129
|
+
"message-event-schema",
|
|
130
|
+
"tool-event-schema",
|
|
131
|
+
"lifecycle-event-schema",
|
|
132
|
+
"subagent-event-schema",
|
|
133
|
+
"configuration-event-schema",
|
|
134
|
+
"extension-ui-schema",
|
|
135
|
+
"unknown-event-type",
|
|
136
|
+
"event-state-transition",
|
|
137
|
+
] as const;
|
|
138
|
+
export type OmpProtocolViolationReason = (typeof OMP_PROTOCOL_VIOLATION_REASONS)[number];
|
|
139
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_PHASES = [
|
|
140
|
+
"startup",
|
|
141
|
+
"negotiation",
|
|
142
|
+
"idle",
|
|
143
|
+
"active-turn",
|
|
144
|
+
"closing",
|
|
145
|
+
] as const;
|
|
146
|
+
export type OmpProtocolDiagnosticPhase = (typeof OMP_PROTOCOL_DIAGNOSTIC_PHASES)[number];
|
|
147
|
+
|
|
148
|
+
export const OMP_PROTOCOL_EVENT_TYPES = [
|
|
149
|
+
"agent_start",
|
|
150
|
+
"agent_end",
|
|
151
|
+
"turn_start",
|
|
152
|
+
"turn_end",
|
|
153
|
+
"message_start",
|
|
154
|
+
"message_update",
|
|
155
|
+
"message_end",
|
|
156
|
+
"tool_execution_start",
|
|
157
|
+
"tool_execution_update",
|
|
158
|
+
"tool_execution_end",
|
|
159
|
+
"compaction_start",
|
|
160
|
+
"compaction_end",
|
|
161
|
+
"subagent_lifecycle",
|
|
162
|
+
"subagent_progress",
|
|
163
|
+
"subagent_event",
|
|
164
|
+
"todo_reminder",
|
|
165
|
+
"model_changed",
|
|
166
|
+
"thinking_level_changed",
|
|
167
|
+
"goal_updated",
|
|
168
|
+
"auto_retry_start",
|
|
169
|
+
"auto_retry_end",
|
|
170
|
+
"retry_fallback_applied",
|
|
171
|
+
"retry_fallback_succeeded",
|
|
172
|
+
"todo_auto_clear",
|
|
173
|
+
"auto_compaction_start",
|
|
174
|
+
"auto_compaction_end",
|
|
175
|
+
"available_commands_update",
|
|
176
|
+
"notice",
|
|
177
|
+
"command_output",
|
|
178
|
+
"extension_ui_request",
|
|
179
|
+
"prompt_result",
|
|
180
|
+
"host_tool_call",
|
|
181
|
+
"host_tool_cancel",
|
|
182
|
+
"tool_approval_request",
|
|
183
|
+
"tool_approval_cancel",
|
|
184
|
+
"advisor_yielded",
|
|
185
|
+
] as const;
|
|
186
|
+
export type OmpProtocolEventType = (typeof OMP_PROTOCOL_EVENT_TYPES)[number];
|
|
187
|
+
|
|
188
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_FIELDS = [
|
|
189
|
+
"frame",
|
|
190
|
+
"frame.byteLength",
|
|
191
|
+
"frame.type",
|
|
192
|
+
"response",
|
|
193
|
+
"ready",
|
|
194
|
+
"event",
|
|
195
|
+
"event.sequence",
|
|
196
|
+
"notice.level",
|
|
197
|
+
"notice.message",
|
|
198
|
+
"chunk",
|
|
199
|
+
"chunk.data",
|
|
200
|
+
"chunk.index",
|
|
201
|
+
"chunk.sequence",
|
|
202
|
+
"chunk.byteLength",
|
|
203
|
+
] as const;
|
|
204
|
+
export type OmpProtocolDiagnosticField = (typeof OMP_PROTOCOL_DIAGNOSTIC_FIELDS)[number];
|
|
205
|
+
|
|
206
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS = [
|
|
207
|
+
"complete-json-line",
|
|
208
|
+
"within-byte-limit",
|
|
209
|
+
"valid-json",
|
|
210
|
+
"object-envelope",
|
|
211
|
+
"bounded-frame-type",
|
|
212
|
+
"valid-response-frame",
|
|
213
|
+
"valid-ready-frame",
|
|
214
|
+
"single-ready-frame",
|
|
215
|
+
"valid-event-frame",
|
|
216
|
+
"valid-event-state-transition",
|
|
217
|
+
"valid-message-event",
|
|
218
|
+
"valid-tool-event",
|
|
219
|
+
"valid-lifecycle-event",
|
|
220
|
+
"valid-subagent-event",
|
|
221
|
+
"valid-configuration-event",
|
|
222
|
+
"valid-extension-ui-event",
|
|
223
|
+
"known-event-type",
|
|
224
|
+
"notice-level-enum",
|
|
225
|
+
"notice-message-string",
|
|
226
|
+
"valid-chunk-frame",
|
|
227
|
+
"valid-base64-chunk",
|
|
228
|
+
"first-chunk-index-zero",
|
|
229
|
+
"contiguous-chunk-sequence",
|
|
230
|
+
"declared-chunk-byte-count",
|
|
231
|
+
"chunk-before-deadline",
|
|
232
|
+
"no-interleaved-frame",
|
|
233
|
+
"no-remote-frame-error",
|
|
234
|
+
] as const;
|
|
235
|
+
export type OmpProtocolDiagnosticExpectation =
|
|
236
|
+
(typeof OMP_PROTOCOL_DIAGNOSTIC_EXPECTATIONS)[number];
|
|
237
|
+
|
|
238
|
+
export const OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES = [
|
|
239
|
+
"missing",
|
|
240
|
+
"null",
|
|
241
|
+
"array",
|
|
242
|
+
"object",
|
|
243
|
+
"string",
|
|
244
|
+
"number",
|
|
245
|
+
"boolean",
|
|
246
|
+
"invalid-json",
|
|
247
|
+
"partial-frame",
|
|
248
|
+
"oversized",
|
|
249
|
+
"duplicate",
|
|
250
|
+
"interleaved",
|
|
251
|
+
"out-of-order",
|
|
252
|
+
"timeout",
|
|
253
|
+
"remote-error",
|
|
254
|
+
"mismatched",
|
|
255
|
+
] as const;
|
|
256
|
+
export type OmpProtocolDiagnosticActualType = (typeof OMP_PROTOCOL_DIAGNOSTIC_ACTUAL_TYPES)[number];
|
|
103
257
|
|
|
104
258
|
export interface OmpProtocolViolationDiagnostic {
|
|
105
259
|
category: OmpProtocolViolationCategory;
|
|
260
|
+
reason: OmpProtocolViolationReason;
|
|
261
|
+
phase: OmpProtocolDiagnosticPhase;
|
|
106
262
|
occurrenceCount: number;
|
|
107
|
-
|
|
263
|
+
eventType?: OmpProtocolEventType;
|
|
264
|
+
frameType?: "ready" | "response" | "rpc_chunk" | "rpc_frame_error" | "notice";
|
|
265
|
+
field?: OmpProtocolDiagnosticField;
|
|
266
|
+
expected?: OmpProtocolDiagnosticExpectation;
|
|
267
|
+
actualType?: OmpProtocolDiagnosticActualType;
|
|
108
268
|
maxByteSize?: number;
|
|
269
|
+
limitBytes?: number;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function protocolDiagnosticActualType(value: unknown): OmpProtocolDiagnosticActualType {
|
|
273
|
+
if (value === undefined) return "missing";
|
|
274
|
+
if (value === null) return "null";
|
|
275
|
+
if (Array.isArray(value)) return "array";
|
|
276
|
+
if (typeof value === "object") return "object";
|
|
277
|
+
if (typeof value === "string") return "string";
|
|
278
|
+
if (typeof value === "number") return "number";
|
|
279
|
+
if (typeof value === "boolean") return "boolean";
|
|
280
|
+
return "object";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isProtocolEventType(value: string): value is OmpProtocolEventType {
|
|
284
|
+
return (OMP_PROTOCOL_EVENT_TYPES as readonly string[]).includes(value);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function eventSchemaClassification(
|
|
288
|
+
eventType: OmpProtocolEventType,
|
|
289
|
+
): Pick<OmpProtocolViolationDiagnostic, "reason" | "expected"> {
|
|
290
|
+
switch (eventType) {
|
|
291
|
+
case "message_start":
|
|
292
|
+
case "message_update":
|
|
293
|
+
case "message_end":
|
|
294
|
+
return { reason: "message-event-schema", expected: "valid-message-event" };
|
|
295
|
+
case "tool_execution_start":
|
|
296
|
+
case "tool_execution_update":
|
|
297
|
+
case "tool_execution_end":
|
|
298
|
+
case "host_tool_call":
|
|
299
|
+
case "host_tool_cancel":
|
|
300
|
+
case "tool_approval_request":
|
|
301
|
+
case "tool_approval_cancel":
|
|
302
|
+
return { reason: "tool-event-schema", expected: "valid-tool-event" };
|
|
303
|
+
case "subagent_lifecycle":
|
|
304
|
+
case "subagent_progress":
|
|
305
|
+
case "subagent_event":
|
|
306
|
+
return { reason: "subagent-event-schema", expected: "valid-subagent-event" };
|
|
307
|
+
case "model_changed":
|
|
308
|
+
case "thinking_level_changed":
|
|
309
|
+
case "goal_updated":
|
|
310
|
+
case "todo_reminder":
|
|
311
|
+
case "todo_auto_clear":
|
|
312
|
+
case "available_commands_update":
|
|
313
|
+
case "retry_fallback_applied":
|
|
314
|
+
case "retry_fallback_succeeded":
|
|
315
|
+
return { reason: "configuration-event-schema", expected: "valid-configuration-event" };
|
|
316
|
+
case "extension_ui_request":
|
|
317
|
+
return { reason: "extension-ui-schema", expected: "valid-extension-ui-event" };
|
|
318
|
+
default:
|
|
319
|
+
return { reason: "lifecycle-event-schema", expected: "valid-lifecycle-event" };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function invalidEventDiagnosticMetadata(
|
|
324
|
+
frame: Record<string, unknown>,
|
|
325
|
+
type: string,
|
|
326
|
+
): Pick<
|
|
327
|
+
OmpProtocolViolationDiagnostic,
|
|
328
|
+
"reason" | "eventType" | "frameType" | "field" | "expected" | "actualType"
|
|
329
|
+
> {
|
|
330
|
+
if (!isProtocolEventType(type)) {
|
|
331
|
+
return {
|
|
332
|
+
reason: "unknown-event-type",
|
|
333
|
+
field: "frame.type",
|
|
334
|
+
expected: "known-event-type",
|
|
335
|
+
actualType: "string",
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (type !== "notice") {
|
|
339
|
+
return {
|
|
340
|
+
...eventSchemaClassification(type),
|
|
341
|
+
eventType: type,
|
|
342
|
+
field: "event",
|
|
343
|
+
actualType: "object",
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (typeof frame.level !== "string") {
|
|
347
|
+
return {
|
|
348
|
+
reason: "notice-level-type",
|
|
349
|
+
eventType: "notice",
|
|
350
|
+
frameType: "notice",
|
|
351
|
+
field: "notice.level",
|
|
352
|
+
expected: "notice-level-enum",
|
|
353
|
+
actualType: protocolDiagnosticActualType(frame.level),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (typeof frame.message !== "string") {
|
|
357
|
+
return {
|
|
358
|
+
reason: "notice-message-type",
|
|
359
|
+
eventType: "notice",
|
|
360
|
+
frameType: "notice",
|
|
361
|
+
field: "notice.message",
|
|
362
|
+
expected: "notice-message-string",
|
|
363
|
+
actualType: protocolDiagnosticActualType(frame.message),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
reason: "notice-schema",
|
|
368
|
+
eventType: "notice",
|
|
369
|
+
frameType: "notice",
|
|
370
|
+
field: "event",
|
|
371
|
+
expected: "valid-event-frame",
|
|
372
|
+
actualType: "object",
|
|
373
|
+
};
|
|
109
374
|
}
|
|
110
375
|
|
|
111
376
|
type PendingProtocolViolation = Omit<OmpProtocolViolationDiagnostic, "occurrenceCount"> & {
|
|
112
377
|
occurrenceCount: number;
|
|
113
378
|
};
|
|
379
|
+
type ProtocolViolationKey = `${OmpProtocolViolationCategory}:${OmpProtocolViolationReason}`;
|
|
114
380
|
const MAX_CONTEXT_PERCENT = 1_000_000;
|
|
115
381
|
function boundedJsonString(maxBytes: number, minBytes = 0) {
|
|
116
382
|
return z.string().refine((value) => {
|
|
@@ -1997,8 +2263,9 @@ class OmpRpcProcess {
|
|
|
1997
2263
|
private spawnFailedWithoutProcess = false;
|
|
1998
2264
|
private readyReceived = false;
|
|
1999
2265
|
private outputSettled = false;
|
|
2266
|
+
private turnActive = false;
|
|
2000
2267
|
private readonly pendingProtocolViolations = new Map<
|
|
2001
|
-
|
|
2268
|
+
ProtocolViolationKey,
|
|
2002
2269
|
PendingProtocolViolation
|
|
2003
2270
|
>();
|
|
2004
2271
|
private protocolViolationTimer: TimerHandle | null = null;
|
|
@@ -2101,6 +2368,10 @@ class OmpRpcProcess {
|
|
|
2101
2368
|
this.outputSettled = true;
|
|
2102
2369
|
if (this.lineBytes > 0 || this.discardingLine) {
|
|
2103
2370
|
this.recordProtocolViolation("incomplete-frame", {
|
|
2371
|
+
reason: "output-ended-mid-frame",
|
|
2372
|
+
field: "frame",
|
|
2373
|
+
expected: "complete-json-line",
|
|
2374
|
+
actualType: "partial-frame",
|
|
2104
2375
|
maxByteSize: this.discardingLine ? this.discardedLineBytes : this.lineBytes,
|
|
2105
2376
|
});
|
|
2106
2377
|
}
|
|
@@ -2383,7 +2654,14 @@ class OmpRpcProcess {
|
|
|
2383
2654
|
this.lineParts = [];
|
|
2384
2655
|
this.lineBytes = 0;
|
|
2385
2656
|
this.discardingLine = true;
|
|
2386
|
-
this.recordProtocolViolation("frame-limit", {
|
|
2657
|
+
this.recordProtocolViolation("frame-limit", {
|
|
2658
|
+
reason: "physical-frame-limit",
|
|
2659
|
+
field: "frame.byteLength",
|
|
2660
|
+
expected: "within-byte-limit",
|
|
2661
|
+
actualType: "oversized",
|
|
2662
|
+
maxByteSize: nextBytes,
|
|
2663
|
+
limitBytes: this.physicalFrameLimit,
|
|
2664
|
+
});
|
|
2387
2665
|
return;
|
|
2388
2666
|
}
|
|
2389
2667
|
this.lineParts.push(part);
|
|
@@ -2404,7 +2682,13 @@ class OmpRpcProcess {
|
|
|
2404
2682
|
try {
|
|
2405
2683
|
decoded = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload));
|
|
2406
2684
|
} catch {
|
|
2407
|
-
this.recordProtocolViolation("invalid-json", {
|
|
2685
|
+
this.recordProtocolViolation("invalid-json", {
|
|
2686
|
+
reason: "json-decode",
|
|
2687
|
+
field: "frame",
|
|
2688
|
+
expected: "valid-json",
|
|
2689
|
+
actualType: "invalid-json",
|
|
2690
|
+
maxByteSize: payload.byteLength,
|
|
2691
|
+
});
|
|
2408
2692
|
return;
|
|
2409
2693
|
}
|
|
2410
2694
|
this.receiveDecodedFrame(decoded, payload.byteLength);
|
|
@@ -2427,17 +2711,30 @@ class OmpRpcProcess {
|
|
|
2427
2711
|
frame.data.length % 4 !== 0 ||
|
|
2428
2712
|
!/^[A-Za-z0-9+/]*={0,2}$/u.test(frame.data)
|
|
2429
2713
|
) {
|
|
2430
|
-
this.rejectChunk(
|
|
2714
|
+
this.rejectChunk("chunk-metadata", {
|
|
2715
|
+
field: "chunk",
|
|
2716
|
+
expected: "valid-chunk-frame",
|
|
2717
|
+
actualType: "object",
|
|
2718
|
+
});
|
|
2431
2719
|
return;
|
|
2432
2720
|
}
|
|
2433
2721
|
const decoded = Buffer.from(frame.data, "base64");
|
|
2434
2722
|
if (decoded.byteLength > MAX_CHUNK_BYTES) {
|
|
2435
|
-
this.rejectChunk(
|
|
2723
|
+
this.rejectChunk("chunk-size", {
|
|
2724
|
+
field: "chunk.data",
|
|
2725
|
+
expected: "within-byte-limit",
|
|
2726
|
+
actualType: "oversized",
|
|
2727
|
+
limitBytes: MAX_CHUNK_BYTES,
|
|
2728
|
+
});
|
|
2436
2729
|
return;
|
|
2437
2730
|
}
|
|
2438
2731
|
if (!this.chunk) {
|
|
2439
2732
|
if (frame.index !== 0) {
|
|
2440
|
-
this.rejectChunk(
|
|
2733
|
+
this.rejectChunk("chunk-start-index", {
|
|
2734
|
+
field: "chunk.index",
|
|
2735
|
+
expected: "first-chunk-index-zero",
|
|
2736
|
+
actualType: "out-of-order",
|
|
2737
|
+
});
|
|
2441
2738
|
return;
|
|
2442
2739
|
}
|
|
2443
2740
|
this.chunk = {
|
|
@@ -2446,7 +2743,15 @@ class OmpRpcProcess {
|
|
|
2446
2743
|
byteLength: frame.byteLength,
|
|
2447
2744
|
parts: [],
|
|
2448
2745
|
receivedBytes: 0,
|
|
2449
|
-
timer: setTimeout(
|
|
2746
|
+
timer: setTimeout(
|
|
2747
|
+
() =>
|
|
2748
|
+
this.rejectChunk("chunk-timeout", {
|
|
2749
|
+
field: "chunk.sequence",
|
|
2750
|
+
expected: "chunk-before-deadline",
|
|
2751
|
+
actualType: "timeout",
|
|
2752
|
+
}),
|
|
2753
|
+
CHUNK_STALE_MS,
|
|
2754
|
+
),
|
|
2450
2755
|
};
|
|
2451
2756
|
}
|
|
2452
2757
|
const chunk = this.chunk;
|
|
@@ -2458,14 +2763,22 @@ class OmpRpcProcess {
|
|
|
2458
2763
|
chunk.receivedBytes + decoded.byteLength > chunk.byteLength ||
|
|
2459
2764
|
chunk.receivedBytes + decoded.byteLength > this.reassembledFrameLimit
|
|
2460
2765
|
) {
|
|
2461
|
-
this.rejectChunk(
|
|
2766
|
+
this.rejectChunk("chunk-sequence", {
|
|
2767
|
+
field: "chunk.sequence",
|
|
2768
|
+
expected: "contiguous-chunk-sequence",
|
|
2769
|
+
actualType: "out-of-order",
|
|
2770
|
+
});
|
|
2462
2771
|
return;
|
|
2463
2772
|
}
|
|
2464
2773
|
chunk.parts.push(decoded);
|
|
2465
2774
|
chunk.receivedBytes += decoded.byteLength;
|
|
2466
2775
|
if (chunk.parts.length !== chunk.count) return;
|
|
2467
2776
|
if (chunk.receivedBytes !== chunk.byteLength) {
|
|
2468
|
-
this.rejectChunk(
|
|
2777
|
+
this.rejectChunk("chunk-byte-count", {
|
|
2778
|
+
field: "chunk.byteLength",
|
|
2779
|
+
expected: "declared-chunk-byte-count",
|
|
2780
|
+
actualType: "mismatched",
|
|
2781
|
+
});
|
|
2469
2782
|
return;
|
|
2470
2783
|
}
|
|
2471
2784
|
const reassembled = Buffer.concat(chunk.parts, chunk.receivedBytes);
|
|
@@ -2474,7 +2787,13 @@ class OmpRpcProcess {
|
|
|
2474
2787
|
try {
|
|
2475
2788
|
decodedFrame = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(reassembled));
|
|
2476
2789
|
} catch {
|
|
2477
|
-
this.recordProtocolViolation("invalid-json", {
|
|
2790
|
+
this.recordProtocolViolation("invalid-json", {
|
|
2791
|
+
reason: "chunk-json-decode",
|
|
2792
|
+
field: "chunk.data",
|
|
2793
|
+
expected: "valid-json",
|
|
2794
|
+
actualType: "invalid-json",
|
|
2795
|
+
maxByteSize: reassembled.byteLength,
|
|
2796
|
+
});
|
|
2478
2797
|
return;
|
|
2479
2798
|
}
|
|
2480
2799
|
this.receiveDecodedFrame(decodedFrame, reassembled.byteLength);
|
|
@@ -2500,7 +2819,13 @@ class OmpRpcProcess {
|
|
|
2500
2819
|
const sanitized = sanitizeLiveDisplayFrame(value);
|
|
2501
2820
|
const frame = JsonObjectSchema.safeParse(sanitized);
|
|
2502
2821
|
if (!frame.success) {
|
|
2503
|
-
this.recordProtocolViolation("invalid-envelope", {
|
|
2822
|
+
this.recordProtocolViolation("invalid-envelope", {
|
|
2823
|
+
reason: "object-envelope",
|
|
2824
|
+
field: "frame",
|
|
2825
|
+
expected: "object-envelope",
|
|
2826
|
+
actualType: protocolDiagnosticActualType(sanitized),
|
|
2827
|
+
maxByteSize: rawByteLength,
|
|
2828
|
+
});
|
|
2504
2829
|
return;
|
|
2505
2830
|
}
|
|
2506
2831
|
this.receiveFrame(frame.data, rawByteLength);
|
|
@@ -2525,10 +2850,21 @@ class OmpRpcProcess {
|
|
|
2525
2850
|
const knownPending = rawId ? this.pending.get(rawId) : undefined;
|
|
2526
2851
|
const response = OmpResponseFrameSchema.safeParse(frame);
|
|
2527
2852
|
if (!response.success) {
|
|
2853
|
+
const handledAcceptedFailure =
|
|
2854
|
+
rawId !== undefined && knownPending === undefined
|
|
2855
|
+
? this.emitAcceptedPromptFailure(rawId, frame)
|
|
2856
|
+
: false;
|
|
2857
|
+
if (!handledAcceptedFailure) {
|
|
2858
|
+
this.recordProtocolViolation("invalid-response", {
|
|
2859
|
+
reason: "response-schema",
|
|
2860
|
+
frameType: "response",
|
|
2861
|
+
field: "response",
|
|
2862
|
+
expected: "valid-response-frame",
|
|
2863
|
+
actualType: "object",
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2528
2866
|
if (rawId && knownPending) {
|
|
2529
2867
|
this.takePending(rawId)?.reject(new Error("OMP RPC response is invalid"));
|
|
2530
|
-
} else if (!rawId || !this.emitAcceptedPromptFailure(rawId, frame)) {
|
|
2531
|
-
this.recordProtocolViolation("invalid-response", { frameType: "response" });
|
|
2532
2868
|
}
|
|
2533
2869
|
return;
|
|
2534
2870
|
}
|
|
@@ -2686,7 +3022,13 @@ class OmpRpcProcess {
|
|
|
2686
3022
|
private receiveFrame(frame: Record<string, unknown>, rawByteLength: number): void {
|
|
2687
3023
|
const type = typeof frame.type === "string" && frame.type.length <= 64 ? frame.type : null;
|
|
2688
3024
|
if (!type) {
|
|
2689
|
-
this.recordProtocolViolation("invalid-envelope", {
|
|
3025
|
+
this.recordProtocolViolation("invalid-envelope", {
|
|
3026
|
+
reason: "missing-frame-type",
|
|
3027
|
+
field: "frame.type",
|
|
3028
|
+
expected: "bounded-frame-type",
|
|
3029
|
+
actualType: protocolDiagnosticActualType(frame.type),
|
|
3030
|
+
maxByteSize: rawByteLength,
|
|
3031
|
+
});
|
|
2690
3032
|
return;
|
|
2691
3033
|
}
|
|
2692
3034
|
const safeFrame = mapRuntimeFrameDetails(frame, createOptionalMetadataSanitizer());
|
|
@@ -2700,31 +3042,66 @@ class OmpRpcProcess {
|
|
|
2700
3042
|
4_096,
|
|
2701
3043
|
) === Number.POSITIVE_INFINITY
|
|
2702
3044
|
) {
|
|
2703
|
-
this.recordProtocolViolation("frame-limit", {
|
|
3045
|
+
this.recordProtocolViolation("frame-limit", {
|
|
3046
|
+
reason: "semantic-frame-limit",
|
|
3047
|
+
field: "frame.byteLength",
|
|
3048
|
+
expected: "within-byte-limit",
|
|
3049
|
+
actualType: "oversized",
|
|
3050
|
+
maxByteSize: rawByteLength,
|
|
3051
|
+
limitBytes: MAX_SEMANTIC_FRAME_BYTES,
|
|
3052
|
+
});
|
|
2704
3053
|
return;
|
|
2705
3054
|
}
|
|
2706
3055
|
if (type === "rpc_chunk") {
|
|
2707
3056
|
const chunk = OmpChunkFrameSchema.safeParse(safeFrame);
|
|
2708
|
-
if (!chunk.success)
|
|
2709
|
-
|
|
3057
|
+
if (!chunk.success) {
|
|
3058
|
+
this.rejectChunk("chunk-schema", {
|
|
3059
|
+
field: "chunk",
|
|
3060
|
+
expected: "valid-chunk-frame",
|
|
3061
|
+
actualType: "object",
|
|
3062
|
+
});
|
|
3063
|
+
} else this.receiveChunk(chunk.data);
|
|
2710
3064
|
return;
|
|
2711
3065
|
}
|
|
2712
3066
|
if (this.chunk) {
|
|
2713
3067
|
this.clearChunk();
|
|
2714
|
-
this.recordProtocolViolation("interleaved-chunk"
|
|
3068
|
+
this.recordProtocolViolation("interleaved-chunk", {
|
|
3069
|
+
reason: "frame-interleaved-with-chunk",
|
|
3070
|
+
field: "chunk.sequence",
|
|
3071
|
+
expected: "no-interleaved-frame",
|
|
3072
|
+
actualType: "interleaved",
|
|
3073
|
+
});
|
|
2715
3074
|
}
|
|
2716
3075
|
if (type === "rpc_frame_error") {
|
|
2717
|
-
this.recordProtocolViolation("remote-frame-error", {
|
|
3076
|
+
this.recordProtocolViolation("remote-frame-error", {
|
|
3077
|
+
reason: "remote-frame-error",
|
|
3078
|
+
frameType: "rpc_frame_error",
|
|
3079
|
+
field: "frame",
|
|
3080
|
+
expected: "no-remote-frame-error",
|
|
3081
|
+
actualType: "remote-error",
|
|
3082
|
+
});
|
|
2718
3083
|
return;
|
|
2719
3084
|
}
|
|
2720
3085
|
if (type === "ready") {
|
|
2721
3086
|
if (this.readyReceived) {
|
|
2722
|
-
this.recordProtocolViolation("duplicate-ready", {
|
|
3087
|
+
this.recordProtocolViolation("duplicate-ready", {
|
|
3088
|
+
reason: "ready-already-received",
|
|
3089
|
+
frameType: "ready",
|
|
3090
|
+
field: "ready",
|
|
3091
|
+
expected: "single-ready-frame",
|
|
3092
|
+
actualType: "duplicate",
|
|
3093
|
+
});
|
|
2723
3094
|
return;
|
|
2724
3095
|
}
|
|
2725
3096
|
const ready = OmpReadyFrameSchema.safeParse(safeFrame);
|
|
2726
3097
|
if (!ready.success) {
|
|
2727
|
-
this.recordProtocolViolation("invalid-ready", {
|
|
3098
|
+
this.recordProtocolViolation("invalid-ready", {
|
|
3099
|
+
reason: "ready-schema",
|
|
3100
|
+
frameType: "ready",
|
|
3101
|
+
field: "ready",
|
|
3102
|
+
expected: "valid-ready-frame",
|
|
3103
|
+
actualType: "object",
|
|
3104
|
+
});
|
|
2728
3105
|
} else {
|
|
2729
3106
|
this.readyReceived = true;
|
|
2730
3107
|
this.resolveReady(ready.data);
|
|
@@ -2739,17 +3116,30 @@ class OmpRpcProcess {
|
|
|
2739
3116
|
if (!event.success) {
|
|
2740
3117
|
this.rejectMatchingToolApproval(safeFrame);
|
|
2741
3118
|
if (type === "agent_end" && this.receiveDegradedAgentEnd(safeFrame, false)) return;
|
|
2742
|
-
this.recordProtocolViolation("invalid-event", {
|
|
3119
|
+
this.recordProtocolViolation("invalid-event", {
|
|
3120
|
+
...invalidEventDiagnosticMetadata(safeFrame, type),
|
|
3121
|
+
maxByteSize: rawByteLength,
|
|
3122
|
+
});
|
|
2743
3123
|
return;
|
|
2744
3124
|
}
|
|
2745
3125
|
if (!this.acceptEventState(event.data)) {
|
|
2746
|
-
this.recordProtocolViolation("invalid-event-state", {
|
|
3126
|
+
this.recordProtocolViolation("invalid-event-state", {
|
|
3127
|
+
reason: "event-state-transition",
|
|
3128
|
+
eventType: event.data.type,
|
|
3129
|
+
field: "event.sequence",
|
|
3130
|
+
expected: "valid-event-state-transition",
|
|
3131
|
+
actualType: "out-of-order",
|
|
3132
|
+
maxByteSize: rawByteLength,
|
|
3133
|
+
});
|
|
2747
3134
|
return;
|
|
2748
3135
|
}
|
|
2749
3136
|
this.emit(event.data);
|
|
2750
3137
|
if (event.data.type === "prompt_result" && event.data.id) {
|
|
2751
3138
|
this.acceptedPromptIds.delete(event.data.id);
|
|
2752
3139
|
}
|
|
3140
|
+
if (event.data.type === "turn_end" || event.data.type === "agent_end") {
|
|
3141
|
+
this.turnActive = false;
|
|
3142
|
+
}
|
|
2753
3143
|
if (
|
|
2754
3144
|
event.data.type === "message_end" ||
|
|
2755
3145
|
event.data.type === "turn_end" ||
|
|
@@ -2777,6 +3167,7 @@ class OmpRpcProcess {
|
|
|
2777
3167
|
|
|
2778
3168
|
private acceptEventState(event: z.infer<typeof OmpRuntimeEventSchema>): boolean {
|
|
2779
3169
|
if (event.type === "turn_start") {
|
|
3170
|
+
this.turnActive = true;
|
|
2780
3171
|
this.streamedBlocks.clear();
|
|
2781
3172
|
this.commandTextLength = 0;
|
|
2782
3173
|
this.activeToolCallIds.clear();
|
|
@@ -2846,32 +3237,66 @@ class OmpRpcProcess {
|
|
|
2846
3237
|
return true;
|
|
2847
3238
|
}
|
|
2848
3239
|
|
|
2849
|
-
private
|
|
3240
|
+
private protocolDiagnosticPhase(): OmpProtocolDiagnosticPhase {
|
|
3241
|
+
if (this.closed || this.exited || this.outputSettled) return "closing";
|
|
3242
|
+
if (!this.readyReceived) return "startup";
|
|
3243
|
+
for (const pending of this.pending.values()) {
|
|
3244
|
+
if (pending.command === "negotiate_protocol") return "negotiation";
|
|
3245
|
+
if (pending.command === "prompt") return "active-turn";
|
|
3246
|
+
}
|
|
3247
|
+
if (this.turnActive || this.acceptedPromptIds.size > 0) return "active-turn";
|
|
3248
|
+
return "idle";
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
private rejectChunk(
|
|
3252
|
+
reason: OmpProtocolViolationReason,
|
|
3253
|
+
metadata: Omit<
|
|
3254
|
+
OmpProtocolViolationDiagnostic,
|
|
3255
|
+
"category" | "reason" | "occurrenceCount" | "phase" | "frameType"
|
|
3256
|
+
> = {},
|
|
3257
|
+
): void {
|
|
2850
3258
|
this.clearChunk();
|
|
2851
|
-
this.recordProtocolViolation("invalid-chunk", {
|
|
3259
|
+
this.recordProtocolViolation("invalid-chunk", {
|
|
3260
|
+
reason,
|
|
3261
|
+
frameType: "rpc_chunk",
|
|
3262
|
+
...metadata,
|
|
3263
|
+
});
|
|
2852
3264
|
}
|
|
2853
3265
|
|
|
2854
3266
|
private recordProtocolViolation(
|
|
2855
3267
|
category: OmpProtocolViolationCategory,
|
|
2856
|
-
metadata: Omit<OmpProtocolViolationDiagnostic, "category" | "occurrenceCount"
|
|
3268
|
+
metadata: Omit<OmpProtocolViolationDiagnostic, "category" | "occurrenceCount" | "phase">,
|
|
2857
3269
|
): void {
|
|
3270
|
+
const phase = this.protocolDiagnosticPhase();
|
|
2858
3271
|
if (!this.protocolViolationTimer) {
|
|
2859
|
-
this.emitProtocolViolation({ category, occurrenceCount: 1, ...metadata });
|
|
3272
|
+
this.emitProtocolViolation({ category, occurrenceCount: 1, phase, ...metadata });
|
|
2860
3273
|
this.protocolViolationTimer = setTimeout(
|
|
2861
3274
|
() => this.flushProtocolViolations(),
|
|
2862
3275
|
PROTOCOL_VIOLATION_COALESCE_MS,
|
|
2863
3276
|
);
|
|
2864
3277
|
return;
|
|
2865
3278
|
}
|
|
2866
|
-
const
|
|
3279
|
+
const key = `${category}:${metadata.reason}` as ProtocolViolationKey;
|
|
3280
|
+
const pending = this.pendingProtocolViolations.get(key);
|
|
2867
3281
|
if (!pending) {
|
|
2868
|
-
this.pendingProtocolViolations.set(
|
|
3282
|
+
this.pendingProtocolViolations.set(key, {
|
|
3283
|
+
category,
|
|
3284
|
+
occurrenceCount: 1,
|
|
3285
|
+
phase,
|
|
3286
|
+
...metadata,
|
|
3287
|
+
});
|
|
2869
3288
|
return;
|
|
2870
3289
|
}
|
|
2871
3290
|
pending.occurrenceCount = Math.min(Number.MAX_SAFE_INTEGER, pending.occurrenceCount + 1);
|
|
2872
3291
|
pending.maxByteSize =
|
|
2873
3292
|
Math.max(pending.maxByteSize ?? 0, metadata.maxByteSize ?? 0) || undefined;
|
|
2874
|
-
|
|
3293
|
+
pending.reason = metadata.reason;
|
|
3294
|
+
pending.phase = phase;
|
|
3295
|
+
pending.frameType = metadata.frameType;
|
|
3296
|
+
pending.field = metadata.field;
|
|
3297
|
+
pending.expected = metadata.expected;
|
|
3298
|
+
pending.actualType = metadata.actualType;
|
|
3299
|
+
pending.limitBytes = metadata.limitBytes;
|
|
2875
3300
|
}
|
|
2876
3301
|
|
|
2877
3302
|
private flushProtocolViolations(): void {
|