@obtoai/agent-bridge 0.1.0-beta.14 → 0.1.0-beta.16
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 +79 -4
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.16",
|
|
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,7 @@ 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 =
|
|
31
|
+
const TAIL_READ_BYTES = 16384; // last 16KB — covers the last message AND the late-appended ai-title record
|
|
32
32
|
const TITLE_MAX_LINES = 40; // scan up to this many lines for a real first message
|
|
33
33
|
const TITLE_MAX_BYTES = 65536; // hard ceiling per file even if MAX_LINES never reached
|
|
34
34
|
|
|
@@ -53,7 +53,74 @@ const readTail = (filePath, maxBytes = TAIL_READ_BYTES) => {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
//
|
|
56
|
+
// Pull the last N user/assistant turns from the tail, oldest-first. Used at
|
|
57
|
+
// adopt time so the bridge thread shows actual prior context (not just a
|
|
58
|
+
// one-line preview) — Phase 6.2.2. Skips injection-pattern user messages so
|
|
59
|
+
// the history feels like real conversation, not platform noise.
|
|
60
|
+
const RECENT_MESSAGE_BODY_MAX = 1500;
|
|
61
|
+
const extractRecentMessages = (jsonlTail, n = 5) => {
|
|
62
|
+
if (!jsonlTail) return [];
|
|
63
|
+
const lines = jsonlTail.split(/\r?\n/);
|
|
64
|
+
const out = [];
|
|
65
|
+
for (let i = lines.length - 1; i >= 0 && out.length < n; i--) {
|
|
66
|
+
const line = lines[i].trim();
|
|
67
|
+
if (!line) continue;
|
|
68
|
+
let obj;
|
|
69
|
+
try { obj = JSON.parse(line); } catch (_) { continue; }
|
|
70
|
+
let role = null, raw = null;
|
|
71
|
+
if (obj && obj.message && (obj.message.role || obj.type)) {
|
|
72
|
+
role = obj.message.role || (obj.type === 'user' ? 'user' : 'assistant');
|
|
73
|
+
raw = obj.message.content;
|
|
74
|
+
} else if (obj && obj.role && (obj.content || obj.text)) {
|
|
75
|
+
role = obj.role;
|
|
76
|
+
raw = obj.content != null ? obj.content : obj.text;
|
|
77
|
+
}
|
|
78
|
+
if (role !== 'user' && role !== 'assistant') continue;
|
|
79
|
+
if (raw == null) continue;
|
|
80
|
+
let text = '';
|
|
81
|
+
if (typeof raw === 'string') text = raw;
|
|
82
|
+
else if (Array.isArray(raw)) {
|
|
83
|
+
text = raw
|
|
84
|
+
.filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
|
|
85
|
+
.map((p) => String(p.text || ''))
|
|
86
|
+
.join(' ');
|
|
87
|
+
}
|
|
88
|
+
text = text.replace(/\s+/g, ' ').trim();
|
|
89
|
+
if (!text) continue;
|
|
90
|
+
if (role === 'user' && isInjectionMessage(text)) continue;
|
|
91
|
+
out.unshift({
|
|
92
|
+
role: role,
|
|
93
|
+
body: text.length > RECENT_MESSAGE_BODY_MAX ? text.slice(0, RECENT_MESSAGE_BODY_MAX) : text,
|
|
94
|
+
ts: obj.timestamp || null,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Claude Code writes an LLM-generated title as a `type: "ai-title"` JSONL
|
|
101
|
+
// record near the end of each session file (this is the same title VSCode's
|
|
102
|
+
// session list shows — "Analyze MongoDB MCP server architecture" style).
|
|
103
|
+
// If we find one, it beats anything we could extract from the user's first
|
|
104
|
+
// raw prompt. Scan the tail backwards to hit it fast.
|
|
105
|
+
const extractAiTitleFromTail = (jsonlTail) => {
|
|
106
|
+
if (!jsonlTail) return '';
|
|
107
|
+
const lines = jsonlTail.split(/\r?\n/);
|
|
108
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
109
|
+
const line = lines[i];
|
|
110
|
+
// Cheap pre-filter so we only JSON.parse candidate lines.
|
|
111
|
+
if (line.indexOf('ai-title') === -1 && line.indexOf('aiTitle') === -1) continue;
|
|
112
|
+
try {
|
|
113
|
+
const obj = JSON.parse(line);
|
|
114
|
+
if (obj && obj.type === 'ai-title' && typeof obj.aiTitle === 'string') {
|
|
115
|
+
const t = obj.aiTitle.trim();
|
|
116
|
+
if (t) return t.length > TITLE_MAX_CHARS ? t.slice(0, TITLE_MAX_CHARS) : t;
|
|
117
|
+
}
|
|
118
|
+
} catch (_) { /* not a JSON line — keep walking */ }
|
|
119
|
+
}
|
|
120
|
+
return '';
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
|
|
57
124
|
// "user" messages BEFORE the human's first real prompt — system reminders,
|
|
58
125
|
// IDE state, untrusted-metadata wrappers, command blocks, etc. These are
|
|
59
126
|
// noise from a label perspective. Filter them so the first PLAIN user
|
|
@@ -223,13 +290,19 @@ const scanClaude = () => {
|
|
|
223
290
|
try { stat = fs.statSync(filePath); } catch (_) { continue; }
|
|
224
291
|
const tail = readTail(filePath);
|
|
225
292
|
const lastMsg = extractLastMessage(tail);
|
|
226
|
-
|
|
293
|
+
// Prefer Claude Code's own LLM-summarized title (matches VSCode's list);
|
|
294
|
+
// fall back to first-user-message scanning if a session is too new to
|
|
295
|
+
// have an ai-title record yet.
|
|
296
|
+
let title = extractAiTitleFromTail(tail);
|
|
297
|
+
if (!title) title = extractTitleFromFile(filePath);
|
|
298
|
+
const recentMessages = extractRecentMessages(tail);
|
|
227
299
|
out.push({
|
|
228
300
|
source: 'claude',
|
|
229
301
|
sessionId,
|
|
230
302
|
projectDir: entry, // raw encoded form
|
|
231
303
|
projectName: decodeClaudeProjectDir(entry), // best-effort decoded
|
|
232
|
-
title: title, // first-user-message
|
|
304
|
+
title: title, // ai-title (Claude) or first-user-message fallback
|
|
305
|
+
recentMessages: recentMessages, // last ≤5 user/assistant turns for adopt-time history
|
|
233
306
|
lastActivityAt: stat.mtimeMs,
|
|
234
307
|
lastMessagePreview: lastMsg ? lastMsg.preview : '',
|
|
235
308
|
lastMessageAuthor: lastMsg ? lastMsg.author : null,
|
|
@@ -298,12 +371,14 @@ const scanCodex = () => {
|
|
|
298
371
|
const tail = readTail(filePath);
|
|
299
372
|
const lastMsg = extractLastMessage(tail);
|
|
300
373
|
const title = extractTitleFromFile(filePath);
|
|
374
|
+
const recentMessages = extractRecentMessages(tail);
|
|
301
375
|
out.push({
|
|
302
376
|
source: 'codex',
|
|
303
377
|
sessionId,
|
|
304
378
|
projectDir: projectDir || `${y}/${m}/${d}`,
|
|
305
379
|
projectName: projectDir || null,
|
|
306
380
|
title: title,
|
|
381
|
+
recentMessages: recentMessages,
|
|
307
382
|
lastActivityAt: stat.mtimeMs,
|
|
308
383
|
lastMessagePreview: lastMsg ? lastMsg.preview : '',
|
|
309
384
|
lastMessageAuthor: lastMsg ? lastMsg.author : null,
|