@obtoai/agent-bridge 0.1.0-beta.11 → 0.1.0-beta.12
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 +60 -0
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.12",
|
|
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
|
@@ -27,7 +27,9 @@ const CLAUDE_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
|
27
27
|
const CODEX_DIR = path.join(os.homedir(), '.codex', 'sessions');
|
|
28
28
|
|
|
29
29
|
const PREVIEW_MAX_CHARS = 200;
|
|
30
|
+
const TITLE_MAX_CHARS = 80;
|
|
30
31
|
const TAIL_READ_BYTES = 8192; // read the last 8KB of each JSONL for the last message
|
|
32
|
+
const HEAD_READ_BYTES = 4096; // read the first 4KB for the first-user-message title
|
|
31
33
|
|
|
32
34
|
// Read the tail of a (potentially large) JSONL file without slurping the
|
|
33
35
|
// whole thing into memory. Returns a string (UTF-8) or '' on any failure.
|
|
@@ -50,6 +52,58 @@ const readTail = (filePath, maxBytes = TAIL_READ_BYTES) => {
|
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
|
|
55
|
+
// Read the head of a JSONL file (first ~4KB). Used to extract the first user
|
|
56
|
+
// message for a meaningful session title — "fix the auth bug" beats "OBTO".
|
|
57
|
+
const readHead = (filePath, maxBytes = HEAD_READ_BYTES) => {
|
|
58
|
+
let fd = null;
|
|
59
|
+
try {
|
|
60
|
+
const stat = fs.statSync(filePath);
|
|
61
|
+
const len = Math.min(stat.size, maxBytes);
|
|
62
|
+
if (len <= 0) return '';
|
|
63
|
+
fd = fs.openSync(filePath, 'r');
|
|
64
|
+
const buf = Buffer.alloc(len);
|
|
65
|
+
fs.readSync(fd, buf, 0, len, 0);
|
|
66
|
+
return buf.toString('utf8');
|
|
67
|
+
} catch (_) {
|
|
68
|
+
return '';
|
|
69
|
+
} finally {
|
|
70
|
+
if (fd !== null) { try { fs.closeSync(fd); } catch (_) {} }
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Walk JSONL lines forward to find the first user message. Same tolerance
|
|
75
|
+
// for the two SDKs' shapes as extractLastMessage. Returns a short title
|
|
76
|
+
// suitable for a thread name, or '' if no user message landed in the head.
|
|
77
|
+
const extractTitle = (jsonlHead) => {
|
|
78
|
+
if (!jsonlHead) return '';
|
|
79
|
+
const lines = jsonlHead.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
80
|
+
for (let i = 0; i < lines.length; i++) {
|
|
81
|
+
let obj;
|
|
82
|
+
try { obj = JSON.parse(lines[i]); } catch (_) { continue; }
|
|
83
|
+
let role = null;
|
|
84
|
+
let raw = null;
|
|
85
|
+
if (obj && obj.message && (obj.message.role || obj.type)) {
|
|
86
|
+
role = obj.message.role || (obj.type === 'user' ? 'user' : 'assistant');
|
|
87
|
+
raw = obj.message.content;
|
|
88
|
+
} else if (obj && obj.role && (obj.content || obj.text)) {
|
|
89
|
+
role = obj.role;
|
|
90
|
+
raw = obj.content != null ? obj.content : obj.text;
|
|
91
|
+
}
|
|
92
|
+
if (role !== 'user' || raw == null) continue;
|
|
93
|
+
let text = '';
|
|
94
|
+
if (typeof raw === 'string') text = raw;
|
|
95
|
+
else if (Array.isArray(raw)) {
|
|
96
|
+
text = raw
|
|
97
|
+
.filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
|
|
98
|
+
.map((p) => String(p.text || ''))
|
|
99
|
+
.join(' ');
|
|
100
|
+
}
|
|
101
|
+
text = text.trim().replace(/\s+/g, ' ');
|
|
102
|
+
if (text) return text.slice(0, TITLE_MAX_CHARS);
|
|
103
|
+
}
|
|
104
|
+
return '';
|
|
105
|
+
};
|
|
106
|
+
|
|
53
107
|
// Walk a JSONL tail backwards, parse each non-empty line as JSON, return the
|
|
54
108
|
// first one we can extract a message from. Tolerant of multiple shapes —
|
|
55
109
|
// Claude and Codex write slightly different envelopes and the formats have
|
|
@@ -142,12 +196,15 @@ const scanClaude = () => {
|
|
|
142
196
|
let stat;
|
|
143
197
|
try { stat = fs.statSync(filePath); } catch (_) { continue; }
|
|
144
198
|
const tail = readTail(filePath);
|
|
199
|
+
const head = readHead(filePath);
|
|
145
200
|
const lastMsg = extractLastMessage(tail);
|
|
201
|
+
const title = extractTitle(head);
|
|
146
202
|
out.push({
|
|
147
203
|
source: 'claude',
|
|
148
204
|
sessionId,
|
|
149
205
|
projectDir: entry, // raw encoded form
|
|
150
206
|
projectName: decodeClaudeProjectDir(entry), // best-effort decoded
|
|
207
|
+
title: title, // first-user-message slice
|
|
151
208
|
lastActivityAt: stat.mtimeMs,
|
|
152
209
|
lastMessagePreview: lastMsg ? lastMsg.preview : '',
|
|
153
210
|
lastMessageAuthor: lastMsg ? lastMsg.author : null,
|
|
@@ -214,12 +271,15 @@ const scanCodex = () => {
|
|
|
214
271
|
}
|
|
215
272
|
|
|
216
273
|
const tail = readTail(filePath);
|
|
274
|
+
const head = readHead(filePath);
|
|
217
275
|
const lastMsg = extractLastMessage(tail);
|
|
276
|
+
const title = extractTitle(head);
|
|
218
277
|
out.push({
|
|
219
278
|
source: 'codex',
|
|
220
279
|
sessionId,
|
|
221
280
|
projectDir: projectDir || `${y}/${m}/${d}`,
|
|
222
281
|
projectName: projectDir || null,
|
|
282
|
+
title: title,
|
|
223
283
|
lastActivityAt: stat.mtimeMs,
|
|
224
284
|
lastMessagePreview: lastMsg ? lastMsg.preview : '',
|
|
225
285
|
lastMessageAuthor: lastMsg ? lastMsg.author : null,
|