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.
- package/cli.mjs +52 -13
- 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
|
}
|
|
@@ -1491,26 +1493,41 @@ function handleRequest(req, res) {
|
|
|
1491
1493
|
return;
|
|
1492
1494
|
}
|
|
1493
1495
|
|
|
1494
|
-
// ── SSE
|
|
1495
|
-
//
|
|
1496
|
-
//
|
|
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
|
|
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
|
-
|
|
1504
|
-
|
|
1515
|
+
sseBuffer += chunk;
|
|
1516
|
+
const lines = sseBuffer.split("\n");
|
|
1517
|
+
sseBuffer = lines.pop() || ""; // keep incomplete last line
|
|
1505
1518
|
|
|
1506
|
-
|
|
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
|
-
|
|
1550
|
+
// Non-JSON SSE data — forward as text anyway
|
|
1551
|
+
fullText += raw;
|
|
1534
1552
|
}
|
|
1535
1553
|
}
|
|
1536
1554
|
}
|
|
1537
1555
|
});
|
|
1538
1556
|
|
|
1539
1557
|
proxyRes.on("end", () => {
|
|
1540
|
-
//
|
|
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;
|