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