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.
- package/cli.mjs +67 -163
- 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
|
-
// ──
|
|
1120
|
-
//
|
|
1121
|
-
//
|
|
1122
|
-
//
|
|
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((
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
1275
|
-
|
|
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
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
//
|
|
1286
|
-
//
|
|
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).
|