openclaw-navigator 5.0.2 → 5.1.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 +279 -48
- package/mcp.mjs +69 -3
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { spawn } from "node:child_process";
|
|
18
|
-
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { randomUUID, createHash } from "node:crypto";
|
|
19
19
|
import { existsSync, appendFileSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
20
20
|
import { createServer, request as httpRequest } from "node:http";
|
|
21
21
|
import { connect as netConnect } from "node:net";
|
|
@@ -57,6 +57,24 @@ const recentEvents = [];
|
|
|
57
57
|
const MAX_EVENTS = 200;
|
|
58
58
|
const validTokens = new Set();
|
|
59
59
|
|
|
60
|
+
// ── Chat session state ────────────────────────────────────────────────────
|
|
61
|
+
const chatSessions = new Map(); // sessionKey → { messages: [...] }
|
|
62
|
+
const wsClients = new Set(); // connected WebSocket clients
|
|
63
|
+
|
|
64
|
+
function getChatSession(key = "main") {
|
|
65
|
+
if (!chatSessions.has(key)) {
|
|
66
|
+
chatSessions.set(key, { messages: [] });
|
|
67
|
+
}
|
|
68
|
+
return chatSessions.get(key);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function broadcastToWS(event) {
|
|
72
|
+
const data = JSON.stringify(event);
|
|
73
|
+
for (const ws of wsClients) {
|
|
74
|
+
try { ws.send(data); } catch {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
60
78
|
// OC Web UI reverse proxy target (configurable via --ui-port or env)
|
|
61
79
|
let ocUIPort = parseInt(process.env.OPENCLAW_UI_PORT ?? "4000", 10);
|
|
62
80
|
|
|
@@ -445,8 +463,9 @@ function handleRequest(req, res) {
|
|
|
445
463
|
},
|
|
446
464
|
routing: {
|
|
447
465
|
"/ui/*": `localhost:${ocUIPort}`,
|
|
448
|
-
"/api/*":
|
|
449
|
-
"/
|
|
466
|
+
"/api/sessions/*": "bridge (in-process chat)",
|
|
467
|
+
"/api/*": `localhost:${ocGatewayPort} (fallback)`,
|
|
468
|
+
"/ws": "bridge (in-process WebSocket)",
|
|
450
469
|
},
|
|
451
470
|
tunnel: activeTunnelURL
|
|
452
471
|
? {
|
|
@@ -905,8 +924,117 @@ function handleRequest(req, res) {
|
|
|
905
924
|
return;
|
|
906
925
|
}
|
|
907
926
|
|
|
927
|
+
// ── POST /api/sessions/send — Chat: receive user message ──
|
|
928
|
+
if (req.method === "POST" && path === "/api/sessions/send") {
|
|
929
|
+
readBody(req).then(raw => {
|
|
930
|
+
const body = JSON.parse(raw);
|
|
931
|
+
const sessionKey = body.sessionKey || "main";
|
|
932
|
+
const message = body.message;
|
|
933
|
+
if (!message) {
|
|
934
|
+
sendJSON(res, 400, { ok: false, error: "Missing 'message'" });
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const session = getChatSession(sessionKey);
|
|
939
|
+
const userMsg = {
|
|
940
|
+
role: "user",
|
|
941
|
+
content: message,
|
|
942
|
+
timestamp: Date.now(),
|
|
943
|
+
id: randomUUID(),
|
|
944
|
+
};
|
|
945
|
+
session.messages.push(userMsg);
|
|
946
|
+
|
|
947
|
+
// Also push as a bridge event so MCP tools can see it
|
|
948
|
+
recentEvents.push({
|
|
949
|
+
type: "chat.user_message",
|
|
950
|
+
sessionKey,
|
|
951
|
+
message,
|
|
952
|
+
messageId: userMsg.id,
|
|
953
|
+
timestamp: userMsg.timestamp,
|
|
954
|
+
});
|
|
955
|
+
if (recentEvents.length > MAX_EVENTS) recentEvents.shift();
|
|
956
|
+
|
|
957
|
+
// Broadcast to WebSocket clients
|
|
958
|
+
broadcastToWS({ type: "chat.user_message", sessionKey, message, messageId: userMsg.id });
|
|
959
|
+
|
|
960
|
+
sendJSON(res, 200, { ok: true, messageId: userMsg.id });
|
|
961
|
+
}).catch(() => {
|
|
962
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
963
|
+
});
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ── GET /api/sessions/history — Chat: fetch history ──
|
|
968
|
+
if (req.method === "GET" && path === "/api/sessions/history") {
|
|
969
|
+
const sessionKey = url.searchParams.get("sessionKey") || "main";
|
|
970
|
+
const session = getChatSession(sessionKey);
|
|
971
|
+
sendJSON(res, 200, { ok: true, messages: session.messages });
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// ── POST /api/sessions/respond — Chat: agent sends response ──
|
|
976
|
+
// Used by MCP tools to send agent responses to the chat
|
|
977
|
+
if (req.method === "POST" && path === "/api/sessions/respond") {
|
|
978
|
+
readBody(req).then(raw => {
|
|
979
|
+
const body = JSON.parse(raw);
|
|
980
|
+
const sessionKey = body.sessionKey || "main";
|
|
981
|
+
const content = body.content || body.message;
|
|
982
|
+
if (!content) {
|
|
983
|
+
sendJSON(res, 400, { ok: false, error: "Missing 'content'" });
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
const session = getChatSession(sessionKey);
|
|
988
|
+
const assistantMsg = {
|
|
989
|
+
role: "assistant",
|
|
990
|
+
content,
|
|
991
|
+
timestamp: Date.now(),
|
|
992
|
+
id: randomUUID(),
|
|
993
|
+
};
|
|
994
|
+
session.messages.push(assistantMsg);
|
|
995
|
+
|
|
996
|
+
// Broadcast final response via WebSocket
|
|
997
|
+
broadcastToWS({
|
|
998
|
+
type: "chat.final",
|
|
999
|
+
text: content,
|
|
1000
|
+
content,
|
|
1001
|
+
runId: body.runId || null,
|
|
1002
|
+
sessionKey,
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
sendJSON(res, 200, { ok: true, messageId: assistantMsg.id });
|
|
1006
|
+
}).catch(() => {
|
|
1007
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
1008
|
+
});
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// ── POST /api/sessions/stream — Chat: agent streams partial text ──
|
|
1013
|
+
if (req.method === "POST" && path === "/api/sessions/stream") {
|
|
1014
|
+
readBody(req).then(raw => {
|
|
1015
|
+
const body = JSON.parse(raw);
|
|
1016
|
+
const text = body.text || body.delta || "";
|
|
1017
|
+
const runId = body.runId || null;
|
|
1018
|
+
const sessionKey = body.sessionKey || "main";
|
|
1019
|
+
|
|
1020
|
+
broadcastToWS({
|
|
1021
|
+
type: "chat.delta",
|
|
1022
|
+
text,
|
|
1023
|
+
delta: text,
|
|
1024
|
+
runId,
|
|
1025
|
+
sessionKey,
|
|
1026
|
+
});
|
|
1027
|
+
|
|
1028
|
+
sendJSON(res, 200, { ok: true });
|
|
1029
|
+
}).catch(() => {
|
|
1030
|
+
sendJSON(res, 400, { ok: false, error: "Invalid JSON" });
|
|
1031
|
+
});
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
908
1035
|
// ── Reverse proxy: /api/* → OC Gateway (localhost:ocGatewayPort) ─────
|
|
909
1036
|
// Keeps /api/ prefix intact so /api/sessions/send → localhost:18789/api/sessions/send
|
|
1037
|
+
// This is a fallback — specific chat paths above are handled directly.
|
|
910
1038
|
if (path.startsWith("/api/")) {
|
|
911
1039
|
const targetURL = `${path}${url.search}`;
|
|
912
1040
|
|
|
@@ -1149,11 +1277,12 @@ ${BOLD}Stability (recommended for production):${RESET}
|
|
|
1149
1277
|
--tunnel-hostname <host> Hostname for named tunnel (e.g. nav.yourdomain.com)
|
|
1150
1278
|
|
|
1151
1279
|
${BOLD}Routing (through Cloudflare tunnel):${RESET}
|
|
1152
|
-
/ui/*
|
|
1153
|
-
/api/*
|
|
1154
|
-
/ws, WebSocket
|
|
1155
|
-
/
|
|
1156
|
-
/
|
|
1280
|
+
/ui/* → localhost:<ui-port> Web UI (login page, dashboard)
|
|
1281
|
+
/api/sessions/* → bridge (in-process) Chat send/history/respond/stream
|
|
1282
|
+
/ws, WebSocket → bridge (in-process) Real-time chat events
|
|
1283
|
+
/api/* (other) → localhost:<gateway-port> Gateway API fallback
|
|
1284
|
+
/health → bridge itself Health check
|
|
1285
|
+
/navigator/* → bridge itself Navigator control endpoints
|
|
1157
1286
|
|
|
1158
1287
|
${BOLD}Environment variables:${RESET}
|
|
1159
1288
|
OPENCLAW_UI_PORT=4000 Where the web UI runs
|
|
@@ -1239,49 +1368,142 @@ module.exports = {
|
|
|
1239
1368
|
server.listen(port, bindHost, () => resolve());
|
|
1240
1369
|
});
|
|
1241
1370
|
|
|
1242
|
-
// ── WebSocket
|
|
1243
|
-
// Proxies WebSocket connections so Navigator can stream chat events
|
|
1371
|
+
// ── WebSocket handler — manages chat connections directly ─────────────
|
|
1244
1372
|
server.on("upgrade", (req, socket, head) => {
|
|
1245
1373
|
const reqUrl = new URL(req.url ?? "/", "http://localhost");
|
|
1246
1374
|
const reqPath = reqUrl.pathname;
|
|
1247
1375
|
|
|
1248
|
-
// Only
|
|
1249
|
-
if (reqPath
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
// Forward the original HTTP upgrade request over the TCP socket
|
|
1254
|
-
const upgradeReq =
|
|
1255
|
-
`${req.method} ${targetPath} HTTP/${req.httpVersion}\r\n` +
|
|
1256
|
-
Object.entries(req.headers)
|
|
1257
|
-
.map(([k, v]) => `${k}: ${v}`)
|
|
1258
|
-
.join("\r\n") +
|
|
1259
|
-
"\r\n\r\n";
|
|
1260
|
-
proxy.write(upgradeReq);
|
|
1261
|
-
if (head && head.length > 0) {
|
|
1262
|
-
proxy.write(head);
|
|
1263
|
-
}
|
|
1264
|
-
// Pipe bidirectionally
|
|
1265
|
-
socket.pipe(proxy);
|
|
1266
|
-
proxy.pipe(socket);
|
|
1267
|
-
});
|
|
1376
|
+
// Only handle /ws paths
|
|
1377
|
+
if (reqPath !== "/ws" && !reqPath.startsWith("/ws/")) {
|
|
1378
|
+
socket.destroy();
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1268
1381
|
|
|
1269
|
-
|
|
1270
|
-
|
|
1382
|
+
// Perform WebSocket handshake
|
|
1383
|
+
const key = req.headers["sec-websocket-key"];
|
|
1384
|
+
if (!key) {
|
|
1385
|
+
socket.destroy();
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
const accept = createHash("sha1")
|
|
1390
|
+
.update(key + "258EAFA5-E914-47DA-95CA-5AB5DC799C07")
|
|
1391
|
+
.digest("base64");
|
|
1392
|
+
|
|
1393
|
+
socket.write(
|
|
1394
|
+
"HTTP/1.1 101 Switching Protocols\r\n" +
|
|
1395
|
+
"Upgrade: websocket\r\n" +
|
|
1396
|
+
"Connection: Upgrade\r\n" +
|
|
1397
|
+
`Sec-WebSocket-Accept: ${accept}\r\n` +
|
|
1398
|
+
"\r\n"
|
|
1399
|
+
);
|
|
1400
|
+
|
|
1401
|
+
// Create a minimal WebSocket wrapper
|
|
1402
|
+
const ws = {
|
|
1403
|
+
socket,
|
|
1404
|
+
send(data) {
|
|
1405
|
+
const payload = Buffer.from(data);
|
|
1406
|
+
const frame = [];
|
|
1407
|
+
frame.push(0x81); // text frame, FIN bit
|
|
1408
|
+
if (payload.length < 126) {
|
|
1409
|
+
frame.push(payload.length);
|
|
1410
|
+
} else if (payload.length < 65536) {
|
|
1411
|
+
frame.push(126, (payload.length >> 8) & 0xFF, payload.length & 0xFF);
|
|
1412
|
+
} else {
|
|
1413
|
+
frame.push(127);
|
|
1414
|
+
for (let i = 7; i >= 0; i--) {
|
|
1415
|
+
frame.push((payload.length >> (i * 8)) & 0xFF);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
socket.write(Buffer.concat([Buffer.from(frame), payload]));
|
|
1419
|
+
},
|
|
1420
|
+
close() {
|
|
1421
|
+
wsClients.delete(ws);
|
|
1271
1422
|
socket.destroy();
|
|
1272
|
-
}
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1273
1425
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1426
|
+
wsClients.add(ws);
|
|
1427
|
+
console.log(` ${DIM}WebSocket client connected (${wsClients.size} active)${RESET}`);
|
|
1428
|
+
|
|
1429
|
+
// Send connected event
|
|
1430
|
+
ws.send(JSON.stringify({ type: "connected" }));
|
|
1431
|
+
|
|
1432
|
+
// Handle incoming WebSocket frames (simplified parser)
|
|
1433
|
+
let frameBuffer = Buffer.alloc(0);
|
|
1434
|
+
socket.on("data", (chunk) => {
|
|
1435
|
+
frameBuffer = Buffer.concat([frameBuffer, chunk]);
|
|
1436
|
+
|
|
1437
|
+
while (frameBuffer.length >= 2) {
|
|
1438
|
+
const firstByte = frameBuffer[0];
|
|
1439
|
+
const secondByte = frameBuffer[1];
|
|
1440
|
+
const opcode = firstByte & 0x0F;
|
|
1441
|
+
const masked = (secondByte & 0x80) !== 0;
|
|
1442
|
+
let payloadLen = secondByte & 0x7F;
|
|
1443
|
+
let offset = 2;
|
|
1444
|
+
|
|
1445
|
+
if (payloadLen === 126) {
|
|
1446
|
+
if (frameBuffer.length < 4) return;
|
|
1447
|
+
payloadLen = (frameBuffer[2] << 8) | frameBuffer[3];
|
|
1448
|
+
offset = 4;
|
|
1449
|
+
} else if (payloadLen === 127) {
|
|
1450
|
+
if (frameBuffer.length < 10) return;
|
|
1451
|
+
payloadLen = 0;
|
|
1452
|
+
for (let i = 0; i < 8; i++) {
|
|
1453
|
+
payloadLen = payloadLen * 256 + frameBuffer[2 + i];
|
|
1454
|
+
}
|
|
1455
|
+
offset = 10;
|
|
1456
|
+
}
|
|
1277
1457
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1458
|
+
const maskSize = masked ? 4 : 0;
|
|
1459
|
+
const totalFrameLen = offset + maskSize + payloadLen;
|
|
1460
|
+
if (frameBuffer.length < totalFrameLen) return;
|
|
1461
|
+
|
|
1462
|
+
let payload;
|
|
1463
|
+
if (masked) {
|
|
1464
|
+
const mask = frameBuffer.slice(offset, offset + 4);
|
|
1465
|
+
payload = Buffer.alloc(payloadLen);
|
|
1466
|
+
for (let i = 0; i < payloadLen; i++) {
|
|
1467
|
+
payload[i] = frameBuffer[offset + 4 + i] ^ mask[i % 4];
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
payload = frameBuffer.slice(offset, offset + payloadLen);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Handle by opcode
|
|
1474
|
+
if (opcode === 0x08) {
|
|
1475
|
+
// Close frame
|
|
1476
|
+
ws.close();
|
|
1477
|
+
return;
|
|
1478
|
+
} else if (opcode === 0x09) {
|
|
1479
|
+
// Ping — send pong
|
|
1480
|
+
const pongFrame = Buffer.from([0x8A, payload.length, ...payload]);
|
|
1481
|
+
socket.write(pongFrame);
|
|
1482
|
+
} else if (opcode === 0x01) {
|
|
1483
|
+
// Text frame — handle as message
|
|
1484
|
+
const text = payload.toString("utf8");
|
|
1485
|
+
try {
|
|
1486
|
+
const msg = JSON.parse(text);
|
|
1487
|
+
if (msg.type === "ping") {
|
|
1488
|
+
ws.send(JSON.stringify({ type: "pong" }));
|
|
1489
|
+
}
|
|
1490
|
+
} catch {
|
|
1491
|
+
// Non-JSON message, ignore
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
frameBuffer = frameBuffer.slice(totalFrameLen);
|
|
1496
|
+
}
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
socket.on("close", () => {
|
|
1500
|
+
wsClients.delete(ws);
|
|
1501
|
+
console.log(` ${DIM}WebSocket client disconnected (${wsClients.size} active)${RESET}`);
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
socket.on("error", () => {
|
|
1505
|
+
wsClients.delete(ws);
|
|
1506
|
+
});
|
|
1285
1507
|
});
|
|
1286
1508
|
|
|
1287
1509
|
ok(`Bridge server running on ${bindHost}:${port}`);
|
|
@@ -1492,9 +1714,10 @@ module.exports = {
|
|
|
1492
1714
|
// ── Show OC Web UI access + routing info ─────────────────────────────
|
|
1493
1715
|
const uiURL = tunnelURL ? `${tunnelURL}/ui/` : `http://localhost:${port}/ui/`;
|
|
1494
1716
|
console.log(` ${BOLD}OC Web UI:${RESET} ${CYAN}${uiURL}${RESET}`);
|
|
1495
|
-
info(` /ui/*
|
|
1496
|
-
info(` /api/*
|
|
1497
|
-
info(` /ws
|
|
1717
|
+
info(` /ui/* → localhost:${ocUIPort} (web UI)`);
|
|
1718
|
+
info(` /api/sessions/* → bridge (in-process chat)`);
|
|
1719
|
+
info(` /ws → bridge (in-process WebSocket)`);
|
|
1720
|
+
info(` /api/* (other) → localhost:${ocGatewayPort} (gateway fallback)`);
|
|
1498
1721
|
|
|
1499
1722
|
// ── Startup health checks ──────────────────────────────────────────
|
|
1500
1723
|
console.log("");
|
|
@@ -1627,7 +1850,7 @@ module.exports = {
|
|
|
1627
1850
|
|
|
1628
1851
|
mkdirSync(mcporterDir, { recursive: true });
|
|
1629
1852
|
writeFileSync(mcporterConfigPath, JSON.stringify(mcporterConfig, null, 2) + "\n", "utf8");
|
|
1630
|
-
ok("Registered MCP server with mcporter (
|
|
1853
|
+
ok("Registered MCP server with mcporter (34 tools: 16 browser + 10 AI + 5 profiling + 3 chat)");
|
|
1631
1854
|
info(` Config: ${mcporterConfigPath}`);
|
|
1632
1855
|
|
|
1633
1856
|
// Step 3: Install the navigator-bridge skill so the OC agent knows about all tools
|
|
@@ -1672,7 +1895,7 @@ module.exports = {
|
|
|
1672
1895
|
"mcporter call navigator.navigator_status",
|
|
1673
1896
|
BT,
|
|
1674
1897
|
"",
|
|
1675
|
-
"## All
|
|
1898
|
+
"## All 34 tools",
|
|
1676
1899
|
"",
|
|
1677
1900
|
"### Browser Control (16 tools)",
|
|
1678
1901
|
"",
|
|
@@ -1720,6 +1943,14 @@ module.exports = {
|
|
|
1720
1943
|
"| `navigator_get_user_profile` | Get aggregated user interest profile |",
|
|
1721
1944
|
"| `navigator_save_user_profile` | Save/update user profile from browsing patterns |",
|
|
1722
1945
|
"",
|
|
1946
|
+
"### Chat (3 tools)",
|
|
1947
|
+
"",
|
|
1948
|
+
"| Tool | What it does |",
|
|
1949
|
+
"|------|-------------|",
|
|
1950
|
+
"| `navigator_get_chat_messages` | Get recent messages from the Navigator chat pane |",
|
|
1951
|
+
"| `navigator_chat_respond` | Send a response message to the Navigator chat pane |",
|
|
1952
|
+
"| `navigator_chat_stream` | Stream partial text to the chat pane (typing effect) |",
|
|
1953
|
+
"",
|
|
1723
1954
|
"## Usage",
|
|
1724
1955
|
"",
|
|
1725
1956
|
BT + "bash",
|
package/mcp.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* openclaw-navigator MCP server v5.0
|
|
4
|
+
* openclaw-navigator MCP server v5.1.0
|
|
5
5
|
*
|
|
6
6
|
* Exposes the Navigator bridge HTTP API as MCP tools so the OpenClaw agent
|
|
7
7
|
* can control the browser natively via its tool schema.
|
|
@@ -204,7 +204,7 @@ async function sendCommand(command, payload, poll) {
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// ── Tool definitions (
|
|
207
|
+
// ── Tool definitions (34 tools: 16 browser + 10 AI intelligence + 5 profiling + 3 chat) ────────────────
|
|
208
208
|
|
|
209
209
|
const TOOLS = [
|
|
210
210
|
// ── Direct HTTP ──
|
|
@@ -596,6 +596,44 @@ const TOOLS = [
|
|
|
596
596
|
required: ["profile"],
|
|
597
597
|
},
|
|
598
598
|
},
|
|
599
|
+
|
|
600
|
+
// ── Chat Tools ──
|
|
601
|
+
{
|
|
602
|
+
name: "navigator_get_chat_messages",
|
|
603
|
+
description: "Get recent chat messages from the Navigator chat pane. Use this to see what the user is asking.",
|
|
604
|
+
inputSchema: {
|
|
605
|
+
type: "object",
|
|
606
|
+
properties: {
|
|
607
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
608
|
+
limit: { type: "number", description: "Max messages to return (default: 20)", default: 20 },
|
|
609
|
+
},
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "navigator_chat_respond",
|
|
614
|
+
description: "Send a response message to the Navigator chat pane. The user will see this as an agent message.",
|
|
615
|
+
inputSchema: {
|
|
616
|
+
type: "object",
|
|
617
|
+
properties: {
|
|
618
|
+
message: { type: "string", description: "The response message to send" },
|
|
619
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
620
|
+
},
|
|
621
|
+
required: ["message"],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "navigator_chat_stream",
|
|
626
|
+
description: "Stream a partial response to the Navigator chat pane (for real-time typing effect).",
|
|
627
|
+
inputSchema: {
|
|
628
|
+
type: "object",
|
|
629
|
+
properties: {
|
|
630
|
+
text: { type: "string", description: "Partial text to stream" },
|
|
631
|
+
sessionKey: { type: "string", description: "Chat session key (default: main)", default: "main" },
|
|
632
|
+
runId: { type: "string", description: "Run ID for grouping streamed chunks" },
|
|
633
|
+
},
|
|
634
|
+
required: ["text"],
|
|
635
|
+
},
|
|
636
|
+
},
|
|
599
637
|
];
|
|
600
638
|
|
|
601
639
|
// ── Tool handler dispatch ─────────────────────────────────────────────────
|
|
@@ -823,6 +861,34 @@ async function handleTool(name, args) {
|
|
|
823
861
|
}),
|
|
824
862
|
);
|
|
825
863
|
|
|
864
|
+
// ── Chat Tools ──
|
|
865
|
+
case "navigator_get_chat_messages": {
|
|
866
|
+
const sessionKey = args.sessionKey || "main";
|
|
867
|
+
const limit = args.limit ?? 20;
|
|
868
|
+
const data = await bridgeGet(`/api/sessions/history?sessionKey=${encodeURIComponent(sessionKey)}`);
|
|
869
|
+
if (data.ok && Array.isArray(data.messages)) {
|
|
870
|
+
return jsonResult({ ok: true, messages: data.messages.slice(-limit) });
|
|
871
|
+
}
|
|
872
|
+
return jsonResult(data);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
case "navigator_chat_respond":
|
|
876
|
+
return jsonResult(
|
|
877
|
+
await bridgePost("/api/sessions/respond", {
|
|
878
|
+
content: args.message,
|
|
879
|
+
sessionKey: args.sessionKey || "main",
|
|
880
|
+
}),
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
case "navigator_chat_stream":
|
|
884
|
+
return jsonResult(
|
|
885
|
+
await bridgePost("/api/sessions/stream", {
|
|
886
|
+
text: args.text,
|
|
887
|
+
sessionKey: args.sessionKey || "main",
|
|
888
|
+
runId: args.runId || null,
|
|
889
|
+
}),
|
|
890
|
+
);
|
|
891
|
+
|
|
826
892
|
default:
|
|
827
893
|
return errorResult(`Unknown tool: ${name}`);
|
|
828
894
|
}
|
|
@@ -835,7 +901,7 @@ async function handleTool(name, args) {
|
|
|
835
901
|
// ── MCP server wiring ─────────────────────────────────────────────────────
|
|
836
902
|
|
|
837
903
|
const server = new Server(
|
|
838
|
-
{ name: "openclaw-navigator", version: "5.
|
|
904
|
+
{ name: "openclaw-navigator", version: "5.1.0" },
|
|
839
905
|
{ capabilities: { tools: {} } },
|
|
840
906
|
);
|
|
841
907
|
|