@brunobracaioli/aibrain 0.3.0-beta.7 → 0.4.0-beta.2
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 +80 -15
- package/dist/assets/claude/.claude-plugin/plugin.json +1 -1
- package/dist/assets/claude/hooks/context-compaction.ts +1100 -77
- package/dist/assets/claude/hooks/native-host-contract-runtime.ts +18 -0
- package/dist/assets/claude/hooks/native-host-contract.test.ts +51 -0
- package/dist/assets/codex-context-compaction.mjs +2179 -194
- package/dist/assets/jev-paid-pilot-terminal.mjs +334 -35
- package/dist/claude-compatibility.mjs +506 -0
- package/dist/claude-observation.mjs +117 -3
- package/dist/cli.mjs +105 -28
- package/dist/codex-compatibility.mjs +481 -0
- package/dist/codex-diagnostics.mjs +39 -3
- package/dist/codex-protocol-fingerprint.json +131 -0
- package/dist/engineering-pack-public-key.json +10 -0
- package/dist/engineering-pack.mjs +722 -0
- package/dist/engineering.mjs +194 -0
- package/dist/launch.mjs +71 -14
- package/dist/mcp.mjs +8 -8
- package/dist/package.json +1 -1
- package/dist/platform.mjs +47 -3
- package/package.json +3 -2
|
@@ -9,9 +9,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
10
10
|
|
|
11
11
|
const MAX_FRAME_BYTES = 16 * 1024 * 1024;
|
|
12
|
+
const MAX_MONITOR_RESPONSE_BYTES = 64 * 1024 * 1024;
|
|
13
|
+
const MAX_MONITOR_PENDING_BYTES = 32 * 1024 * 1024;
|
|
12
14
|
const MAX_QUEUED_FRAMES = 64;
|
|
13
15
|
const MAX_QUEUED_BYTES = 32 * 1024 * 1024;
|
|
14
16
|
const STARTUP_HANDOFF_GRACE_MS = 5_000;
|
|
17
|
+
const MAX_AUXILIARY_CONNECTIONS = 1;
|
|
18
|
+
const MAX_AUXILIARY_MESSAGES = 64;
|
|
19
|
+
const MAX_AUXILIARY_IN_FLIGHT = 32;
|
|
20
|
+
const MAX_AUXILIARY_NOTIFICATIONS = 256;
|
|
21
|
+
const DEFAULT_AUXILIARY_IDLE_TIMEOUT_MS = 120_000;
|
|
15
22
|
const NORMAL_DISCOVERY_CLOSE_CODES = new Set([1000, 1001, 1005]);
|
|
16
23
|
|
|
17
24
|
function repoRoot() {
|
|
@@ -60,7 +67,13 @@ async function main() {
|
|
|
60
67
|
const codexBinary = process.env.CODEX_BIN ?? 'codex';
|
|
61
68
|
const nativeArgs =
|
|
62
69
|
process.argv.slice(2)[0] === '--' ? process.argv.slice(3) : process.argv.slice(2);
|
|
63
|
-
|
|
70
|
+
// The bridge owns an interactive Codex TUI transport. Any native command
|
|
71
|
+
// that reaches this bridge may perform a short-lived read-only bootstrap
|
|
72
|
+
// before attaching its conversation transport. The protocol boundary below
|
|
73
|
+
// is the authority: initialize, same-client identity, successful allowlisted
|
|
74
|
+
// discovery, no started operation, one candidate, and a five-second deadline.
|
|
75
|
+
// Non-interactive Codex subcommands never open the socket and exit normally.
|
|
76
|
+
const allowStartupHandoff = true;
|
|
64
77
|
const childEnv = { ...process.env };
|
|
65
78
|
delete childEnv.JEV_API_KEY;
|
|
66
79
|
delete childEnv.TYPESAFE_API_KEY;
|
|
@@ -71,11 +84,19 @@ async function main() {
|
|
|
71
84
|
const generations = new Set();
|
|
72
85
|
let activeConnection = null;
|
|
73
86
|
let pendingCandidate = null;
|
|
87
|
+
const auxiliaryConnections = new Set();
|
|
74
88
|
let handoffCount = 0;
|
|
75
89
|
let handoffAttemptCount = 0;
|
|
76
90
|
let socketUpgradeCount = 0;
|
|
77
91
|
let secondConnectionRejectCount = 0;
|
|
78
92
|
let candidateRejectCount = 0;
|
|
93
|
+
let auxiliaryConnectionCount = 0;
|
|
94
|
+
let auxiliaryRejectCount = 0;
|
|
95
|
+
let auxiliaryMutationRejectCount = 0;
|
|
96
|
+
let auxiliaryUnknownResponseCount = 0;
|
|
97
|
+
let auxiliaryIdleTimeoutCount = 0;
|
|
98
|
+
let auxiliaryMessageCount = 0;
|
|
99
|
+
let auxiliaryNotificationCount = 0;
|
|
79
100
|
let shuttingDown = false;
|
|
80
101
|
const reportPath = process.env.CODEX_TERMINAL_BRIDGE_REPORT;
|
|
81
102
|
let initializeReceived = false;
|
|
@@ -86,12 +107,20 @@ async function main() {
|
|
|
86
107
|
let turnStartCount = 0;
|
|
87
108
|
let threadResumeCount = 0;
|
|
88
109
|
let threadResumeResponseSeen = false;
|
|
110
|
+
let threadResumeErrorCode = null;
|
|
111
|
+
let threadResumeErrorCategory = null;
|
|
89
112
|
let handoffInitializeAck = false;
|
|
90
113
|
const observedMethods = new Set();
|
|
91
114
|
const observedThreadIds = new Set();
|
|
92
115
|
let monitorLineCount = 0;
|
|
93
116
|
let monitorByteCount = 0;
|
|
94
117
|
let monitorExitCode = null;
|
|
118
|
+
let transportAbortReason = null;
|
|
119
|
+
let transportAbortBytes = null;
|
|
120
|
+
let transportAbortMethod = null;
|
|
121
|
+
let maxMonitorFrameBytes = 0;
|
|
122
|
+
let maxMonitorFrameMethod = null;
|
|
123
|
+
let outputPauseCount = 0;
|
|
95
124
|
let terminalExitCode = null;
|
|
96
125
|
let server;
|
|
97
126
|
let webSocketServer;
|
|
@@ -108,10 +137,12 @@ async function main() {
|
|
|
108
137
|
};
|
|
109
138
|
const READ_ONLY_DISCOVERY = new Set([
|
|
110
139
|
'account/read',
|
|
140
|
+
'account/bedrock/checkGovCloudRequirements',
|
|
141
|
+
'config/read',
|
|
142
|
+
'configRequirements/read',
|
|
111
143
|
'thread/list',
|
|
112
144
|
'thread/read',
|
|
113
145
|
'model/list',
|
|
114
|
-
'configRequirements/read',
|
|
115
146
|
'collaborationMode/list',
|
|
116
147
|
'hooks/list',
|
|
117
148
|
'thread/turns/list',
|
|
@@ -129,6 +160,8 @@ async function main() {
|
|
|
129
160
|
'initialize',
|
|
130
161
|
'initialized',
|
|
131
162
|
'account/read',
|
|
163
|
+
'account/bedrock/checkGovCloudRequirements',
|
|
164
|
+
'config/read',
|
|
132
165
|
'thread/list',
|
|
133
166
|
'thread/read',
|
|
134
167
|
'model/list',
|
|
@@ -164,7 +197,10 @@ async function main() {
|
|
|
164
197
|
child: null,
|
|
165
198
|
connection: null,
|
|
166
199
|
retiring: false,
|
|
167
|
-
|
|
200
|
+
monitorFrameParts: [],
|
|
201
|
+
monitorFrameBytes: 0,
|
|
202
|
+
monitorPendingChunk: Buffer.alloc(0),
|
|
203
|
+
monitorPaused: false,
|
|
168
204
|
monitorWritable: true,
|
|
169
205
|
pendingToMonitor: [],
|
|
170
206
|
queuedFrames: 0,
|
|
@@ -190,6 +226,10 @@ async function main() {
|
|
|
190
226
|
rejectCandidate(generation.connection, 1011, 'candidate_monitor_failed');
|
|
191
227
|
return;
|
|
192
228
|
}
|
|
229
|
+
if (generation.connection?.isAuxiliary) {
|
|
230
|
+
rejectAuxiliary(generation.connection, 'auxiliary_monitor_failed');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
193
233
|
monitorExitCode = code;
|
|
194
234
|
void cleanup().finally(() => process.exit(code));
|
|
195
235
|
};
|
|
@@ -203,22 +243,30 @@ async function main() {
|
|
|
203
243
|
});
|
|
204
244
|
child.stdout.on('data', (chunk) => {
|
|
205
245
|
if (generation.retiring || shuttingDown) return;
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
246
|
+
const part = Buffer.from(chunk);
|
|
247
|
+
if (generation.monitorPaused) {
|
|
248
|
+
if (generation.monitorPendingChunk.length + part.length > MAX_MONITOR_PENDING_BYTES) {
|
|
249
|
+
abortGeneration(
|
|
250
|
+
generation,
|
|
251
|
+
'monitor_backpressure_limit',
|
|
252
|
+
generation.monitorPendingChunk.length + part.length,
|
|
253
|
+
);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
generation.monitorPendingChunk =
|
|
257
|
+
generation.monitorPendingChunk.length === 0
|
|
258
|
+
? part
|
|
259
|
+
: Buffer.concat([generation.monitorPendingChunk, part]);
|
|
260
|
+
return;
|
|
216
261
|
}
|
|
262
|
+
processMonitorChunk(generation, part);
|
|
217
263
|
});
|
|
218
264
|
child.on('exit', (code) => {
|
|
219
265
|
if (generation.retiring || shuttingDown) {
|
|
220
266
|
generation.pendingToMonitor.length = 0;
|
|
221
|
-
generation.
|
|
267
|
+
generation.monitorFrameParts = [];
|
|
268
|
+
generation.monitorFrameBytes = 0;
|
|
269
|
+
generation.monitorPendingChunk = Buffer.alloc(0);
|
|
222
270
|
return;
|
|
223
271
|
}
|
|
224
272
|
fail(code ?? 0);
|
|
@@ -227,6 +275,56 @@ async function main() {
|
|
|
227
275
|
};
|
|
228
276
|
|
|
229
277
|
let initialGeneration;
|
|
278
|
+
const resumeMonitor = (generation) => {
|
|
279
|
+
if (!generation.monitorPaused || generation.retiring || shuttingDown) return;
|
|
280
|
+
generation.monitorPaused = false;
|
|
281
|
+
processMonitorBuffer(generation);
|
|
282
|
+
if (!generation.retiring && !shuttingDown && !generation.monitorPaused)
|
|
283
|
+
generation.child.stdout.resume();
|
|
284
|
+
};
|
|
285
|
+
const pauseMonitor = (generation) => {
|
|
286
|
+
if (generation.monitorPaused || generation.retiring || shuttingDown) return;
|
|
287
|
+
generation.monitorPaused = true;
|
|
288
|
+
outputPauseCount += 1;
|
|
289
|
+
generation.child.stdout.pause();
|
|
290
|
+
};
|
|
291
|
+
const processMonitorChunk = (generation, incoming) => {
|
|
292
|
+
if (generation.retiring || shuttingDown) return;
|
|
293
|
+
let offset = 0;
|
|
294
|
+
while (offset < incoming.length && !generation.monitorPaused && !generation.retiring) {
|
|
295
|
+
const newline = incoming.indexOf(10, offset);
|
|
296
|
+
const end = newline < 0 ? incoming.length : newline;
|
|
297
|
+
const part = incoming.subarray(offset, end);
|
|
298
|
+
if (generation.monitorFrameBytes + part.length > MAX_MONITOR_RESPONSE_BYTES) {
|
|
299
|
+
abortGeneration(
|
|
300
|
+
generation,
|
|
301
|
+
'monitor_frame_limit',
|
|
302
|
+
generation.monitorFrameBytes + part.length,
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (part.length > 0) {
|
|
307
|
+
generation.monitorFrameParts.push(part);
|
|
308
|
+
generation.monitorFrameBytes += part.length;
|
|
309
|
+
}
|
|
310
|
+
offset = newline < 0 ? incoming.length : newline + 1;
|
|
311
|
+
if (newline >= 0) {
|
|
312
|
+
const line = Buffer.concat(generation.monitorFrameParts, generation.monitorFrameBytes);
|
|
313
|
+
generation.monitorFrameParts = [];
|
|
314
|
+
generation.monitorFrameBytes = 0;
|
|
315
|
+
handleMonitorLine(generation, line);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (offset < incoming.length) generation.monitorPendingChunk = incoming.subarray(offset);
|
|
319
|
+
};
|
|
320
|
+
const processMonitorBuffer = (generation) => {
|
|
321
|
+
if (generation.retiring || shuttingDown) return;
|
|
322
|
+
if (generation.monitorPendingChunk.length > 0) {
|
|
323
|
+
const pending = generation.monitorPendingChunk;
|
|
324
|
+
generation.monitorPendingChunk = Buffer.alloc(0);
|
|
325
|
+
processMonitorChunk(generation, pending);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
230
328
|
const flushGeneration = (generation) => {
|
|
231
329
|
while (generation.monitorWritable && generation.pendingToMonitor.length > 0) {
|
|
232
330
|
const frame = generation.pendingToMonitor.shift();
|
|
@@ -258,16 +356,39 @@ async function main() {
|
|
|
258
356
|
generation.retirementTimer = setTimeout(() => {
|
|
259
357
|
killOwned(generation.child, 'SIGKILL');
|
|
260
358
|
generation.pendingToMonitor.length = 0;
|
|
261
|
-
generation.
|
|
359
|
+
generation.monitorFrameParts = [];
|
|
360
|
+
generation.monitorFrameBytes = 0;
|
|
361
|
+
generation.monitorPendingChunk = Buffer.alloc(0);
|
|
262
362
|
generations.delete(generation);
|
|
263
363
|
}, 500);
|
|
264
364
|
};
|
|
265
|
-
const abortGeneration = (generation, reason) => {
|
|
365
|
+
const abortGeneration = (generation, reason, bytes = null, method = null) => {
|
|
266
366
|
const connection = generation.connection;
|
|
367
|
+
const primaryConnection = connection?.isCandidate !== true && connection?.isAuxiliary !== true;
|
|
368
|
+
if (transportAbortReason === null || primaryConnection) {
|
|
369
|
+
transportAbortReason = reason;
|
|
370
|
+
transportAbortBytes = Number.isSafeInteger(bytes) ? bytes : null;
|
|
371
|
+
transportAbortMethod = typeof method === 'string' ? method : null;
|
|
372
|
+
}
|
|
373
|
+
if (primaryConnection && cleanupInitiator === 'unknown') {
|
|
374
|
+
cleanupInitiator = `transport_${reason}`;
|
|
375
|
+
process.stderr.write(
|
|
376
|
+
`${JSON.stringify({
|
|
377
|
+
event: 'codex_terminal_bridge_error',
|
|
378
|
+
category: reason,
|
|
379
|
+
...(transportAbortBytes === null ? {} : { bytes: transportAbortBytes }),
|
|
380
|
+
...(transportAbortMethod === null ? {} : { method: transportAbortMethod }),
|
|
381
|
+
})}\n`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
267
384
|
if (connection?.isCandidate) {
|
|
268
385
|
rejectCandidate(connection, 1013, reason);
|
|
269
386
|
return;
|
|
270
387
|
}
|
|
388
|
+
if (connection?.isAuxiliary) {
|
|
389
|
+
rejectAuxiliary(connection, reason);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
271
392
|
if (connection) connection.retiring = true;
|
|
272
393
|
cleanupGeneration(generation);
|
|
273
394
|
connection?.socket.close(1013, reason);
|
|
@@ -308,7 +429,7 @@ async function main() {
|
|
|
308
429
|
),
|
|
309
430
|
new Promise((resolve) => setTimeout(resolve, 500)),
|
|
310
431
|
]);
|
|
311
|
-
for (const connection of [activeConnection, pendingCandidate]) {
|
|
432
|
+
for (const connection of [activeConnection, pendingCandidate, ...auxiliaryConnections]) {
|
|
312
433
|
if (connection?.socket.readyState === WebSocket.OPEN)
|
|
313
434
|
connection.socket.close(1001, 'bridge stopped');
|
|
314
435
|
connection?.socket.terminate();
|
|
@@ -333,12 +454,20 @@ async function main() {
|
|
|
333
454
|
turn_start_count: turnStartCount,
|
|
334
455
|
thread_resume_count: threadResumeCount,
|
|
335
456
|
thread_resume_response_seen: threadResumeResponseSeen,
|
|
457
|
+
thread_resume_error_code: threadResumeErrorCode,
|
|
458
|
+
thread_resume_error_category: threadResumeErrorCategory,
|
|
336
459
|
handoff_initialize_ack: handoffInitializeAck,
|
|
337
460
|
observed_methods: [...observedMethods],
|
|
338
461
|
observed_thread_ids: [...observedThreadIds],
|
|
339
462
|
monitor_line_count: monitorLineCount,
|
|
340
463
|
monitor_byte_count: monitorByteCount,
|
|
341
464
|
monitor_exit_code: monitorExitCode,
|
|
465
|
+
transport_abort_reason: transportAbortReason,
|
|
466
|
+
transport_abort_bytes: transportAbortBytes,
|
|
467
|
+
transport_abort_method: transportAbortMethod,
|
|
468
|
+
max_monitor_frame_bytes: maxMonitorFrameBytes,
|
|
469
|
+
max_monitor_frame_method: maxMonitorFrameMethod,
|
|
470
|
+
output_pause_count: outputPauseCount,
|
|
342
471
|
terminal_exit_code: terminalExitCode,
|
|
343
472
|
handoff_count: handoffCount,
|
|
344
473
|
handoff_attempt_count: handoffAttemptCount,
|
|
@@ -346,6 +475,13 @@ async function main() {
|
|
|
346
475
|
socket_upgrade_count: socketUpgradeCount,
|
|
347
476
|
second_connection_reject_count: secondConnectionRejectCount,
|
|
348
477
|
candidate_reject_count: candidateRejectCount,
|
|
478
|
+
auxiliary_connection_count: auxiliaryConnectionCount,
|
|
479
|
+
auxiliary_reject_count: auxiliaryRejectCount,
|
|
480
|
+
auxiliary_mutation_reject_count: auxiliaryMutationRejectCount,
|
|
481
|
+
auxiliary_unknown_response_count: auxiliaryUnknownResponseCount,
|
|
482
|
+
auxiliary_idle_timeout_count: auxiliaryIdleTimeoutCount,
|
|
483
|
+
auxiliary_message_count: auxiliaryMessageCount,
|
|
484
|
+
auxiliary_notification_count: auxiliaryNotificationCount,
|
|
349
485
|
cleanup_initiator: cleanupInitiator,
|
|
350
486
|
last_socket_close_code: lastSocketCloseCode,
|
|
351
487
|
terminal_alive_at_socket_close: terminalAliveAtSocketClose,
|
|
@@ -406,6 +542,24 @@ async function main() {
|
|
|
406
542
|
connection.initialized === true &&
|
|
407
543
|
connection.initializeAck === true &&
|
|
408
544
|
(connection.socket.readyState === WebSocket.OPEN || connection.discoveryClosed === true);
|
|
545
|
+
const auxiliaryIdleTimeoutMs = (() => {
|
|
546
|
+
const value = Number.parseInt(process.env.CODEX_AUXILIARY_IDLE_TIMEOUT_MS ?? '', 10);
|
|
547
|
+
return Number.isSafeInteger(value) && value >= 1_000 && value <= 300_000
|
|
548
|
+
? value
|
|
549
|
+
: DEFAULT_AUXILIARY_IDLE_TIMEOUT_MS;
|
|
550
|
+
})();
|
|
551
|
+
const rejectAuxiliary = (connection, reason = 'auxiliary_rejected') => {
|
|
552
|
+
if (!connection || connection.retiring || !connection.isAuxiliary) return;
|
|
553
|
+
connection.retiring = true;
|
|
554
|
+
if (connection.auxiliaryTimer !== null) clearTimeout(connection.auxiliaryTimer);
|
|
555
|
+
auxiliaryConnections.delete(connection);
|
|
556
|
+
auxiliaryRejectCount += 1;
|
|
557
|
+
if (reason === 'auxiliary_idle_timeout') auxiliaryIdleTimeoutCount += 1;
|
|
558
|
+
if (connection.socket.readyState === WebSocket.OPEN)
|
|
559
|
+
connection.socket.close(1008, reason);
|
|
560
|
+
else connection.socket.terminate();
|
|
561
|
+
cleanupGeneration(connection.generation);
|
|
562
|
+
};
|
|
409
563
|
const rejectCandidate = (connection, statusCode = 1008, reason = 'handoff_rejected') => {
|
|
410
564
|
if (!connection || connection.retiring) return;
|
|
411
565
|
if (connection?.candidateTimer) clearTimeout(connection.candidateTimer);
|
|
@@ -463,14 +617,20 @@ async function main() {
|
|
|
463
617
|
if (line.length === 0) return;
|
|
464
618
|
const connection = generation.connection;
|
|
465
619
|
if (!connection || connection.retiring) return;
|
|
466
|
-
if (line.length >
|
|
467
|
-
abortGeneration(generation, 'monitor_frame_limit');
|
|
620
|
+
if (line.length > MAX_MONITOR_RESPONSE_BYTES) {
|
|
621
|
+
abortGeneration(generation, 'monitor_frame_limit', line.length);
|
|
468
622
|
return;
|
|
469
623
|
}
|
|
470
624
|
let parsed;
|
|
625
|
+
let responseMethod;
|
|
471
626
|
try {
|
|
472
627
|
parsed = JSON.parse(line.toString('utf8'));
|
|
473
|
-
|
|
628
|
+
const isInitializeResponse =
|
|
629
|
+
connection.initializeId !== null &&
|
|
630
|
+
parsed.id === connection.initializeId &&
|
|
631
|
+
connection.initializeResponseConsumed === false;
|
|
632
|
+
if (isInitializeResponse) {
|
|
633
|
+
connection.initializeResponseConsumed = true;
|
|
474
634
|
initializeResponseSeen = true;
|
|
475
635
|
initializeErrorCode = parsed.error?.code ?? null;
|
|
476
636
|
if (parsed.error === undefined && parsed.result !== undefined) {
|
|
@@ -497,12 +657,32 @@ async function main() {
|
|
|
497
657
|
const isJsonRpcResponse =
|
|
498
658
|
Object.prototype.hasOwnProperty.call(parsed, 'result') ||
|
|
499
659
|
Object.prototype.hasOwnProperty.call(parsed, 'error');
|
|
660
|
+
if (connection.isAuxiliary && parsed.method !== undefined && parsed.id === undefined) {
|
|
661
|
+
auxiliaryNotificationCount += 1;
|
|
662
|
+
connection.auxiliaryNotificationCount += 1;
|
|
663
|
+
if (connection.auxiliaryNotificationCount > MAX_AUXILIARY_NOTIFICATIONS) {
|
|
664
|
+
rejectAuxiliary(connection, 'auxiliary_notification_limit');
|
|
665
|
+
}
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (
|
|
669
|
+
connection.isAuxiliary &&
|
|
670
|
+
(parsed.method !== undefined ||
|
|
671
|
+
(isJsonRpcResponse &&
|
|
672
|
+
!isInitializeResponse &&
|
|
673
|
+
!connection.pendingRequests.has(parsed.id)))
|
|
674
|
+
) {
|
|
675
|
+
auxiliaryUnknownResponseCount += 1;
|
|
676
|
+
rejectAuxiliary(connection, 'auxiliary_response_rejected');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
500
679
|
if (
|
|
501
680
|
isJsonRpcResponse &&
|
|
502
681
|
parsed.id !== undefined &&
|
|
503
682
|
connection.pendingRequests.has(parsed.id)
|
|
504
683
|
) {
|
|
505
684
|
const method = connection.pendingRequests.get(parsed.id);
|
|
685
|
+
responseMethod = method;
|
|
506
686
|
connection.pendingRequests.delete(parsed.id);
|
|
507
687
|
if (parsed.error === undefined && PRIMARY_DISCOVERY.has(method)) {
|
|
508
688
|
connection.discoverySatisfied = true;
|
|
@@ -516,16 +696,45 @@ async function main() {
|
|
|
516
696
|
) {
|
|
517
697
|
connection.threadResumeRequestIds.delete(parsed.id);
|
|
518
698
|
if (parsed.error === undefined) threadResumeResponseSeen = true;
|
|
699
|
+
else {
|
|
700
|
+
if (Number.isSafeInteger(parsed.error?.code)) threadResumeErrorCode = parsed.error.code;
|
|
701
|
+
const message =
|
|
702
|
+
typeof parsed.error?.message === 'string' ? parsed.error.message.toLowerCase() : '';
|
|
703
|
+
threadResumeErrorCategory = /no.?rollout|rollout.*not.?found/u.test(message)
|
|
704
|
+
? 'no_rollout_found'
|
|
705
|
+
: /thread.*not.?found|unknown.*thread/u.test(message)
|
|
706
|
+
? 'thread_not_found'
|
|
707
|
+
: /sqlite|database|locked|busy/u.test(message)
|
|
708
|
+
? 'database_state'
|
|
709
|
+
: /config|invalid.*param|parameter/u.test(message)
|
|
710
|
+
? 'protocol_configuration'
|
|
711
|
+
: /model|provider|auth|credential/u.test(message)
|
|
712
|
+
? 'model_or_auth'
|
|
713
|
+
: 'native_internal_error';
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
if (line.length > maxMonitorFrameBytes) {
|
|
717
|
+
maxMonitorFrameBytes = line.length;
|
|
718
|
+
maxMonitorFrameMethod = responseMethod ?? null;
|
|
519
719
|
}
|
|
520
720
|
} catch {
|
|
521
|
-
|
|
721
|
+
abortGeneration(generation, 'monitor_frame_invalid', line.length, responseMethod);
|
|
722
|
+
return;
|
|
522
723
|
}
|
|
523
|
-
if (connection.
|
|
524
|
-
|
|
525
|
-
|
|
724
|
+
if (connection.isAuxiliary && parsed === null) {
|
|
725
|
+
auxiliaryUnknownResponseCount += 1;
|
|
726
|
+
rejectAuxiliary(connection, 'auxiliary_response_rejected');
|
|
526
727
|
return;
|
|
527
728
|
}
|
|
528
|
-
connection.socket.
|
|
729
|
+
if (connection.socket.readyState !== WebSocket.OPEN) return;
|
|
730
|
+
pauseMonitor(generation);
|
|
731
|
+
connection.socket.send(line.toString('utf8'), { binary: false }, (error) => {
|
|
732
|
+
if (error && !generation.retiring && !shuttingDown) {
|
|
733
|
+
abortGeneration(generation, 'bridge_output_failed', line.length, responseMethod);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
resumeMonitor(generation);
|
|
737
|
+
});
|
|
529
738
|
};
|
|
530
739
|
const handleClientMessage = (connection, data, isBinary) => {
|
|
531
740
|
if (shuttingDown) return;
|
|
@@ -535,18 +744,29 @@ async function main() {
|
|
|
535
744
|
const parsed = JSON.parse(frame.toString('utf8'));
|
|
536
745
|
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
537
746
|
throw new Error('bridge_message_not_object');
|
|
747
|
+
if (
|
|
748
|
+
connection.isAuxiliary &&
|
|
749
|
+
(typeof parsed.method !== 'string' ||
|
|
750
|
+
Object.prototype.hasOwnProperty.call(parsed, 'result') ||
|
|
751
|
+
Object.prototype.hasOwnProperty.call(parsed, 'error'))
|
|
752
|
+
) {
|
|
753
|
+
auxiliaryMutationRejectCount += 1;
|
|
754
|
+
rejectAuxiliary(connection, 'auxiliary_client_frame_rejected');
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
538
757
|
const normalizedFrame = Buffer.from(JSON.stringify(parsed), 'utf8');
|
|
758
|
+
const wasInitialized = connection.initialized;
|
|
539
759
|
if (!connection.initialized) {
|
|
540
760
|
const initialize = parseInitialize(normalizedFrame.toString('utf8'));
|
|
541
761
|
if (
|
|
542
|
-
connection.isCandidate &&
|
|
762
|
+
(connection.isCandidate || connection.isAuxiliary) &&
|
|
543
763
|
!sameClient(activeConnection?.clientInfo, initialize.params.clientInfo)
|
|
544
764
|
)
|
|
545
765
|
throw new Error('handoff_client_mismatch');
|
|
546
766
|
connection.initialized = true;
|
|
547
767
|
connection.initializeId = initialize.id;
|
|
548
768
|
connection.clientInfo = initialize.params.clientInfo;
|
|
549
|
-
if (!connection.isCandidate) {
|
|
769
|
+
if (!connection.isCandidate && !connection.isAuxiliary) {
|
|
550
770
|
initializeReceived = true;
|
|
551
771
|
experimentalApi = initialize.params.capabilities.experimentalApi === true;
|
|
552
772
|
}
|
|
@@ -555,8 +775,30 @@ async function main() {
|
|
|
555
775
|
connection.generation = spawnMonitor();
|
|
556
776
|
connection.generation.connection = connection;
|
|
557
777
|
}
|
|
778
|
+
if (connection.isAuxiliary && connection.generation === null) {
|
|
779
|
+
connection.generation = spawnMonitor();
|
|
780
|
+
connection.generation.connection = connection;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (connection.isAuxiliary) {
|
|
784
|
+
auxiliaryMessageCount += 1;
|
|
785
|
+
connection.auxiliaryMessageCount += 1;
|
|
786
|
+
if (connection.auxiliaryMessageCount > MAX_AUXILIARY_MESSAGES) {
|
|
787
|
+
rejectAuxiliary(connection, 'auxiliary_message_limit');
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (connection.auxiliaryTimer !== null) clearTimeout(connection.auxiliaryTimer);
|
|
791
|
+
connection.auxiliaryTimer = setTimeout(
|
|
792
|
+
() => rejectAuxiliary(connection, 'auxiliary_idle_timeout'),
|
|
793
|
+
auxiliaryIdleTimeoutMs,
|
|
794
|
+
);
|
|
558
795
|
}
|
|
559
|
-
if (parsed.method === '
|
|
796
|
+
if (connection.isAuxiliary && wasInitialized && parsed.method === 'initialize') {
|
|
797
|
+
auxiliaryMutationRejectCount += 1;
|
|
798
|
+
rejectAuxiliary(connection, 'auxiliary_duplicate_initialize');
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
if (parsed.method === 'turn/start' && !connection.isAuxiliary) turnStartCount += 1;
|
|
560
802
|
if (typeof parsed.method === 'string') {
|
|
561
803
|
if (DIAGNOSTIC_METHODS.has(parsed.method)) {
|
|
562
804
|
if (observedMethods.size < MAX_DIAGNOSTIC_ENTRIES) observedMethods.add(parsed.method);
|
|
@@ -565,7 +807,33 @@ async function main() {
|
|
|
565
807
|
}
|
|
566
808
|
if (parsed.method === 'thread/resume') threadResumeCount += 1;
|
|
567
809
|
if (parsed.method === 'initialize' || parsed.method === 'initialized') {
|
|
810
|
+
if (connection.isAuxiliary && parsed.method === 'initialized') {
|
|
811
|
+
if (connection.initializedNotificationSeen) {
|
|
812
|
+
auxiliaryMutationRejectCount += 1;
|
|
813
|
+
rejectAuxiliary(connection, 'auxiliary_duplicate_initialized');
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
connection.initializedNotificationSeen = true;
|
|
817
|
+
}
|
|
568
818
|
// The notification is part of the handshake and does not affect startup eligibility.
|
|
819
|
+
} else if (connection.isAuxiliary) {
|
|
820
|
+
if (!READ_ONLY_DISCOVERY.has(parsed.method)) {
|
|
821
|
+
auxiliaryMutationRejectCount += 1;
|
|
822
|
+
rejectAuxiliary(connection, 'auxiliary_mutation_rejected');
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const validRequestId =
|
|
826
|
+
(typeof parsed.id === 'string' && parsed.id.length > 0 && parsed.id.length <= 128) ||
|
|
827
|
+
(Number.isSafeInteger(parsed.id) && parsed.id >= 0);
|
|
828
|
+
if (
|
|
829
|
+
!validRequestId ||
|
|
830
|
+
connection.pendingRequests.has(parsed.id) ||
|
|
831
|
+
connection.pendingRequests.size >= MAX_AUXILIARY_IN_FLIGHT
|
|
832
|
+
) {
|
|
833
|
+
rejectAuxiliary(connection, 'auxiliary_request_limit');
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
connection.pendingRequests.set(parsed.id, parsed.method);
|
|
569
837
|
} else if (READ_ONLY_DISCOVERY.has(parsed.method)) {
|
|
570
838
|
const validRequestId =
|
|
571
839
|
(typeof parsed.id === 'string' && parsed.id.length > 0 && parsed.id.length <= 128) ||
|
|
@@ -607,18 +875,22 @@ async function main() {
|
|
|
607
875
|
}
|
|
608
876
|
} catch (error) {
|
|
609
877
|
if (connection.isCandidate) rejectCandidate(connection, 1008, safeError(error));
|
|
878
|
+
else if (connection.isAuxiliary) rejectAuxiliary(connection, safeError(error));
|
|
610
879
|
else connection.socket.close(1008, safeError(error));
|
|
611
880
|
}
|
|
612
881
|
};
|
|
613
|
-
const attachConnection = (webSocket, isCandidate) => {
|
|
882
|
+
const attachConnection = (webSocket, isCandidate, isAuxiliary = false) => {
|
|
614
883
|
const connection = {
|
|
615
884
|
socket: webSocket,
|
|
616
|
-
generation: isCandidate ? null : initialGeneration,
|
|
885
|
+
generation: isCandidate || isAuxiliary ? null : initialGeneration,
|
|
617
886
|
isCandidate,
|
|
618
|
-
|
|
887
|
+
isAuxiliary,
|
|
888
|
+
isActive: !isCandidate && !isAuxiliary,
|
|
619
889
|
retiring: false,
|
|
620
890
|
initialized: false,
|
|
621
891
|
initializeId: null,
|
|
892
|
+
initializeResponseConsumed: false,
|
|
893
|
+
initializedNotificationSeen: false,
|
|
622
894
|
initializeAck: false,
|
|
623
895
|
clientInfo: null,
|
|
624
896
|
pendingRequests: new Map(),
|
|
@@ -631,15 +903,27 @@ async function main() {
|
|
|
631
903
|
handoffComplete: false,
|
|
632
904
|
discoveryClosed: false,
|
|
633
905
|
candidateTimer: null,
|
|
906
|
+
auxiliaryTimer: null,
|
|
907
|
+
auxiliaryMessageCount: 0,
|
|
908
|
+
auxiliaryNotificationCount: 0,
|
|
634
909
|
};
|
|
635
|
-
if (!isCandidate) {
|
|
910
|
+
if (!isCandidate && !isAuxiliary) {
|
|
636
911
|
activeConnection = connection;
|
|
637
912
|
initialGeneration.connection = connection;
|
|
638
913
|
}
|
|
914
|
+
if (isAuxiliary) {
|
|
915
|
+
auxiliaryConnections.add(connection);
|
|
916
|
+
auxiliaryConnectionCount += 1;
|
|
917
|
+
connection.auxiliaryTimer = setTimeout(
|
|
918
|
+
() => rejectAuxiliary(connection, 'auxiliary_idle_timeout'),
|
|
919
|
+
auxiliaryIdleTimeoutMs,
|
|
920
|
+
);
|
|
921
|
+
}
|
|
639
922
|
webSocket.on('message', (data, isBinary) => handleClientMessage(connection, data, isBinary));
|
|
640
923
|
webSocket.on('error', () => {
|
|
641
924
|
if (connection.retiring || shuttingDown) return;
|
|
642
925
|
if (connection.isCandidate) rejectCandidate(connection, 1011, 'candidate_socket_error');
|
|
926
|
+
else if (connection.isAuxiliary) rejectAuxiliary(connection, 'auxiliary_socket_error');
|
|
643
927
|
else {
|
|
644
928
|
cleanupInitiator = 'socket_error';
|
|
645
929
|
recordLifecycle('socket_error');
|
|
@@ -652,6 +936,12 @@ async function main() {
|
|
|
652
936
|
lastSocketCloseCode = code;
|
|
653
937
|
terminalAliveAtSocketClose = terminal?.exitCode === null;
|
|
654
938
|
recordLifecycle('socket_close');
|
|
939
|
+
if (connection.isAuxiliary) {
|
|
940
|
+
if (connection.auxiliaryTimer !== null) clearTimeout(connection.auxiliaryTimer);
|
|
941
|
+
auxiliaryConnections.delete(connection);
|
|
942
|
+
cleanupGeneration(connection.generation);
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
655
945
|
if (connection.isCandidate) {
|
|
656
946
|
rejectCandidate(connection);
|
|
657
947
|
return;
|
|
@@ -705,14 +995,23 @@ async function main() {
|
|
|
705
995
|
closeSocket(socket, 403, 'origin_rejected');
|
|
706
996
|
return;
|
|
707
997
|
}
|
|
708
|
-
const isCandidate = activeConnection !== null;
|
|
709
|
-
|
|
998
|
+
const isCandidate = activeConnection !== null && canHandoff(activeConnection);
|
|
999
|
+
const isAuxiliary =
|
|
1000
|
+
activeConnection !== null &&
|
|
1001
|
+
!isCandidate &&
|
|
1002
|
+
activeConnection.initialized === true &&
|
|
1003
|
+
activeConnection.startedOperation === true;
|
|
1004
|
+
if (
|
|
1005
|
+
activeConnection !== null &&
|
|
1006
|
+
!isCandidate &&
|
|
1007
|
+
(!isAuxiliary || auxiliaryConnections.size >= MAX_AUXILIARY_CONNECTIONS)
|
|
1008
|
+
) {
|
|
710
1009
|
secondConnectionRejectCount += 1;
|
|
711
1010
|
closeSocket(socket, 409, 'second_connection_rejected');
|
|
712
1011
|
return;
|
|
713
1012
|
}
|
|
714
1013
|
webSocketServer.handleUpgrade(request, socket, head, (webSocket) => {
|
|
715
|
-
const connection = attachConnection(webSocket, isCandidate);
|
|
1014
|
+
const connection = attachConnection(webSocket, isCandidate, isAuxiliary);
|
|
716
1015
|
if (isCandidate) {
|
|
717
1016
|
pendingCandidate = connection;
|
|
718
1017
|
connection.candidateTimer = setTimeout(
|