openclaw-navigator 5.4.2 → 5.5.0

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 +164 -1
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * openclaw-navigator v5.4.2
4
+ * openclaw-navigator v5.5.0
5
5
  *
6
6
  * One-command bridge + tunnel for the Navigator browser.
7
7
  * Starts a local bridge, creates a Cloudflare tunnel automatically,
@@ -1116,10 +1116,173 @@ function handleRequest(req, res) {
1116
1116
  return;
1117
1117
  }
1118
1118
 
1119
+ // ── Smart proxy: /api/sessions/send → OC Gateway + SSE bridge ───────────
1120
+ // This is the main chat send endpoint. The gateway may return:
1121
+ // (a) JSON: { ok, response, message, ... } — pass through + broadcast
1122
+ // (b) SSE: text/event-stream with data lines — collect + broadcast + return JSON
1123
+ // Either way, the response is also broadcast via WebSocket so Navigator's
1124
+ // chat pane (OCChatService) gets it via both HTTP and WS.
1125
+ if (path === "/api/sessions/send" && req.method === "POST") {
1126
+ readBody(req)
1127
+ .then((bodyStr) => {
1128
+ const proxyOpts = {
1129
+ hostname: "127.0.0.1",
1130
+ port: ocGatewayPort,
1131
+ path: `${path}${url.search}`,
1132
+ method: "POST",
1133
+ headers: {
1134
+ ...req.headers,
1135
+ host: `127.0.0.1:${ocGatewayPort}`,
1136
+ "content-type": "application/json",
1137
+ "content-length": Buffer.byteLength(bodyStr),
1138
+ },
1139
+ };
1140
+
1141
+ const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
1142
+ const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
1143
+ const isSSE = contentType.includes("text/event-stream");
1144
+
1145
+ if (isSSE) {
1146
+ // ── SSE response: collect stream, broadcast chunks, return JSON ──
1147
+ let fullText = "";
1148
+ let buffer = "";
1149
+
1150
+ proxyRes.setEncoding("utf-8");
1151
+ proxyRes.on("data", (chunk) => {
1152
+ buffer += chunk;
1153
+ // Parse SSE lines
1154
+ const lines = buffer.split("\n");
1155
+ buffer = lines.pop() || ""; // keep incomplete line
1156
+ for (const line of lines) {
1157
+ if (line.startsWith("data: ")) {
1158
+ const raw = line.slice(6).trim();
1159
+ if (raw === "[DONE]") {
1160
+ continue;
1161
+ }
1162
+ try {
1163
+ const evt = JSON.parse(raw);
1164
+ // Extract text from common SSE shapes
1165
+ const text =
1166
+ evt.text || evt.content || evt.delta?.text || evt.delta?.content || "";
1167
+ if (text) {
1168
+ fullText += text;
1169
+ broadcastToWS({
1170
+ type: "chat.delta",
1171
+ text: fullText,
1172
+ delta: text,
1173
+ sessionKey: "main",
1174
+ timestamp: Date.now(),
1175
+ });
1176
+ }
1177
+ } catch {
1178
+ // Non-JSON SSE line — treat as plain text
1179
+ if (raw && raw !== "[DONE]") {
1180
+ fullText += raw;
1181
+ }
1182
+ }
1183
+ }
1184
+ }
1185
+ });
1186
+
1187
+ proxyRes.on("end", () => {
1188
+ // Process any remaining buffer
1189
+ if (buffer.startsWith("data: ")) {
1190
+ const raw = buffer.slice(6).trim();
1191
+ if (raw && raw !== "[DONE]") {
1192
+ try {
1193
+ const evt = JSON.parse(raw);
1194
+ fullText += evt.text || evt.content || evt.delta?.text || "";
1195
+ } catch {
1196
+ fullText += raw;
1197
+ }
1198
+ }
1199
+ }
1200
+
1201
+ if (fullText) {
1202
+ // Broadcast final via WebSocket
1203
+ broadcastToWS({
1204
+ type: "chat.final",
1205
+ text: fullText,
1206
+ content: fullText,
1207
+ sessionKey: "main",
1208
+ role: "assistant",
1209
+ timestamp: Date.now(),
1210
+ });
1211
+ console.log(` ${DIM}SSE→WS bridge: ${fullText.substring(0, 80)}...${RESET}`);
1212
+ }
1213
+
1214
+ // Return clean JSON to the caller (OCChatService)
1215
+ sendJSON(res, 200, {
1216
+ ok: true,
1217
+ response: fullText || null,
1218
+ format: "sse-bridged",
1219
+ delivered: wsClients.size,
1220
+ });
1221
+ });
1222
+
1223
+ proxyRes.on("error", () => {
1224
+ sendJSON(res, 502, { ok: false, error: "SSE stream error from gateway" });
1225
+ });
1226
+ } else {
1227
+ // ── JSON response: collect body, broadcast if it has a response, pass through ──
1228
+ const chunks = [];
1229
+ proxyRes.on("data", (c) => chunks.push(c));
1230
+ proxyRes.on("end", () => {
1231
+ const body = Buffer.concat(chunks).toString("utf-8");
1232
+ let jsonBody;
1233
+ try {
1234
+ jsonBody = JSON.parse(body);
1235
+ } catch {
1236
+ // Not JSON — pass through raw
1237
+ const headers = { ...proxyRes.headers };
1238
+ headers["access-control-allow-origin"] = "*";
1239
+ res.writeHead(proxyRes.statusCode ?? 200, headers);
1240
+ res.end(body);
1241
+ return;
1242
+ }
1243
+
1244
+ // If the gateway returned an inline response, broadcast it via WebSocket
1245
+ const inlineResponse =
1246
+ jsonBody.response || jsonBody.message || jsonBody.answer || jsonBody.text || "";
1247
+ if (inlineResponse) {
1248
+ broadcastToWS({
1249
+ type: "chat.final",
1250
+ text: inlineResponse,
1251
+ content: inlineResponse,
1252
+ sessionKey: "main",
1253
+ role: "assistant",
1254
+ timestamp: Date.now(),
1255
+ });
1256
+ console.log(
1257
+ ` ${DIM}Inline response→WS: ${inlineResponse.substring(0, 80)}...${RESET}`,
1258
+ );
1259
+ }
1260
+
1261
+ sendJSON(res, proxyRes.statusCode ?? 200, jsonBody);
1262
+ });
1263
+ }
1264
+ });
1265
+
1266
+ proxyReq.on("error", (err) => {
1267
+ sendJSON(res, 502, {
1268
+ ok: false,
1269
+ error: `OC Gateway not reachable on port ${ocGatewayPort}`,
1270
+ detail: err.message,
1271
+ });
1272
+ });
1273
+
1274
+ proxyReq.write(bodyStr);
1275
+ proxyReq.end();
1276
+ })
1277
+ .catch(() => sendJSON(res, 400, { ok: false, error: "Bad request body" }));
1278
+ return;
1279
+ }
1280
+
1119
1281
  // ── Reverse proxy: /api/* → OC Gateway (localhost:ocGatewayPort) ──────────
1120
1282
  // ALL /api/* requests go to the OC gateway EXCEPT:
1121
1283
  // - /api/auth/* → falls through to web UI (NextAuth login/session)
1122
1284
  // - /api/sessions/respond + /api/sessions/stream → handled locally above
1285
+ // - /api/sessions/send → smart SSE bridge above
1123
1286
  // The OC web UI frontend expects /api/agents, /api/sessions/*, /api/chat/*,
1124
1287
  // etc. to reach the gateway. Only /api/auth/* is a Next.js API route.
1125
1288
  if (path.startsWith("/api/") && !path.startsWith("/api/auth/") && path !== "/api/auth") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.4.2",
3
+ "version": "5.5.0",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",