openclaw-navigator 5.7.5 → 5.7.7
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 +43 -62
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -1216,7 +1216,8 @@ function handleRequest(req, res) {
|
|
|
1216
1216
|
chatHistory.unshift({ role: "user", content: message });
|
|
1217
1217
|
}
|
|
1218
1218
|
const proxyBody = JSON.stringify({
|
|
1219
|
-
|
|
1219
|
+
message: message, // BFF expects "message" (singular) — the current user text
|
|
1220
|
+
messages: chatHistory, // Also send full history for OpenAI-compatible endpoints
|
|
1220
1221
|
stream: true,
|
|
1221
1222
|
});
|
|
1222
1223
|
// Build cookie header: prefer captured BFF cookies, fall back to browser's cookies
|
|
@@ -1612,82 +1613,62 @@ function handleRequest(req, res) {
|
|
|
1612
1613
|
return;
|
|
1613
1614
|
}
|
|
1614
1615
|
|
|
1615
|
-
// ── SSE→
|
|
1616
|
-
// The
|
|
1617
|
-
//
|
|
1618
|
-
// the "data: " prefix).
|
|
1619
|
-
//
|
|
1616
|
+
// ── SSE → NDJSON for streaming endpoints (/api/chat) ─────────────
|
|
1617
|
+
// The OC web UI frontend does fetch() + JSON.parse() on each line,
|
|
1618
|
+
// NOT proper EventSource parsing. So it chokes on "data: {...}"
|
|
1619
|
+
// (the "data: " prefix isn't valid JSON). We strip the prefix and
|
|
1620
|
+
// forward clean JSON lines (NDJSON), one per write, flushed immediately.
|
|
1620
1621
|
if (isSSE && isStreamingEndpoint) {
|
|
1621
|
-
console.log(` ${DIM}SSE→
|
|
1622
|
+
console.log(` ${DIM}SSE→NDJSON: ${path}${RESET}`);
|
|
1622
1623
|
|
|
1623
|
-
|
|
1624
|
-
|
|
1624
|
+
// Send as NDJSON (application/x-ndjson) so the frontend gets
|
|
1625
|
+
// one clean JSON object per line, no SSE framing.
|
|
1626
|
+
headers["content-type"] = "application/x-ndjson";
|
|
1627
|
+
headers["cache-control"] = "no-cache";
|
|
1628
|
+
headers["connection"] = "keep-alive";
|
|
1629
|
+
delete headers["content-length"];
|
|
1630
|
+
delete headers["transfer-encoding"];
|
|
1625
1631
|
|
|
1632
|
+
res.writeHead(200, headers);
|
|
1633
|
+
if (res.socket) res.socket.setNoDelay(true);
|
|
1634
|
+
|
|
1635
|
+
let sseLineBuf = "";
|
|
1626
1636
|
proxyRes.setEncoding("utf-8");
|
|
1627
1637
|
proxyRes.on("data", (chunk) => {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
for (const line of sseData.split("\n")) {
|
|
1638
|
+
sseLineBuf += chunk;
|
|
1639
|
+
// Process complete lines
|
|
1640
|
+
const lines = sseLineBuf.split("\n");
|
|
1641
|
+
sseLineBuf = lines.pop() || ""; // keep incomplete line in buffer
|
|
1642
|
+
for (const line of lines) {
|
|
1634
1643
|
if (line.startsWith("data: ")) {
|
|
1635
|
-
const
|
|
1636
|
-
if (
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
evt.delta?.text ||
|
|
1642
|
-
evt.text ||
|
|
1643
|
-
evt.content ||
|
|
1644
|
-
"";
|
|
1645
|
-
if (delta) fullText += delta;
|
|
1646
|
-
} catch {
|
|
1647
|
-
if (raw) fullText += raw;
|
|
1644
|
+
const payload = line.slice(6).trim();
|
|
1645
|
+
if (payload === "[DONE]") {
|
|
1646
|
+
res.write("data: [DONE]\n\n");
|
|
1647
|
+
} else if (payload) {
|
|
1648
|
+
// Write clean JSON + newline — one object per write
|
|
1649
|
+
res.write(payload + "\n");
|
|
1648
1650
|
}
|
|
1649
1651
|
}
|
|
1652
|
+
// Skip empty lines, "event:", "id:", "retry:" etc.
|
|
1650
1653
|
}
|
|
1654
|
+
if (typeof res.flush === "function") res.flush();
|
|
1655
|
+
});
|
|
1651
1656
|
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
message: { role: "assistant", content: fullText },
|
|
1660
|
-
finish_reason: "stop",
|
|
1661
|
-
}],
|
|
1662
|
-
};
|
|
1663
|
-
|
|
1664
|
-
headers["content-type"] = "application/json";
|
|
1665
|
-
delete headers["content-length"];
|
|
1666
|
-
delete headers["transfer-encoding"];
|
|
1667
|
-
res.writeHead(200, headers);
|
|
1668
|
-
res.end(JSON.stringify(result));
|
|
1669
|
-
|
|
1670
|
-
// Broadcast via WebSocket for live updates (but DON'T store in
|
|
1671
|
-
// bridge chat session — web UI manages its own conversation state
|
|
1672
|
-
// separately from sidepane chat. Mixing them causes 400 errors from
|
|
1673
|
-
// the BFF due to malformed message history.)
|
|
1674
|
-
if (fullText) {
|
|
1675
|
-
broadcastToWS({
|
|
1676
|
-
type: "chat.webui",
|
|
1677
|
-
text: fullText,
|
|
1678
|
-
content: fullText,
|
|
1679
|
-
role: "assistant",
|
|
1680
|
-
timestamp: Date.now(),
|
|
1681
|
-
});
|
|
1682
|
-
console.log(` ${GREEN}✓${RESET} Chat response (${fullText.length} chars): ${fullText.substring(0, 80)}...`);
|
|
1683
|
-
} else {
|
|
1684
|
-
console.log(` ${DIM}SSE stream ended with no content${RESET}`);
|
|
1657
|
+
proxyRes.on("end", () => {
|
|
1658
|
+
// Flush remaining buffer
|
|
1659
|
+
if (sseLineBuf.startsWith("data: ")) {
|
|
1660
|
+
const payload = sseLineBuf.slice(6).trim();
|
|
1661
|
+
if (payload && payload !== "[DONE]") {
|
|
1662
|
+
res.write(payload + "\n");
|
|
1663
|
+
}
|
|
1685
1664
|
}
|
|
1665
|
+
res.end();
|
|
1666
|
+
console.log(` ${GREEN}✓${RESET} SSE→NDJSON stream completed for ${path}`);
|
|
1686
1667
|
});
|
|
1687
1668
|
|
|
1688
1669
|
proxyRes.on("error", (err) => {
|
|
1689
1670
|
console.log(` ${DIM}SSE stream error: ${err.message}${RESET}`);
|
|
1690
|
-
|
|
1671
|
+
res.end();
|
|
1691
1672
|
});
|
|
1692
1673
|
return;
|
|
1693
1674
|
}
|