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.
- package/cli.mjs +71 -17
- 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
|
|
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
|
-
|
|
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
|
|
1483
|
-
//
|
|
1484
|
-
//
|
|
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
|
|
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
|
-
|
|
1492
|
-
|
|
1515
|
+
sseBuffer += chunk;
|
|
1516
|
+
const lines = sseBuffer.split("\n");
|
|
1517
|
+
sseBuffer = lines.pop() || ""; // keep incomplete last line
|
|
1493
1518
|
|
|
1494
|
-
|
|
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
|
-
|
|
1550
|
+
// Non-JSON SSE data — forward as text anyway
|
|
1551
|
+
fullText += raw;
|
|
1522
1552
|
}
|
|
1523
1553
|
}
|
|
1524
1554
|
}
|
|
1525
1555
|
});
|
|
1526
1556
|
|
|
1527
1557
|
proxyRes.on("end", () => {
|
|
1528
|
-
//
|
|
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
|
-
|
|
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 });
|