openclaw-navigator 5.6.3 → 5.6.4

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.
Files changed (2) hide show
  1. package/cli.mjs +52 -13
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -1189,6 +1189,7 @@ function handleRequest(req, res) {
1189
1189
  const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
1190
1190
  const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
1191
1191
  const isSSE = contentType.includes("text/event-stream");
1192
+ console.log(` ${DIM}Gateway relay response: ${proxyRes.statusCode} ${contentType || "no-content-type"}${RESET}`);
1192
1193
 
1193
1194
  if (isSSE) {
1194
1195
  // SSE response: collect stream, broadcast chunks via WebSocket
@@ -1274,7 +1275,8 @@ function handleRequest(req, res) {
1274
1275
  console.log(` ${DIM}Gateway returned JSON with no response field — waiting for MCP${RESET}`);
1275
1276
  }
1276
1277
  } catch {
1277
- console.log(` ${DIM}Gateway returned non-JSON waiting for MCP${RESET}`);
1278
+ console.log(` ${DIM}Gateway returned non-JSON (${body.length} bytes): ${body.substring(0, 200)}${RESET}`);
1279
+ console.log(` ${DIM}Waiting for MCP agent to respond...${RESET}`);
1278
1280
  }
1279
1281
  });
1280
1282
  }
@@ -1491,26 +1493,41 @@ function handleRequest(req, res) {
1491
1493
  return;
1492
1494
  }
1493
1495
 
1494
- // ── SSE passthrough for streaming endpoints (/api/chat) ──────────
1495
- // Let the SSE stream through to the browser AND tap it to broadcast
1496
- // chunks via our WebSocket (so Navigator's sidepane chat gets them).
1496
+ // ── SSE NDJSON conversion for streaming endpoints (/api/chat) ──
1497
+ // The OC gateway returns standard SSE (data: {...}\n), but the web UI
1498
+ // frontend expects clean JSON lines (NDJSON). Strip the "data: " prefix
1499
+ // and forward clean JSON. Also tap data to broadcast via WebSocket
1500
+ // so Navigator's sidepane chat gets the response too.
1497
1501
  if (isSSE && isStreamingEndpoint) {
1498
- console.log(` ${DIM}SSE passthrough + WS tap: ${path}${RESET}`);
1502
+ console.log(` ${DIM}SSE→NDJSON + WS tap: ${path}${RESET}`);
1503
+
1504
+ // Change content-type so frontend doesn't try to parse as SSE
1505
+ headers["content-type"] = "text/plain; charset=utf-8";
1506
+ delete headers["content-length"];
1507
+ delete headers["transfer-encoding"];
1499
1508
  res.writeHead(proxyRes.statusCode ?? 200, headers);
1500
1509
 
1501
1510
  let fullText = "";
1511
+ let sseBuffer = "";
1512
+
1513
+ proxyRes.setEncoding("utf-8");
1502
1514
  proxyRes.on("data", (chunk) => {
1503
- // Forward the chunk to the browser immediately
1504
- res.write(chunk);
1515
+ sseBuffer += chunk;
1516
+ const lines = sseBuffer.split("\n");
1517
+ sseBuffer = lines.pop() || ""; // keep incomplete last line
1505
1518
 
1506
- // Also parse and broadcast to WebSocket
1507
- const text = chunk.toString("utf-8");
1508
- for (const line of text.split("\n")) {
1519
+ for (const line of lines) {
1509
1520
  if (line.startsWith("data: ")) {
1510
1521
  const raw = line.slice(6).trim();
1511
1522
  if (raw === "[DONE]" || !raw) {
1512
1523
  continue;
1513
1524
  }
1525
+
1526
+ // Forward clean JSON line to browser (no "data: " prefix)
1527
+ res.write(raw + "\n");
1528
+ console.log(` ${DIM} SSE chunk: ${raw.substring(0, 100)}${raw.length > 100 ? "..." : ""}${RESET}`);
1529
+
1530
+ // Also broadcast to WebSocket for Navigator sidepane
1514
1531
  try {
1515
1532
  const evt = JSON.parse(raw);
1516
1533
  const delta =
@@ -1530,15 +1547,33 @@ function handleRequest(req, res) {
1530
1547
  });
1531
1548
  }
1532
1549
  } catch {
1533
- /* non-JSON SSE eventskip */
1550
+ // Non-JSON SSE dataforward as text anyway
1551
+ fullText += raw;
1534
1552
  }
1535
1553
  }
1536
1554
  }
1537
1555
  });
1538
1556
 
1539
1557
  proxyRes.on("end", () => {
1540
- // Stream ended — broadcast final message via WebSocket
1558
+ // Process remaining buffer
1559
+ if (sseBuffer.startsWith("data: ")) {
1560
+ const raw = sseBuffer.slice(6).trim();
1561
+ if (raw && raw !== "[DONE]") {
1562
+ res.write(raw + "\n");
1563
+ try {
1564
+ const evt = JSON.parse(raw);
1565
+ const delta = evt.choices?.[0]?.delta?.content || evt.delta?.text || evt.text || evt.content || "";
1566
+ if (delta) fullText += delta;
1567
+ } catch { fullText += raw; }
1568
+ }
1569
+ }
1570
+
1571
+ // Broadcast final message via WebSocket
1541
1572
  if (fullText) {
1573
+ // Also store as assistant message in bridge chat session
1574
+ const session = getChatSession("main");
1575
+ session.messages.push({ role: "assistant", content: fullText, timestamp: Date.now() });
1576
+
1542
1577
  broadcastToWS({
1543
1578
  type: "chat.final",
1544
1579
  text: fullText,
@@ -1547,11 +1582,15 @@ function handleRequest(req, res) {
1547
1582
  role: "assistant",
1548
1583
  timestamp: Date.now(),
1549
1584
  });
1585
+ console.log(` ${GREEN}✓${RESET} Chat response (${fullText.length} chars): ${fullText.substring(0, 80)}...`);
1586
+ } else {
1587
+ console.log(` ${DIM}SSE stream ended with no content${RESET}`);
1550
1588
  }
1551
1589
  res.end();
1552
1590
  });
1553
1591
 
1554
- proxyRes.on("error", () => {
1592
+ proxyRes.on("error", (err) => {
1593
+ console.log(` ${DIM}SSE stream error: ${err.message}${RESET}`);
1555
1594
  res.end();
1556
1595
  });
1557
1596
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.6.3",
3
+ "version": "5.6.4",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",