openclaw-navigator 5.6.0 → 5.6.2
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 +149 -30
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -1136,42 +1136,161 @@ function handleRequest(req, res) {
|
|
|
1136
1136
|
return;
|
|
1137
1137
|
}
|
|
1138
1138
|
|
|
1139
|
-
// ── Chat send: store
|
|
1140
|
-
//
|
|
1141
|
-
//
|
|
1142
|
-
//
|
|
1139
|
+
// ── Chat send: store locally + respond immediately ─────────────────────
|
|
1140
|
+
// 1. Store user message in bridge memory (for MCP tools to read)
|
|
1141
|
+
// 2. Respond to Navigator immediately (don't block on gateway)
|
|
1142
|
+
// 3. In background: try relaying to gateway for AI processing
|
|
1143
|
+
// - If gateway responds → store assistant message + broadcast via WS
|
|
1144
|
+
// - If gateway hangs/fails → agent will respond via MCP tools later
|
|
1143
1145
|
if (path === "/api/sessions/send" && req.method === "POST") {
|
|
1144
1146
|
readBody(req)
|
|
1145
|
-
.then((
|
|
1147
|
+
.then((bodyStr) => {
|
|
1148
|
+
let data;
|
|
1146
1149
|
try {
|
|
1147
|
-
|
|
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;
|
|
1153
|
-
}
|
|
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(),
|
|
1160
|
-
});
|
|
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 });
|
|
1150
|
+
data = JSON.parse(bodyStr);
|
|
1172
1151
|
} catch {
|
|
1173
1152
|
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
1153
|
+
return;
|
|
1174
1154
|
}
|
|
1155
|
+
|
|
1156
|
+
const message = data.message || data.content || data.text || "";
|
|
1157
|
+
const sessionKey = data.sessionKey || "main";
|
|
1158
|
+
if (!message) {
|
|
1159
|
+
sendJSON(res, 400, { ok: false, error: "Missing 'message'" });
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// 1. Store user message locally
|
|
1164
|
+
const session = getChatSession(sessionKey);
|
|
1165
|
+
session.messages.push({
|
|
1166
|
+
role: "user",
|
|
1167
|
+
content: message,
|
|
1168
|
+
timestamp: Date.now(),
|
|
1169
|
+
});
|
|
1170
|
+
console.log(` ${DIM}Chat send [${sessionKey}]: ${message.substring(0, 60)}...${RESET}`);
|
|
1171
|
+
|
|
1172
|
+
// 2. Respond immediately — don't block Navigator
|
|
1173
|
+
sendJSON(res, 200, { ok: true, stored: true, messageCount: session.messages.length });
|
|
1174
|
+
|
|
1175
|
+
// 3. Background: relay to gateway for AI processing
|
|
1176
|
+
const proxyBody = JSON.stringify({ message, sessionKey });
|
|
1177
|
+
const proxyOpts = {
|
|
1178
|
+
hostname: "127.0.0.1",
|
|
1179
|
+
port: ocGatewayPort,
|
|
1180
|
+
path: `/api/sessions/send`,
|
|
1181
|
+
method: "POST",
|
|
1182
|
+
timeout: 60000, // 60s — agent may take a while to respond
|
|
1183
|
+
headers: {
|
|
1184
|
+
"content-type": "application/json",
|
|
1185
|
+
"content-length": Buffer.byteLength(proxyBody),
|
|
1186
|
+
},
|
|
1187
|
+
};
|
|
1188
|
+
|
|
1189
|
+
const proxyReq = httpRequest(proxyOpts, (proxyRes) => {
|
|
1190
|
+
const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
|
|
1191
|
+
const isSSE = contentType.includes("text/event-stream");
|
|
1192
|
+
|
|
1193
|
+
if (isSSE) {
|
|
1194
|
+
// SSE response: collect stream, broadcast chunks via WebSocket
|
|
1195
|
+
let fullText = "";
|
|
1196
|
+
let buffer = "";
|
|
1197
|
+
proxyRes.setEncoding("utf-8");
|
|
1198
|
+
proxyRes.on("data", (chunk) => {
|
|
1199
|
+
buffer += chunk;
|
|
1200
|
+
const lines = buffer.split("\n");
|
|
1201
|
+
buffer = lines.pop() || "";
|
|
1202
|
+
for (const line of lines) {
|
|
1203
|
+
if (line.startsWith("data: ")) {
|
|
1204
|
+
const raw = line.slice(6).trim();
|
|
1205
|
+
if (raw === "[DONE]") continue;
|
|
1206
|
+
try {
|
|
1207
|
+
const evt = JSON.parse(raw);
|
|
1208
|
+
const text =
|
|
1209
|
+
evt.text || evt.content || evt.delta?.text || evt.delta?.content || "";
|
|
1210
|
+
if (text) {
|
|
1211
|
+
fullText += text;
|
|
1212
|
+
broadcastToWS({
|
|
1213
|
+
type: "chat.delta",
|
|
1214
|
+
text: fullText,
|
|
1215
|
+
delta: text,
|
|
1216
|
+
sessionKey,
|
|
1217
|
+
timestamp: Date.now(),
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
} catch {
|
|
1221
|
+
if (raw && raw !== "[DONE]") fullText += raw;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
});
|
|
1226
|
+
proxyRes.on("end", () => {
|
|
1227
|
+
if (buffer.startsWith("data: ")) {
|
|
1228
|
+
const raw = buffer.slice(6).trim();
|
|
1229
|
+
if (raw && raw !== "[DONE]") {
|
|
1230
|
+
try {
|
|
1231
|
+
const evt = JSON.parse(raw);
|
|
1232
|
+
fullText += evt.text || evt.content || evt.delta?.text || "";
|
|
1233
|
+
} catch {
|
|
1234
|
+
fullText += raw;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (fullText) {
|
|
1239
|
+
session.messages.push({ role: "assistant", content: fullText, timestamp: Date.now() });
|
|
1240
|
+
broadcastToWS({
|
|
1241
|
+
type: "chat.final",
|
|
1242
|
+
text: fullText,
|
|
1243
|
+
content: fullText,
|
|
1244
|
+
sessionKey,
|
|
1245
|
+
role: "assistant",
|
|
1246
|
+
timestamp: Date.now(),
|
|
1247
|
+
});
|
|
1248
|
+
console.log(` ${GREEN}✓${RESET} Gateway SSE response: ${fullText.substring(0, 80)}...`);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
1251
|
+
proxyRes.on("error", () => {});
|
|
1252
|
+
} else {
|
|
1253
|
+
// JSON response from gateway
|
|
1254
|
+
const chunks = [];
|
|
1255
|
+
proxyRes.on("data", (c) => chunks.push(c));
|
|
1256
|
+
proxyRes.on("end", () => {
|
|
1257
|
+
const body = Buffer.concat(chunks).toString("utf-8");
|
|
1258
|
+
try {
|
|
1259
|
+
const jsonBody = JSON.parse(body);
|
|
1260
|
+
const inlineResponse =
|
|
1261
|
+
jsonBody.response || jsonBody.message || jsonBody.answer || jsonBody.text || "";
|
|
1262
|
+
if (inlineResponse) {
|
|
1263
|
+
session.messages.push({ role: "assistant", content: inlineResponse, timestamp: Date.now() });
|
|
1264
|
+
broadcastToWS({
|
|
1265
|
+
type: "chat.final",
|
|
1266
|
+
text: inlineResponse,
|
|
1267
|
+
content: inlineResponse,
|
|
1268
|
+
sessionKey,
|
|
1269
|
+
role: "assistant",
|
|
1270
|
+
timestamp: Date.now(),
|
|
1271
|
+
});
|
|
1272
|
+
console.log(` ${GREEN}✓${RESET} Gateway JSON response: ${inlineResponse.substring(0, 80)}...`);
|
|
1273
|
+
} else {
|
|
1274
|
+
console.log(` ${DIM}Gateway returned JSON with no response field — waiting for MCP${RESET}`);
|
|
1275
|
+
}
|
|
1276
|
+
} catch {
|
|
1277
|
+
console.log(` ${DIM}Gateway returned non-JSON — waiting for MCP${RESET}`);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
proxyReq.on("timeout", () => {
|
|
1284
|
+
proxyReq.destroy();
|
|
1285
|
+
console.log(` ${DIM}Gateway relay timed out — agent will respond via MCP${RESET}`);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
proxyReq.on("error", (err) => {
|
|
1289
|
+
console.log(` ${DIM}Gateway relay failed (${err.message}) — agent will respond via MCP${RESET}`);
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
proxyReq.write(proxyBody);
|
|
1293
|
+
proxyReq.end();
|
|
1175
1294
|
})
|
|
1176
1295
|
.catch(() => sendJSON(res, 400, { ok: false, error: "Bad request body" }));
|
|
1177
1296
|
return;
|