openclaw-navigator 5.5.3 → 5.6.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 +67 -163
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -362,6 +362,19 @@ function validateBridgeAuth(req) {
362
362
  return validTokens.has(token);
363
363
  }
364
364
 
365
+ // ── Chat session storage (in-memory) ────────────────────────────────────
366
+ // Stores messages per session key. Navigator sends user messages here,
367
+ // the OC agent reads them via navigator_get_chat_messages MCP tool,
368
+ // and pushes responses via navigator_chat_respond MCP tool.
369
+ const chatSessions = new Map();
370
+
371
+ function getChatSession(sessionKey = "main") {
372
+ if (!chatSessions.has(sessionKey)) {
373
+ chatSessions.set(sessionKey, { messages: [], createdAt: Date.now() });
374
+ }
375
+ return chatSessions.get(sessionKey);
376
+ }
377
+
365
378
  // ── WebSocket server for chat (minimal, no dependencies) ────────────────
366
379
  // Tracks connected WebSocket clients. When the OC agent pushes messages
367
380
  // via /api/sessions/respond or /api/sessions/stream, we broadcast to all
@@ -1069,6 +1082,13 @@ function handleRequest(req, res) {
1069
1082
  sendJSON(res, 400, { ok: false, error: "Missing 'content' or 'message'" });
1070
1083
  return;
1071
1084
  }
1085
+ // Store assistant message in local session
1086
+ const session = getChatSession(sessionKey);
1087
+ session.messages.push({
1088
+ role: "assistant",
1089
+ content: message,
1090
+ timestamp: Date.now(),
1091
+ });
1072
1092
  // Broadcast as a final chat message via WebSocket
1073
1093
  broadcastToWS({
1074
1094
  type: "chat.final",
@@ -1116,179 +1136,63 @@ function handleRequest(req, res) {
1116
1136
  return;
1117
1137
  }
1118
1138
 
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.
1139
+ // ── Chat send: store user message + broadcast via WebSocket ────────────
1140
+ // The bridge IS the chat backend messages are stored here in-memory.
1141
+ // The OC agent reads them via navigator_get_chat_messages MCP tool,
1142
+ // processes them, and pushes responses via navigator_chat_respond.
1125
1143
  if (path === "/api/sessions/send" && req.method === "POST") {
1126
1144
  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
- });
1145
+ .then((body) => {
1146
+ try {
1147
+ const data = JSON.parse(body);
1148
+ const message = data.message || data.content || data.text || "";
1149
+ const sessionKey = data.sessionKey || "main";
1150
+ if (!message) {
1151
+ sendJSON(res, 400, { ok: false, error: "Missing 'message'" });
1152
+ return;
1263
1153
  }
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,
1154
+ // Store user message in local session
1155
+ const session = getChatSession(sessionKey);
1156
+ session.messages.push({
1157
+ role: "user",
1158
+ content: message,
1159
+ timestamp: Date.now(),
1271
1160
  });
1272
- });
1273
-
1274
- proxyReq.write(bodyStr);
1275
- proxyReq.end();
1161
+ // Broadcast via WebSocket so any listening UI gets it
1162
+ broadcastToWS({
1163
+ type: "chat.user",
1164
+ text: message,
1165
+ content: message,
1166
+ sessionKey,
1167
+ role: "user",
1168
+ timestamp: Date.now(),
1169
+ });
1170
+ console.log(` ${DIM}Chat send [${sessionKey}]: ${message.substring(0, 60)}...${RESET}`);
1171
+ sendJSON(res, 200, { ok: true, stored: true, messageCount: session.messages.length });
1172
+ } catch {
1173
+ sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
1174
+ }
1276
1175
  })
1277
1176
  .catch(() => sendJSON(res, 400, { ok: false, error: "Bad request body" }));
1278
1177
  return;
1279
1178
  }
1280
1179
 
1180
+ // ── Chat history: return stored messages for a session ────────────────
1181
+ // Called by the OC agent (via navigator_get_chat_messages MCP tool)
1182
+ // and by Navigator's OCChatService to load history on tab switch.
1183
+ if (path === "/api/sessions/history" && req.method === "GET") {
1184
+ const sessionKey = url.searchParams?.get("sessionKey") || "main";
1185
+ const session = getChatSession(sessionKey);
1186
+ sendJSON(res, 200, { ok: true, messages: session.messages });
1187
+ return;
1188
+ }
1189
+
1281
1190
  // ── /api/* routing strategy ──────────────────────────────────────────────
1282
- // NO direct gateway proxy for /api/* here. The web UI (Next.js on port 4000)
1283
- // has its own /api/* routes that act as a BFF (Backend for Frontend) —
1284
- // they call the gateway internally (localhost:18789) and return clean JSON.
1285
- // Routing /api/* directly to the gateway bypasses the BFF and breaks things
1286
- // (e.g. /api/agents returns raw SSE instead of JSON).
1287
- //
1288
- // Specific bridge-handled routes (above this point):
1289
- // - /api/sessions/respond → local WebSocket broadcast
1290
- // - /api/sessions/stream → local WebSocket broadcast
1291
- // - /api/sessions/send → smart SSE bridge to gateway (sidepane chat)
1191
+ // The bridge handles chat endpoints locally:
1192
+ // - /api/sessions/send → store user message + WS broadcast
1193
+ // - /api/sessions/history → return stored messages
1194
+ // - /api/sessions/respond → store assistant message + WS broadcast
1195
+ // - /api/sessions/stream → WS broadcast (streaming delta)
1292
1196
  //
1293
1197
  // Everything else (/api/agents, /api/auth/*, /api/chat/*, etc.) falls through
1294
1198
  // to the fallback proxy below, which sends it to port 4000 (web UI).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.5.3",
3
+ "version": "5.6.0",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",