@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 +1 -1
- package/src/external-scanner.js +63 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@obtoai/agent-bridge",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
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.",
|
package/src/external-scanner.js
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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,
|