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

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.16",
3
+ "version": "0.1.0-beta.17",
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.",
@@ -53,17 +53,23 @@ const readTail = (filePath, maxBytes = TAIL_READ_BYTES) => {
53
53
  }
54
54
  };
55
55
 
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) => {
56
+ // Pull the last N user/assistant *logical turns* from the tail, oldest-first.
57
+ // Phase 6.2.3 beta.17 fix: Claude Code streams a single assistant reply as
58
+ // MULTIPLE JSONL lines (text tool_use tool_result text → …), each its
59
+ // own JSONL record. The earlier extractor took one line per turn and
60
+ // fragmented responses into single-shard previews. Now we walk forward,
61
+ // parse every text-bearing user/assistant line, and **coalesce consecutive
62
+ // same-role lines into one logical turn** before slicing the last N. Result:
63
+ // a real conversation, not a single mid-sentence excerpt.
64
+ const RECENT_MESSAGE_BODY_MAX = 3000;
65
+ const RECENT_TURN_COUNT = 10;
66
+
67
+ const extractRecentMessages = (jsonlTail, n = RECENT_TURN_COUNT) => {
62
68
  if (!jsonlTail) return [];
63
69
  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();
70
+ const parsed = [];
71
+ for (const lineRaw of lines) {
72
+ const line = lineRaw.trim();
67
73
  if (!line) continue;
68
74
  let obj;
69
75
  try { obj = JSON.parse(line); } catch (_) { continue; }
@@ -86,15 +92,37 @@ const extractRecentMessages = (jsonlTail, n = 5) => {
86
92
  .join(' ');
87
93
  }
88
94
  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
- });
95
+ if (!text) continue; // skip pure tool_use / tool_result lines (no text body)
96
+ parsed.push({ role, text, ts: obj.timestamp || null });
96
97
  }
97
- return out;
98
+
99
+ // Coalesce consecutive same-role lines (one logical turn split across JSONL
100
+ // records). This is the actual fix — without it, assistant turns with mid-
101
+ // reply tool calls fragment into the first text shard only.
102
+ const coalesced = [];
103
+ for (const p of parsed) {
104
+ const last = coalesced[coalesced.length - 1];
105
+ if (last && last.role === p.role) {
106
+ last.text = (last.text + ' ' + p.text).replace(/\s+/g, ' ').trim();
107
+ last.ts = p.ts || last.ts;
108
+ } else {
109
+ coalesced.push({ role: p.role, text: p.text, ts: p.ts });
110
+ }
111
+ }
112
+
113
+ // Filter user turns that are pure platform-injection noise. Assistant turns
114
+ // are never filtered — they're always real Claude/Codex output.
115
+ const filtered = coalesced.filter((m) => {
116
+ if (m.role !== 'user') return true;
117
+ return !isInjectionMessage(m.text);
118
+ });
119
+
120
+ const sliced = filtered.slice(-n);
121
+ return sliced.map((m) => ({
122
+ role: m.role,
123
+ body: m.text.length > RECENT_MESSAGE_BODY_MAX ? m.text.slice(0, RECENT_MESSAGE_BODY_MAX) : m.text,
124
+ ts: m.ts,
125
+ }));
98
126
  };
99
127
 
100
128
  // Claude Code writes an LLM-generated title as a `type: "ai-title"` JSONL