openclaw-navigator 5.6.2 → 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 +71 -17
  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
  }
@@ -1363,6 +1365,10 @@ function handleRequest(req, res) {
1363
1365
  {
1364
1366
  const targetURL = `${path}${url.search}`;
1365
1367
  const incomingHost = req.headers.host || "localhost";
1368
+ // Log API calls for diagnostics (helps debug web UI chat)
1369
+ if (path.startsWith("/api/")) {
1370
+ console.log(` ${DIM}→ Proxy ${req.method} ${path} → localhost:${ocUIPort}${RESET}`);
1371
+ }
1366
1372
 
1367
1373
  // Encourage JSON responses from the web UI's API routes
1368
1374
  const proxyHeaders = {
@@ -1373,8 +1379,10 @@ function handleRequest(req, res) {
1373
1379
  "x-forwarded-for": req.socket.remoteAddress || "127.0.0.1",
1374
1380
  };
1375
1381
 
1376
- // For /api/* requests, prefer JSON over SSE
1377
- if (path.startsWith("/api/")) {
1382
+ // For /api/* requests, prefer JSON over SSE — BUT not for streaming chat endpoints
1383
+ // which need text/event-stream to get SSE responses from the BFF
1384
+ const isStreamingReq = path.startsWith("/api/chat") || path.startsWith("/api/stream");
1385
+ if (path.startsWith("/api/") && !isStreamingReq) {
1378
1386
  proxyHeaders["accept"] = "application/json, text/plain, */*";
1379
1387
  }
1380
1388
 
@@ -1389,6 +1397,12 @@ function handleRequest(req, res) {
1389
1397
  const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
1390
1398
  const headers = { ...proxyRes.headers };
1391
1399
 
1400
+ // Log response info for API calls (helps debug web UI chat)
1401
+ if (path.startsWith("/api/")) {
1402
+ const ct = (proxyRes.headers["content-type"] || "unknown").split(";")[0];
1403
+ console.log(` ${DIM}← ${proxyRes.statusCode} ${ct} for ${path}${RESET}`);
1404
+ }
1405
+
1392
1406
  // CORS
1393
1407
  headers["access-control-allow-origin"] = "*";
1394
1408
  headers["access-control-allow-methods"] = "GET, POST, PUT, DELETE, OPTIONS";
@@ -1479,26 +1493,41 @@ function handleRequest(req, res) {
1479
1493
  return;
1480
1494
  }
1481
1495
 
1482
- // ── SSE passthrough for streaming endpoints (/api/chat) ──────────
1483
- // Let the SSE stream through to the browser AND tap it to broadcast
1484
- // 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.
1485
1501
  if (isSSE && isStreamingEndpoint) {
1486
- 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"];
1487
1508
  res.writeHead(proxyRes.statusCode ?? 200, headers);
1488
1509
 
1489
1510
  let fullText = "";
1511
+ let sseBuffer = "";
1512
+
1513
+ proxyRes.setEncoding("utf-8");
1490
1514
  proxyRes.on("data", (chunk) => {
1491
- // Forward the chunk to the browser immediately
1492
- res.write(chunk);
1515
+ sseBuffer += chunk;
1516
+ const lines = sseBuffer.split("\n");
1517
+ sseBuffer = lines.pop() || ""; // keep incomplete last line
1493
1518
 
1494
- // Also parse and broadcast to WebSocket
1495
- const text = chunk.toString("utf-8");
1496
- for (const line of text.split("\n")) {
1519
+ for (const line of lines) {
1497
1520
  if (line.startsWith("data: ")) {
1498
1521
  const raw = line.slice(6).trim();
1499
1522
  if (raw === "[DONE]" || !raw) {
1500
1523
  continue;
1501
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
1502
1531
  try {
1503
1532
  const evt = JSON.parse(raw);
1504
1533
  const delta =
@@ -1518,15 +1547,33 @@ function handleRequest(req, res) {
1518
1547
  });
1519
1548
  }
1520
1549
  } catch {
1521
- /* non-JSON SSE eventskip */
1550
+ // Non-JSON SSE dataforward as text anyway
1551
+ fullText += raw;
1522
1552
  }
1523
1553
  }
1524
1554
  }
1525
1555
  });
1526
1556
 
1527
1557
  proxyRes.on("end", () => {
1528
- // 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
1529
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
+
1530
1577
  broadcastToWS({
1531
1578
  type: "chat.final",
1532
1579
  text: fullText,
@@ -1535,11 +1582,15 @@ function handleRequest(req, res) {
1535
1582
  role: "assistant",
1536
1583
  timestamp: Date.now(),
1537
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}`);
1538
1588
  }
1539
1589
  res.end();
1540
1590
  });
1541
1591
 
1542
- proxyRes.on("error", () => {
1592
+ proxyRes.on("error", (err) => {
1593
+ console.log(` ${DIM}SSE stream error: ${err.message}${RESET}`);
1543
1594
  res.end();
1544
1595
  });
1545
1596
  return;
@@ -1550,8 +1601,11 @@ function handleRequest(req, res) {
1550
1601
  proxyRes.pipe(res, { end: true });
1551
1602
  });
1552
1603
 
1553
- proxyReq.on("error", () => {
1554
- sendJSON(res, 404, { ok: false, error: "Not found" });
1604
+ proxyReq.on("error", (err) => {
1605
+ if (path.startsWith("/api/")) {
1606
+ console.log(` ${DIM}✗ Proxy error for ${path}: ${err.message}${RESET}`);
1607
+ }
1608
+ sendJSON(res, 502, { ok: false, error: `Web UI (port ${ocUIPort}) not reachable: ${err.message}` });
1555
1609
  });
1556
1610
 
1557
1611
  req.pipe(proxyReq, { end: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.6.2",
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",