@brunobracaioli/aibrain 0.3.0-beta.14 → 0.3.0-beta.15

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.
@@ -9,6 +9,8 @@ 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;
@@ -113,6 +115,12 @@ async function main() {
113
115
  let monitorLineCount = 0;
114
116
  let monitorByteCount = 0;
115
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;
116
124
  let terminalExitCode = null;
117
125
  let server;
118
126
  let webSocketServer;
@@ -189,7 +197,10 @@ async function main() {
189
197
  child: null,
190
198
  connection: null,
191
199
  retiring: false,
192
- monitorBuffer: Buffer.alloc(0),
200
+ monitorFrameParts: [],
201
+ monitorFrameBytes: 0,
202
+ monitorPendingChunk: Buffer.alloc(0),
203
+ monitorPaused: false,
193
204
  monitorWritable: true,
194
205
  pendingToMonitor: [],
195
206
  queuedFrames: 0,
@@ -232,22 +243,30 @@ async function main() {
232
243
  });
233
244
  child.stdout.on('data', (chunk) => {
234
245
  if (generation.retiring || shuttingDown) return;
235
- generation.monitorBuffer = Buffer.concat([generation.monitorBuffer, Buffer.from(chunk)]);
236
- while (true) {
237
- const newline = generation.monitorBuffer.indexOf(10);
238
- if (newline < 0) break;
239
- const line = generation.monitorBuffer.subarray(0, newline);
240
- generation.monitorBuffer = generation.monitorBuffer.subarray(newline + 1);
241
- handleMonitorLine(generation, line);
242
- }
243
- if (generation.monitorBuffer.length > MAX_FRAME_BYTES) {
244
- abortGeneration(generation, 'monitor_frame_limit');
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;
245
261
  }
262
+ processMonitorChunk(generation, part);
246
263
  });
247
264
  child.on('exit', (code) => {
248
265
  if (generation.retiring || shuttingDown) {
249
266
  generation.pendingToMonitor.length = 0;
250
- generation.monitorBuffer = Buffer.alloc(0);
267
+ generation.monitorFrameParts = [];
268
+ generation.monitorFrameBytes = 0;
269
+ generation.monitorPendingChunk = Buffer.alloc(0);
251
270
  return;
252
271
  }
253
272
  fail(code ?? 0);
@@ -256,6 +275,56 @@ async function main() {
256
275
  };
257
276
 
258
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
+ };
259
328
  const flushGeneration = (generation) => {
260
329
  while (generation.monitorWritable && generation.pendingToMonitor.length > 0) {
261
330
  const frame = generation.pendingToMonitor.shift();
@@ -287,12 +356,31 @@ async function main() {
287
356
  generation.retirementTimer = setTimeout(() => {
288
357
  killOwned(generation.child, 'SIGKILL');
289
358
  generation.pendingToMonitor.length = 0;
290
- generation.monitorBuffer = Buffer.alloc(0);
359
+ generation.monitorFrameParts = [];
360
+ generation.monitorFrameBytes = 0;
361
+ generation.monitorPendingChunk = Buffer.alloc(0);
291
362
  generations.delete(generation);
292
363
  }, 500);
293
364
  };
294
- const abortGeneration = (generation, reason) => {
365
+ const abortGeneration = (generation, reason, bytes = null, method = null) => {
295
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
+ }
296
384
  if (connection?.isCandidate) {
297
385
  rejectCandidate(connection, 1013, reason);
298
386
  return;
@@ -374,6 +462,12 @@ async function main() {
374
462
  monitor_line_count: monitorLineCount,
375
463
  monitor_byte_count: monitorByteCount,
376
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,
377
471
  terminal_exit_code: terminalExitCode,
378
472
  handoff_count: handoffCount,
379
473
  handoff_attempt_count: handoffAttemptCount,
@@ -523,11 +617,12 @@ async function main() {
523
617
  if (line.length === 0) return;
524
618
  const connection = generation.connection;
525
619
  if (!connection || connection.retiring) return;
526
- if (line.length > MAX_FRAME_BYTES) {
527
- abortGeneration(generation, 'monitor_frame_limit');
620
+ if (line.length > MAX_MONITOR_RESPONSE_BYTES) {
621
+ abortGeneration(generation, 'monitor_frame_limit', line.length);
528
622
  return;
529
623
  }
530
624
  let parsed;
625
+ let responseMethod;
531
626
  try {
532
627
  parsed = JSON.parse(line.toString('utf8'));
533
628
  const isInitializeResponse =
@@ -587,6 +682,7 @@ async function main() {
587
682
  connection.pendingRequests.has(parsed.id)
588
683
  ) {
589
684
  const method = connection.pendingRequests.get(parsed.id);
685
+ responseMethod = method;
590
686
  connection.pendingRequests.delete(parsed.id);
591
687
  if (parsed.error === undefined && PRIMARY_DISCOVERY.has(method)) {
592
688
  connection.discoverySatisfied = true;
@@ -617,8 +713,13 @@ async function main() {
617
713
  : 'native_internal_error';
618
714
  }
619
715
  }
716
+ if (line.length > maxMonitorFrameBytes) {
717
+ maxMonitorFrameBytes = line.length;
718
+ maxMonitorFrameMethod = responseMethod ?? null;
719
+ }
620
720
  } catch {
621
- parsed = null;
721
+ abortGeneration(generation, 'monitor_frame_invalid', line.length, responseMethod);
722
+ return;
622
723
  }
623
724
  if (connection.isAuxiliary && parsed === null) {
624
725
  auxiliaryUnknownResponseCount += 1;
@@ -626,11 +727,14 @@ async function main() {
626
727
  return;
627
728
  }
628
729
  if (connection.socket.readyState !== WebSocket.OPEN) return;
629
- if (connection.socket.bufferedAmount + line.length > MAX_QUEUED_BYTES) {
630
- abortGeneration(generation, 'bridge_queue_limit');
631
- return;
632
- }
633
- connection.socket.send(line.toString('utf8'), { binary: false });
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
+ });
634
738
  };
635
739
  const handleClientMessage = (connection, data, isBinary) => {
636
740
  if (shuttingDown) return;
package/dist/cli.mjs CHANGED
@@ -24,7 +24,7 @@ import { codexPolicyJson, makePolicy, publicPolicy, isProfile } from './policy.m
24
24
  import { readDiagnostics } from './claude-observation.mjs';
25
25
  import { readCodexDiagnostics } from './codex-diagnostics.mjs';
26
26
 
27
- const VERSION = '0.3.0-beta.14';
27
+ const VERSION = '0.3.0-beta.15';
28
28
  const ENDPOINT = 'https://mcp.ai-brain.aisac.cloud/mcp';
29
29
  const PACKAGE_ROOT = dirname(fileURLToPath(import.meta.url));
30
30
  const LEGACY_DISCLOSURE_VERSION = 'aibrain-paid-context-v1-2026-10-09';
@@ -2,11 +2,28 @@
2
2
 
3
3
  import { createHash } from 'node:crypto';
4
4
  import { chmod, lstat, mkdir, open, readFile, readdir, rename, rm, stat } from 'node:fs/promises';
5
- import { constants } from 'node:fs';
5
+ import { constants, readFileSync } from 'node:fs';
6
6
  import { join, resolve } from 'node:path';
7
7
 
8
8
  export const CODEX_DIAGNOSTIC_SCHEMA_VERSION = 1;
9
- export const CODEX_DIAGNOSTIC_ADAPTER_VERSION = '0.3.0-beta.13';
9
+ const PACKAGE_VERSION_PATTERN =
10
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/u;
11
+ function packageVersion() {
12
+ for (const packageUrl of [
13
+ new URL('./package.json', import.meta.url),
14
+ new URL('../package.json', import.meta.url),
15
+ ]) {
16
+ try {
17
+ const value = JSON.parse(readFileSync(packageUrl, 'utf8'));
18
+ if (typeof value?.version === 'string' && PACKAGE_VERSION_PATTERN.test(value.version))
19
+ return value.version;
20
+ } catch {
21
+ // Source and packaged dist use different relative package metadata locations.
22
+ }
23
+ }
24
+ throw new Error('codex_diagnostics_package_version_unavailable');
25
+ }
26
+ export const CODEX_DIAGNOSTIC_ADAPTER_VERSION = packageVersion();
10
27
  export const CODEX_DIAGNOSTIC_MAX_LOG_BYTES = 512 * 1024;
11
28
  export const CODEX_DIAGNOSTIC_MAX_LOG_FILES = 3;
12
29
  export const CODEX_DIAGNOSTIC_MAX_RECORDS = 128;
package/dist/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@brunobracaioli/aibrain",
3
- "version": "0.3.0-beta.14",
3
+ "version": "0.3.0-beta.15",
4
4
  "type": "module"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brunobracaioli/aibrain",
3
- "version": "0.3.0-beta.14",
3
+ "version": "0.3.0-beta.15",
4
4
  "private": false,
5
5
  "description": "AI Brain assisted context beta client for Claude Code and Codex",
6
6
  "license": "UNLICENSED",