@obtoai/agent-bridge 0.1.0-beta.19 → 0.1.0-beta.20

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.19",
3
+ "version": "0.1.0-beta.20",
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.",
@@ -155,21 +155,28 @@ const extractRecentMessages = (jsonlTail, n = RECENT_TURN_COUNT) => {
155
155
  text = raw
156
156
  .filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
157
157
  .map((p) => String(p.text || ''))
158
- .join(' ');
158
+ .join('\n');
159
159
  }
160
- text = text.replace(/\s+/g, ' ').trim();
160
+ // Phase 6.2.6 keep newlines so the bridge UI can render markdown
161
+ // (tables, lists, code blocks, paragraphs). Collapsing every \s+ to a
162
+ // single space was stripping all the formatting before storage. We
163
+ // still collapse runs of horizontal whitespace and excessive blank
164
+ // lines, but real line breaks survive.
165
+ text = text.replace(/[ \t]+/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
161
166
  if (!text) continue; // skip pure tool_use / tool_result lines (no text body)
162
167
  parsed.push({ role, text, ts: obj.timestamp || null });
163
168
  }
164
169
 
165
170
  // Coalesce consecutive same-role lines (one logical turn split across JSONL
166
171
  // records). This is the actual fix — without it, assistant turns with mid-
167
- // reply tool calls fragment into the first text shard only.
172
+ // reply tool calls fragment into the first text shard only. Joining with
173
+ // \n\n preserves paragraph boundaries between coalesced chunks so markdown
174
+ // renders correctly.
168
175
  const coalesced = [];
169
176
  for (const p of parsed) {
170
177
  const last = coalesced[coalesced.length - 1];
171
178
  if (last && last.role === p.role) {
172
- last.text = (last.text + ' ' + p.text).replace(/\s+/g, ' ').trim();
179
+ last.text = (last.text + '\n\n' + p.text).replace(/\n{3,}/g, '\n\n').trim();
173
180
  last.ts = p.ts || last.ts;
174
181
  } else {
175
182
  coalesced.push({ role: p.role, text: p.text, ts: p.ts });
@@ -275,7 +282,7 @@ const extractTitleFromFile = (filePath) => {
275
282
  text = raw
276
283
  .filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
277
284
  .map((p) => String(p.text || ''))
278
- .join(' ');
285
+ .join('\n');
279
286
  }
280
287
  text = text.replace(/\s+/g, ' ').trim();
281
288
  if (!text) continue;
@@ -319,7 +326,7 @@ const extractLastMessage = (jsonlTail) => {
319
326
  text = raw
320
327
  .filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
321
328
  .map((p) => String(p.text || ''))
322
- .join(' ');
329
+ .join('\n');
323
330
  }
324
331
  text = text.trim();
325
332
  if (text) return { author: role === 'user' ? 'user' : 'assistant', preview: text.slice(0, PREVIEW_MAX_CHARS) };
@@ -338,7 +345,7 @@ const extractLastMessage = (jsonlTail) => {
338
345
  text = raw
339
346
  .filter((p) => p && (p.type === 'text' || typeof p.text === 'string'))
340
347
  .map((p) => String(p.text || ''))
341
- .join(' ');
348
+ .join('\n');
342
349
  }
343
350
  text = text.trim();
344
351
  if (text) return { author: role === 'user' ? 'user' : 'assistant', preview: text.slice(0, PREVIEW_MAX_CHARS) };