@obtoai/agent-bridge 0.1.0-beta.17 → 0.1.0-beta.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obtoai/agent-bridge",
3
- "version": "0.1.0-beta.17",
3
+ "version": "0.1.0-beta.18",
4
4
  "description": "Local consumer for the OBTO Agent Bridge. Receives bridge events over SSE and drives a coding agent (Claude Code or OpenAI Codex) on your machine.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "OBTO Inc.",
@@ -28,7 +28,10 @@ const CODEX_DIR = path.join(os.homedir(), '.codex', 'sessions');
28
28
 
29
29
  const PREVIEW_MAX_CHARS = 200;
30
30
  const TITLE_MAX_CHARS = 80;
31
- const TAIL_READ_BYTES = 16384; // last 16KB covers the last message AND the late-appended ai-title record
31
+ const TAIL_READ_BYTES = 16384; // fixed-budget tail (cheap; used for ai-title + lastMessage)
32
+ const TAIL_STREAM_MAX_BYTES = 524288;// streaming tail cap — 512KB max read for recents (Phase 6.2.4)
33
+ const TAIL_STREAM_CHUNK = 65536; // 64KB chunk size for the backward stream
34
+ const TAIL_STREAM_MIN_MESSAGES = 10; // stop reading once we have this many text-bearing lines
32
35
  const TITLE_MAX_LINES = 40; // scan up to this many lines for a real first message
33
36
  const TITLE_MAX_BYTES = 65536; // hard ceiling per file even if MAX_LINES never reached
34
37
 
@@ -53,6 +56,59 @@ const readTail = (filePath, maxBytes = TAIL_READ_BYTES) => {
53
56
  }
54
57
  };
55
58
 
59
+ // Phase 6.2.4 — streaming tail. For sessions where individual JSONL records
60
+ // exceed the fixed 16KB budget (long assistant messages with lots of inline
61
+ // code), the fixed tail returns 0 parseable lines. This reads backward in
62
+ // 64KB chunks until we either have ≥TAIL_STREAM_MIN_MESSAGES text-bearing
63
+ // user/assistant lines OR hit TAIL_STREAM_MAX_BYTES (512KB) — whichever
64
+ // comes first. The first partial line at the buffer boundary is dropped
65
+ // because it won't JSON.parse cleanly; that's fine, the next chunk will
66
+ // pick it up complete.
67
+ const readTailUntilMessages = (filePath) => {
68
+ let fd = null;
69
+ try {
70
+ const stat = fs.statSync(filePath);
71
+ if (stat.size === 0) return '';
72
+ fd = fs.openSync(filePath, 'r');
73
+ let buffer = '';
74
+ let pos = stat.size;
75
+ while (pos > 0 && buffer.length < TAIL_STREAM_MAX_BYTES) {
76
+ const readSize = Math.min(TAIL_STREAM_CHUNK, pos);
77
+ pos -= readSize;
78
+ const chunk = Buffer.alloc(readSize);
79
+ fs.readSync(fd, chunk, 0, readSize, pos);
80
+ buffer = chunk.toString('utf8') + buffer;
81
+ // Cheap completeness check: count parseable user/assistant lines with text.
82
+ const lines = buffer.split(/\r?\n/);
83
+ let count = 0;
84
+ for (const line of lines) {
85
+ if (!line.trim()) continue;
86
+ let obj;
87
+ try { obj = JSON.parse(line); } catch (_) { continue; }
88
+ const role = (obj && (obj.role || (obj.message && obj.message.role))) || null;
89
+ if (role !== 'user' && role !== 'assistant') continue;
90
+ const raw = (obj.message && obj.message.content) || obj.content;
91
+ if (!raw) continue;
92
+ if (typeof raw === 'string') {
93
+ if (raw.trim()) count++;
94
+ } else if (Array.isArray(raw)) {
95
+ for (const p of raw) {
96
+ if (p && (p.type === 'text' || typeof p.text === 'string') && String(p.text || '').trim()) {
97
+ count++; break;
98
+ }
99
+ }
100
+ }
101
+ }
102
+ if (count >= TAIL_STREAM_MIN_MESSAGES) break;
103
+ }
104
+ return buffer;
105
+ } catch (_) {
106
+ return '';
107
+ } finally {
108
+ if (fd !== null) { try { fs.closeSync(fd); } catch (_) {} }
109
+ }
110
+ };
111
+
56
112
  // Pull the last N user/assistant *logical turns* from the tail, oldest-first.
57
113
  // Phase 6.2.3 — beta.17 fix: Claude Code streams a single assistant reply as
58
114
  // MULTIPLE JSONL lines (text → tool_use → tool_result → text → …), each its
@@ -323,7 +379,9 @@ const scanClaude = () => {
323
379
  // have an ai-title record yet.
324
380
  let title = extractAiTitleFromTail(tail);
325
381
  if (!title) title = extractTitleFromFile(filePath);
326
- const recentMessages = extractRecentMessages(tail);
382
+ // Recent messages get the streaming tail (handles huge per-message
383
+ // assistant turns that overflow the 16KB fixed budget).
384
+ const recentMessages = extractRecentMessages(readTailUntilMessages(filePath));
327
385
  out.push({
328
386
  source: 'claude',
329
387
  sessionId,
@@ -399,7 +457,9 @@ const scanCodex = () => {
399
457
  const tail = readTail(filePath);
400
458
  const lastMsg = extractLastMessage(tail);
401
459
  const title = extractTitleFromFile(filePath);
402
- const recentMessages = extractRecentMessages(tail);
460
+ // Recent messages get the streaming tail (handles huge per-message
461
+ // assistant turns that overflow the 16KB fixed budget).
462
+ const recentMessages = extractRecentMessages(readTailUntilMessages(filePath));
403
463
  out.push({
404
464
  source: 'codex',
405
465
  sessionId,