@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obtoai/agent-bridge",
3
- "version": "0.1.0-beta.14",
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.",
@@ -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 = 8192; // read the last 8KB of each JSONL for the last message
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
- // Claude Code (and to a lesser extent Codex) prepend a parade of injected
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
- const title = extractTitleFromFile(filePath);
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 slice
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,