openclaw-navigator 5.0.2 → 5.2.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 +164 -259
- package/mcp.mjs +94 -29
- package/package.json +1 -1
package/cli.mjs
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* openclaw-navigator
|
|
4
|
+
* openclaw-navigator v5.2.0
|
|
5
5
|
*
|
|
6
6
|
* One-command bridge + tunnel for the Navigator browser.
|
|
7
7
|
* Starts a local bridge, creates a Cloudflare tunnel automatically,
|
|
8
8
|
* and gives you a 6-digit pairing code. Works on any OS.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Chat and WebSocket are transparently proxied to the OC gateway (port 18789).
|
|
11
|
+
* The web UI at port 4000 is also proxied — no local build needed.
|
|
10
12
|
*
|
|
11
13
|
* Usage:
|
|
12
14
|
* npx openclaw-navigator Auto-tunnel (default)
|
|
@@ -80,7 +82,9 @@ function loadBridgeIdentity() {
|
|
|
80
82
|
if (data.pairingCode && data.token) {
|
|
81
83
|
return data;
|
|
82
84
|
}
|
|
83
|
-
} catch {
|
|
85
|
+
} catch {
|
|
86
|
+
/* first run */
|
|
87
|
+
}
|
|
84
88
|
return null;
|
|
85
89
|
}
|
|
86
90
|
|
|
@@ -112,7 +116,15 @@ function appendJSONL(filePath, record) {
|
|
|
112
116
|
function readJSONL(filePath, limit = 200) {
|
|
113
117
|
try {
|
|
114
118
|
const lines = readFileSync(filePath, "utf8").trim().split("\n").filter(Boolean);
|
|
115
|
-
const parsed = lines
|
|
119
|
+
const parsed = lines
|
|
120
|
+
.map((l) => {
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(l);
|
|
123
|
+
} catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
.filter(Boolean);
|
|
116
128
|
return limit > 0 ? parsed.slice(-limit) : parsed;
|
|
117
129
|
} catch {
|
|
118
130
|
return [];
|
|
@@ -132,146 +144,6 @@ function writeJSON(filePath, data) {
|
|
|
132
144
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
// ── OC Web UI lifecycle ──────────────────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
const UI_DIR = join(homedir(), ".openclaw", "ui");
|
|
138
|
-
const UI_REPO = "https://github.com/sandman66666/openclaw-ui.git";
|
|
139
|
-
let uiProcess = null;
|
|
140
|
-
|
|
141
|
-
async function isUIInstalled() {
|
|
142
|
-
return existsSync(join(UI_DIR, "package.json")) && existsSync(join(UI_DIR, ".next"));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async function setupUI() {
|
|
146
|
-
const { execSync } = await import("node:child_process");
|
|
147
|
-
|
|
148
|
-
if (existsSync(join(UI_DIR, "package.json"))) {
|
|
149
|
-
info(" UI directory exists, reinstalling...");
|
|
150
|
-
return await buildUI();
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
heading("Setting up OC Web UI (first time)");
|
|
154
|
-
info(" Cloning from GitHub...");
|
|
155
|
-
|
|
156
|
-
mkdirSync(join(homedir(), ".openclaw"), { recursive: true });
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
execSync(`git clone --depth 1 ${UI_REPO} "${UI_DIR}"`, {
|
|
160
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
161
|
-
timeout: 60000,
|
|
162
|
-
});
|
|
163
|
-
ok("Repository cloned");
|
|
164
|
-
} catch (err) {
|
|
165
|
-
fail(`Failed to clone UI repo: ${err.message}`);
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
return await buildUI();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function buildUI() {
|
|
173
|
-
const { execSync } = await import("node:child_process");
|
|
174
|
-
|
|
175
|
-
process.stdout.write(` ${DIM}Installing dependencies (this may take a minute)...${RESET}`);
|
|
176
|
-
try {
|
|
177
|
-
execSync("npm install --production=false", {
|
|
178
|
-
cwd: UI_DIR,
|
|
179
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
180
|
-
timeout: 120000,
|
|
181
|
-
env: { ...process.env, NODE_ENV: "development" },
|
|
182
|
-
});
|
|
183
|
-
process.stdout.write(`\r${" ".repeat(70)}\r`);
|
|
184
|
-
ok("Dependencies installed");
|
|
185
|
-
} catch (err) {
|
|
186
|
-
process.stdout.write(`\r${" ".repeat(70)}\r`);
|
|
187
|
-
fail(`npm install failed: ${err.stderr?.toString()?.slice(-200) || err.message}`);
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
process.stdout.write(` ${DIM}Building web UI...${RESET}`);
|
|
192
|
-
try {
|
|
193
|
-
execSync("npx next build", {
|
|
194
|
-
cwd: UI_DIR,
|
|
195
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
196
|
-
timeout: 180000,
|
|
197
|
-
});
|
|
198
|
-
process.stdout.write(`\r${" ".repeat(70)}\r`);
|
|
199
|
-
ok("Web UI built successfully");
|
|
200
|
-
return true;
|
|
201
|
-
} catch (err) {
|
|
202
|
-
process.stdout.write(`\r${" ".repeat(70)}\r`);
|
|
203
|
-
fail(`Build failed: ${err.stderr?.toString()?.slice(-200) || err.message}`);
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function updateUI() {
|
|
209
|
-
const { execSync } = await import("node:child_process");
|
|
210
|
-
|
|
211
|
-
if (!existsSync(join(UI_DIR, ".git"))) {
|
|
212
|
-
warn("UI not installed yet — running full setup instead");
|
|
213
|
-
return await setupUI();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
heading("Updating OC Web UI");
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const result = execSync("git pull --rebase origin main", {
|
|
220
|
-
cwd: UI_DIR,
|
|
221
|
-
encoding: "utf8",
|
|
222
|
-
timeout: 30000,
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
if (result.includes("Already up to date")) {
|
|
226
|
-
ok("Already up to date");
|
|
227
|
-
return true;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
ok("Pulled latest changes");
|
|
231
|
-
return await buildUI();
|
|
232
|
-
} catch (err) {
|
|
233
|
-
fail(`Update failed: ${err.message}`);
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function startUIServer(port) {
|
|
239
|
-
if (uiProcess) {
|
|
240
|
-
uiProcess.kill();
|
|
241
|
-
uiProcess = null;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
uiProcess = spawn("npx", ["next", "start", "-p", String(port)], {
|
|
245
|
-
cwd: UI_DIR,
|
|
246
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
247
|
-
env: { ...process.env, PORT: String(port), NODE_ENV: "production" },
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
uiProcess.on("error", (err) => {
|
|
251
|
-
warn(`OC Web UI failed to start: ${err.message}`);
|
|
252
|
-
uiProcess = null;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
uiProcess.on("exit", (code) => {
|
|
256
|
-
if (code !== null && code !== 0) {
|
|
257
|
-
warn(`OC Web UI exited with code ${code}`);
|
|
258
|
-
}
|
|
259
|
-
uiProcess = null;
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
return new Promise((resolve) => {
|
|
263
|
-
const timer = setTimeout(() => {
|
|
264
|
-
ok(`OC Web UI starting on port ${port} (PID ${uiProcess?.pid})`);
|
|
265
|
-
resolve(true);
|
|
266
|
-
}, 1500);
|
|
267
|
-
|
|
268
|
-
uiProcess.on("exit", () => {
|
|
269
|
-
clearTimeout(timer);
|
|
270
|
-
resolve(false);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
147
|
// Pairing code state
|
|
276
148
|
let pairingCode = null;
|
|
277
149
|
let pairingData = null;
|
|
@@ -408,7 +280,9 @@ function sendJSON(res, status, body) {
|
|
|
408
280
|
|
|
409
281
|
function validateBridgeAuth(req) {
|
|
410
282
|
const authHeader = req.headers["authorization"];
|
|
411
|
-
if (!authHeader)
|
|
283
|
+
if (!authHeader) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
412
286
|
const token = authHeader.replace(/^Bearer\s+/i, "");
|
|
413
287
|
return validTokens.has(token);
|
|
414
288
|
}
|
|
@@ -446,7 +320,7 @@ function handleRequest(req, res) {
|
|
|
446
320
|
routing: {
|
|
447
321
|
"/ui/*": `localhost:${ocUIPort}`,
|
|
448
322
|
"/api/*": `localhost:${ocGatewayPort}`,
|
|
449
|
-
"/ws": `localhost:${ocGatewayPort}`,
|
|
323
|
+
"/ws": `localhost:${ocGatewayPort} (WebSocket proxy)`,
|
|
450
324
|
},
|
|
451
325
|
tunnel: activeTunnelURL
|
|
452
326
|
? {
|
|
@@ -463,7 +337,11 @@ function handleRequest(req, res) {
|
|
|
463
337
|
// ── GET /navigator/commands ──
|
|
464
338
|
if (req.method === "GET" && path === "/navigator/commands") {
|
|
465
339
|
if (!validateBridgeAuth(req)) {
|
|
466
|
-
sendJSON(res, 401, {
|
|
340
|
+
sendJSON(res, 401, {
|
|
341
|
+
ok: false,
|
|
342
|
+
error: "unauthorized",
|
|
343
|
+
hint: "Include Authorization: Bearer <token> header",
|
|
344
|
+
});
|
|
467
345
|
return;
|
|
468
346
|
}
|
|
469
347
|
if (!bridgeState.connected) {
|
|
@@ -484,7 +362,11 @@ function handleRequest(req, res) {
|
|
|
484
362
|
// ── POST /navigator/events ──
|
|
485
363
|
if (req.method === "POST" && path === "/navigator/events") {
|
|
486
364
|
if (!validateBridgeAuth(req)) {
|
|
487
|
-
sendJSON(res, 401, {
|
|
365
|
+
sendJSON(res, 401, {
|
|
366
|
+
ok: false,
|
|
367
|
+
error: "unauthorized",
|
|
368
|
+
hint: "Include Authorization: Bearer <token> header",
|
|
369
|
+
});
|
|
488
370
|
return;
|
|
489
371
|
}
|
|
490
372
|
readBody(req)
|
|
@@ -571,7 +453,11 @@ function handleRequest(req, res) {
|
|
|
571
453
|
// ── POST /navigator/command ──
|
|
572
454
|
if (req.method === "POST" && path === "/navigator/command") {
|
|
573
455
|
if (!validateBridgeAuth(req)) {
|
|
574
|
-
sendJSON(res, 401, {
|
|
456
|
+
sendJSON(res, 401, {
|
|
457
|
+
ok: false,
|
|
458
|
+
error: "unauthorized",
|
|
459
|
+
hint: "Include Authorization: Bearer <token> header",
|
|
460
|
+
});
|
|
575
461
|
return;
|
|
576
462
|
}
|
|
577
463
|
readBody(req)
|
|
@@ -894,9 +780,8 @@ function handleRequest(req, res) {
|
|
|
894
780
|
proxyReq.on("error", (err) => {
|
|
895
781
|
sendJSON(res, 502, {
|
|
896
782
|
ok: false,
|
|
897
|
-
error: `OC Web UI not reachable on port ${ocUIPort}`,
|
|
783
|
+
error: `OC Web UI not reachable on port ${ocUIPort} — make sure the OC gateway is running`,
|
|
898
784
|
detail: err.message,
|
|
899
|
-
hint: `Not found — Reverse proxy rule for /ui/* → localhost:${ocUIPort} is working, but the web UI is not running. Start it with: openclaw gateway start`,
|
|
900
785
|
});
|
|
901
786
|
});
|
|
902
787
|
|
|
@@ -906,7 +791,7 @@ function handleRequest(req, res) {
|
|
|
906
791
|
}
|
|
907
792
|
|
|
908
793
|
// ── Reverse proxy: /api/* → OC Gateway (localhost:ocGatewayPort) ─────
|
|
909
|
-
// Keeps /api/ prefix intact
|
|
794
|
+
// Keeps /api/ prefix intact — all API calls including chat go to the gateway.
|
|
910
795
|
if (path.startsWith("/api/")) {
|
|
911
796
|
const targetURL = `${path}${url.search}`;
|
|
912
797
|
|
|
@@ -935,7 +820,10 @@ function handleRequest(req, res) {
|
|
|
935
820
|
ok: false,
|
|
936
821
|
error: `OC Gateway not reachable on port ${ocGatewayPort}`,
|
|
937
822
|
detail: err.message,
|
|
938
|
-
hint:
|
|
823
|
+
hint:
|
|
824
|
+
"Make sure the OC gateway is running on port " +
|
|
825
|
+
ocGatewayPort +
|
|
826
|
+
" (openclaw gateway start)",
|
|
939
827
|
});
|
|
940
828
|
});
|
|
941
829
|
|
|
@@ -1073,12 +961,9 @@ async function main() {
|
|
|
1073
961
|
let noTunnel = false;
|
|
1074
962
|
let withMcp = false;
|
|
1075
963
|
let pm2Setup = false;
|
|
1076
|
-
let tunnelToken = null;
|
|
964
|
+
let tunnelToken = null; // For named tunnels (Cloudflare)
|
|
1077
965
|
let tunnelHostname = null; // For named tunnels (stable URL)
|
|
1078
966
|
let freshIdentity = false; // --new-code: force new pairing code
|
|
1079
|
-
let setupUIFlag = false;
|
|
1080
|
-
let updateUIFlag = false;
|
|
1081
|
-
let noUIFlag = false;
|
|
1082
967
|
|
|
1083
968
|
for (let i = 0; i < args.length; i++) {
|
|
1084
969
|
if (args[i] === "--port" && args[i + 1]) {
|
|
@@ -1111,15 +996,6 @@ async function main() {
|
|
|
1111
996
|
if (args[i] === "--new-code") {
|
|
1112
997
|
freshIdentity = true;
|
|
1113
998
|
}
|
|
1114
|
-
if (args[i] === "--setup-ui") {
|
|
1115
|
-
setupUIFlag = true;
|
|
1116
|
-
}
|
|
1117
|
-
if (args[i] === "--update-ui") {
|
|
1118
|
-
updateUIFlag = true;
|
|
1119
|
-
}
|
|
1120
|
-
if (args[i] === "--no-ui") {
|
|
1121
|
-
noUIFlag = true;
|
|
1122
|
-
}
|
|
1123
999
|
if (args[i] === "--help" || args[i] === "-h") {
|
|
1124
1000
|
console.log(`
|
|
1125
1001
|
${BOLD}openclaw-navigator${RESET} — One-command bridge + tunnel for Navigator
|
|
@@ -1138,9 +1014,6 @@ ${BOLD}Options:${RESET}
|
|
|
1138
1014
|
--no-tunnel Skip auto-tunnel, use SSH or LAN instead
|
|
1139
1015
|
--bind <host> Bind address (default: 127.0.0.1)
|
|
1140
1016
|
--new-code Force a new pairing code (discard saved identity)
|
|
1141
|
-
--setup-ui Force (re)install + build OC Web UI
|
|
1142
|
-
--update-ui Pull latest UI changes and rebuild
|
|
1143
|
-
--no-ui Don't auto-start the web UI
|
|
1144
1017
|
--help Show this help
|
|
1145
1018
|
|
|
1146
1019
|
${BOLD}Stability (recommended for production):${RESET}
|
|
@@ -1149,11 +1022,11 @@ ${BOLD}Stability (recommended for production):${RESET}
|
|
|
1149
1022
|
--tunnel-hostname <host> Hostname for named tunnel (e.g. nav.yourdomain.com)
|
|
1150
1023
|
|
|
1151
1024
|
${BOLD}Routing (through Cloudflare tunnel):${RESET}
|
|
1152
|
-
/ui/*
|
|
1153
|
-
/api/*
|
|
1154
|
-
/ws, WebSocket
|
|
1155
|
-
/health
|
|
1156
|
-
/navigator/*
|
|
1025
|
+
/ui/* → localhost:<ui-port> Web UI (proxied to OC gateway's UI server)
|
|
1026
|
+
/api/* → localhost:<gateway-port> OC gateway API (chat, sessions, etc.)
|
|
1027
|
+
/ws, WebSocket → localhost:<gateway-port> WebSocket proxy to OC gateway
|
|
1028
|
+
/health → bridge itself Health check
|
|
1029
|
+
/navigator/* → bridge itself Navigator control endpoints
|
|
1157
1030
|
|
|
1158
1031
|
${BOLD}Environment variables:${RESET}
|
|
1159
1032
|
OPENCLAW_UI_PORT=4000 Where the web UI runs
|
|
@@ -1176,7 +1049,11 @@ ${BOLD}How it works:${RESET}
|
|
|
1176
1049
|
if (pm2Setup) {
|
|
1177
1050
|
const { execSync: findNode } = await import("node:child_process");
|
|
1178
1051
|
let npxPath;
|
|
1179
|
-
try {
|
|
1052
|
+
try {
|
|
1053
|
+
npxPath = findNode("which npx", { encoding: "utf8" }).trim();
|
|
1054
|
+
} catch {
|
|
1055
|
+
npxPath = "npx";
|
|
1056
|
+
}
|
|
1180
1057
|
|
|
1181
1058
|
const ecosystemContent = `// PM2 ecosystem config for openclaw-navigator bridge
|
|
1182
1059
|
// Generated by: npx openclaw-navigator --pm2-setup
|
|
@@ -1185,7 +1062,7 @@ module.exports = {
|
|
|
1185
1062
|
apps: [{
|
|
1186
1063
|
name: "openclaw-navigator",
|
|
1187
1064
|
script: "${npxPath}",
|
|
1188
|
-
args: "openclaw-navigator@latest --mcp --
|
|
1065
|
+
args: "openclaw-navigator@latest --mcp --port ${port}",
|
|
1189
1066
|
cwd: "${homedir()}",
|
|
1190
1067
|
autorestart: true,
|
|
1191
1068
|
max_restarts: 50,
|
|
@@ -1212,7 +1089,9 @@ module.exports = {
|
|
|
1212
1089
|
console.log(` ${CYAN}npm install -g pm2${RESET} ${DIM}(if not installed)${RESET}`);
|
|
1213
1090
|
console.log(` ${CYAN}pm2 start ecosystem.config.cjs${RESET}`);
|
|
1214
1091
|
console.log(` ${CYAN}pm2 save${RESET} ${DIM}(auto-start on boot)${RESET}`);
|
|
1215
|
-
console.log(
|
|
1092
|
+
console.log(
|
|
1093
|
+
` ${CYAN}pm2 startup${RESET} ${DIM}(install system startup hook)${RESET}`,
|
|
1094
|
+
);
|
|
1216
1095
|
console.log("");
|
|
1217
1096
|
console.log(`${BOLD}Useful PM2 commands:${RESET}`);
|
|
1218
1097
|
console.log(` ${CYAN}pm2 logs openclaw-navigator${RESET} ${DIM}(view logs)${RESET}`);
|
|
@@ -1239,49 +1118,62 @@ module.exports = {
|
|
|
1239
1118
|
server.listen(port, bindHost, () => resolve());
|
|
1240
1119
|
});
|
|
1241
1120
|
|
|
1242
|
-
// ── WebSocket
|
|
1243
|
-
//
|
|
1121
|
+
// ── WebSocket proxy — forward /ws connections to OC gateway ─────────
|
|
1122
|
+
// The bridge is a transparent relay: Navigator connects here, we pipe to
|
|
1123
|
+
// the OC gateway WebSocket at ws://localhost:ocGatewayPort/ws.
|
|
1244
1124
|
server.on("upgrade", (req, socket, head) => {
|
|
1245
1125
|
const reqUrl = new URL(req.url ?? "/", "http://localhost");
|
|
1246
1126
|
const reqPath = reqUrl.pathname;
|
|
1247
1127
|
|
|
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
|
-
});
|
|
1268
|
-
|
|
1269
|
-
proxy.on("error", (err) => {
|
|
1270
|
-
console.log(` ${DIM}WebSocket proxy error: ${err.message}${RESET}`);
|
|
1271
|
-
socket.destroy();
|
|
1272
|
-
});
|
|
1128
|
+
// Only handle /ws paths
|
|
1129
|
+
if (reqPath !== "/ws" && !reqPath.startsWith("/ws/")) {
|
|
1130
|
+
socket.destroy();
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1273
1133
|
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1134
|
+
// Connect to the gateway WebSocket
|
|
1135
|
+
const gwSocket = netConnect(ocGatewayPort, "127.0.0.1", () => {
|
|
1136
|
+
// Forward the HTTP upgrade request to the gateway
|
|
1137
|
+
const upgradeHeaders = [
|
|
1138
|
+
`${req.method} ${req.url} HTTP/${req.httpVersion}`,
|
|
1139
|
+
...Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`),
|
|
1140
|
+
"",
|
|
1141
|
+
"",
|
|
1142
|
+
].join("\r\n");
|
|
1143
|
+
gwSocket.write(upgradeHeaders);
|
|
1144
|
+
if (head && head.length > 0) {
|
|
1145
|
+
gwSocket.write(head);
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1277
1148
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
});
|
|
1281
|
-
} else {
|
|
1282
|
-
// Not a proxied path — reject
|
|
1149
|
+
gwSocket.on("error", (err) => {
|
|
1150
|
+
console.log(` ${DIM}WS proxy: gateway unreachable — ${err.message}${RESET}`);
|
|
1283
1151
|
socket.destroy();
|
|
1284
|
-
}
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
// Once the gateway responds with 101, pipe bidirectionally
|
|
1155
|
+
gwSocket.once("data", (firstChunk) => {
|
|
1156
|
+
const response = firstChunk.toString();
|
|
1157
|
+
if (response.startsWith("HTTP/1.1 101")) {
|
|
1158
|
+
// Forward the 101 response to the client
|
|
1159
|
+
socket.write(firstChunk);
|
|
1160
|
+
// Now pipe both directions transparently
|
|
1161
|
+
socket.pipe(gwSocket);
|
|
1162
|
+
gwSocket.pipe(socket);
|
|
1163
|
+
|
|
1164
|
+
socket.on("close", () => gwSocket.destroy());
|
|
1165
|
+
gwSocket.on("close", () => socket.destroy());
|
|
1166
|
+
socket.on("error", () => gwSocket.destroy());
|
|
1167
|
+
gwSocket.on("error", () => socket.destroy());
|
|
1168
|
+
|
|
1169
|
+
console.log(` ${DIM}WebSocket proxied to gateway:${ocGatewayPort}${RESET}`);
|
|
1170
|
+
} else {
|
|
1171
|
+
// Gateway rejected the upgrade
|
|
1172
|
+
socket.write(firstChunk);
|
|
1173
|
+
socket.destroy();
|
|
1174
|
+
gwSocket.destroy();
|
|
1175
|
+
}
|
|
1176
|
+
});
|
|
1285
1177
|
});
|
|
1286
1178
|
|
|
1287
1179
|
ok(`Bridge server running on ${bindHost}:${port}`);
|
|
@@ -1323,14 +1215,15 @@ module.exports = {
|
|
|
1323
1215
|
const MAX_TUNNEL_RECONNECT_DELAY = 60_000; // cap at 60s
|
|
1324
1216
|
|
|
1325
1217
|
async function startOrReconnectTunnel() {
|
|
1326
|
-
if (!cloudflaredBin)
|
|
1218
|
+
if (!cloudflaredBin) {
|
|
1219
|
+
return null;
|
|
1220
|
+
}
|
|
1327
1221
|
|
|
1328
1222
|
// Named tunnel mode (stable URL, no reconnect gymnastics needed)
|
|
1223
|
+
// The tunnel routing (hostname → localhost:port) is configured in
|
|
1224
|
+
// the Cloudflare Zero Trust dashboard, not on the CLI.
|
|
1329
1225
|
if (tunnelToken) {
|
|
1330
1226
|
const tunnelArgs = ["tunnel", "run", "--token", tunnelToken];
|
|
1331
|
-
if (tunnelHostname) {
|
|
1332
|
-
tunnelArgs.push("--url", `http://localhost:${port}`);
|
|
1333
|
-
}
|
|
1334
1227
|
const child = spawn(cloudflaredBin, tunnelArgs, {
|
|
1335
1228
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1336
1229
|
});
|
|
@@ -1349,6 +1242,14 @@ module.exports = {
|
|
|
1349
1242
|
gatewayURL = namedURL;
|
|
1350
1243
|
pairingData.url = namedURL;
|
|
1351
1244
|
ok(`Named tunnel active: ${CYAN}${namedURL}${RESET}`);
|
|
1245
|
+
|
|
1246
|
+
// Register with relay so QuickConnect pairing codes resolve
|
|
1247
|
+
const relayOk = await registerWithRelay(pairingCode, namedURL, token, displayName);
|
|
1248
|
+
if (relayOk) {
|
|
1249
|
+
ok("Pairing code registered with relay");
|
|
1250
|
+
} else {
|
|
1251
|
+
warn("Relay unavailable — use the deep link or tunnel URL directly");
|
|
1252
|
+
}
|
|
1352
1253
|
}
|
|
1353
1254
|
return child;
|
|
1354
1255
|
}
|
|
@@ -1356,9 +1257,14 @@ module.exports = {
|
|
|
1356
1257
|
// Quick Tunnel mode — URL changes on every start
|
|
1357
1258
|
const result = await startTunnel(cloudflaredBin, port);
|
|
1358
1259
|
if (!result) {
|
|
1359
|
-
const delay = Math.min(
|
|
1260
|
+
const delay = Math.min(
|
|
1261
|
+
2000 * Math.pow(2, tunnelReconnectAttempts),
|
|
1262
|
+
MAX_TUNNEL_RECONNECT_DELAY,
|
|
1263
|
+
);
|
|
1360
1264
|
tunnelReconnectAttempts++;
|
|
1361
|
-
warn(
|
|
1265
|
+
warn(
|
|
1266
|
+
`Tunnel failed — retrying in ${Math.round(delay / 1000)}s (attempt ${tunnelReconnectAttempts})...`,
|
|
1267
|
+
);
|
|
1362
1268
|
setTimeout(startOrReconnectTunnel, delay);
|
|
1363
1269
|
return null;
|
|
1364
1270
|
}
|
|
@@ -1390,7 +1296,10 @@ module.exports = {
|
|
|
1390
1296
|
tunnelProcess = null;
|
|
1391
1297
|
activeTunnelURL = null;
|
|
1392
1298
|
// Exponential backoff restart
|
|
1393
|
-
const delay = Math.min(
|
|
1299
|
+
const delay = Math.min(
|
|
1300
|
+
3000 * Math.pow(2, tunnelReconnectAttempts),
|
|
1301
|
+
MAX_TUNNEL_RECONNECT_DELAY,
|
|
1302
|
+
);
|
|
1394
1303
|
tunnelReconnectAttempts++;
|
|
1395
1304
|
setTimeout(startOrReconnectTunnel, delay);
|
|
1396
1305
|
});
|
|
@@ -1439,33 +1348,10 @@ module.exports = {
|
|
|
1439
1348
|
console.log("");
|
|
1440
1349
|
}
|
|
1441
1350
|
|
|
1442
|
-
// ── OC Web UI: auto-setup + start ─────────────────────────────────────
|
|
1443
|
-
if (!noUIFlag) {
|
|
1444
|
-
if (setupUIFlag) {
|
|
1445
|
-
await setupUI();
|
|
1446
|
-
} else if (updateUIFlag) {
|
|
1447
|
-
await updateUI();
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
if (await isUIInstalled()) {
|
|
1451
|
-
await startUIServer(ocUIPort);
|
|
1452
|
-
} else if (!setupUIFlag && !noUIFlag) {
|
|
1453
|
-
heading("OC Web UI not found — setting up automatically");
|
|
1454
|
-
const setupOk = await setupUI();
|
|
1455
|
-
if (setupOk) {
|
|
1456
|
-
await startUIServer(ocUIPort);
|
|
1457
|
-
} else {
|
|
1458
|
-
warn("Web UI setup failed — you can retry with: npx openclaw-navigator --setup-ui");
|
|
1459
|
-
warn("The bridge will still work, but /ui/* won't serve the dashboard");
|
|
1460
|
-
}
|
|
1461
|
-
}
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
1351
|
// ── Step 4: Register initial pairing code with relay ────────────────
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
// Local mode — register code for local resolution
|
|
1352
|
+
// Both quick-tunnel and named-tunnel register inside startOrReconnectTunnel()
|
|
1353
|
+
if (!tunnelURL) {
|
|
1354
|
+
// Local mode — no relay registration needed
|
|
1469
1355
|
}
|
|
1470
1356
|
|
|
1471
1357
|
// ── Step 5: Show connection info ──────────────────────────────────────
|
|
@@ -1492,9 +1378,9 @@ module.exports = {
|
|
|
1492
1378
|
// ── Show OC Web UI access + routing info ─────────────────────────────
|
|
1493
1379
|
const uiURL = tunnelURL ? `${tunnelURL}/ui/` : `http://localhost:${port}/ui/`;
|
|
1494
1380
|
console.log(` ${BOLD}OC Web UI:${RESET} ${CYAN}${uiURL}${RESET}`);
|
|
1495
|
-
info(` /ui/*
|
|
1496
|
-
info(` /api/*
|
|
1497
|
-
info(` /ws
|
|
1381
|
+
info(` /ui/* → localhost:${ocUIPort} (web UI proxy)`);
|
|
1382
|
+
info(` /api/* → localhost:${ocGatewayPort} (OC gateway)`);
|
|
1383
|
+
info(` /ws → localhost:${ocGatewayPort} (WebSocket proxy)`);
|
|
1498
1384
|
|
|
1499
1385
|
// ── Startup health checks ──────────────────────────────────────────
|
|
1500
1386
|
console.log("");
|
|
@@ -1627,7 +1513,9 @@ module.exports = {
|
|
|
1627
1513
|
|
|
1628
1514
|
mkdirSync(mcporterDir, { recursive: true });
|
|
1629
1515
|
writeFileSync(mcporterConfigPath, JSON.stringify(mcporterConfig, null, 2) + "\n", "utf8");
|
|
1630
|
-
ok(
|
|
1516
|
+
ok(
|
|
1517
|
+
"Registered MCP server with mcporter (34 tools: 16 browser + 10 AI + 5 profiling + 3 chat)",
|
|
1518
|
+
);
|
|
1631
1519
|
info(` Config: ${mcporterConfigPath}`);
|
|
1632
1520
|
|
|
1633
1521
|
// Step 3: Install the navigator-bridge skill so the OC agent knows about all tools
|
|
@@ -1672,7 +1560,7 @@ module.exports = {
|
|
|
1672
1560
|
"mcporter call navigator.navigator_status",
|
|
1673
1561
|
BT,
|
|
1674
1562
|
"",
|
|
1675
|
-
"## All
|
|
1563
|
+
"## All 34 tools",
|
|
1676
1564
|
"",
|
|
1677
1565
|
"### Browser Control (16 tools)",
|
|
1678
1566
|
"",
|
|
@@ -1720,6 +1608,14 @@ module.exports = {
|
|
|
1720
1608
|
"| `navigator_get_user_profile` | Get aggregated user interest profile |",
|
|
1721
1609
|
"| `navigator_save_user_profile` | Save/update user profile from browsing patterns |",
|
|
1722
1610
|
"",
|
|
1611
|
+
"### Chat (3 tools)",
|
|
1612
|
+
"",
|
|
1613
|
+
"| Tool | What it does |",
|
|
1614
|
+
"|------|-------------|",
|
|
1615
|
+
"| `navigator_get_chat_messages` | Get recent messages from the Navigator chat pane |",
|
|
1616
|
+
"| `navigator_chat_respond` | Send a response message to the Navigator chat pane |",
|
|
1617
|
+
"| `navigator_chat_stream` | Stream partial text to the chat pane (typing effect) |",
|
|
1618
|
+
"",
|
|
1723
1619
|
"## Usage",
|
|
1724
1620
|
"",
|
|
1725
1621
|
BT + "bash",
|
|
@@ -1814,7 +1710,7 @@ module.exports = {
|
|
|
1814
1710
|
' url="<page_url>" \\',
|
|
1815
1711
|
' title="<page_title>" \\',
|
|
1816
1712
|
' summary="<haiku_generated_summary>" \\',
|
|
1817
|
-
|
|
1713
|
+
' signals=\'{"names":[],"interests":[],"services":[],"purchases":[],"intent":[],"topics":[]}\'',
|
|
1818
1714
|
BT,
|
|
1819
1715
|
"",
|
|
1820
1716
|
"### 3. Daily Profile Synthesis (You Do This — Use Opus 4.6)",
|
|
@@ -1862,7 +1758,7 @@ module.exports = {
|
|
|
1862
1758
|
"",
|
|
1863
1759
|
"- **Be smart about noise**: Ignore login pages, error pages, redirects",
|
|
1864
1760
|
"- **Respect privacy**: Don't store passwords, tokens, or PII like SSNs/credit cards",
|
|
1865
|
-
|
|
1761
|
+
'- **Extract signal from noise**: A visit to "Nike Air Max" tells you about shopping interest, not just a URL',
|
|
1866
1762
|
"- **Cross-reference**: If user visits Stripe docs AND Vercel, they're likely a developer building a SaaS",
|
|
1867
1763
|
"- **Temporal awareness**: Morning habits vs evening habits, weekday vs weekend",
|
|
1868
1764
|
"- **Don't hallucinate**: Only include signals backed by actual browsing data",
|
|
@@ -1871,7 +1767,7 @@ module.exports = {
|
|
|
1871
1767
|
"",
|
|
1872
1768
|
"- **Summarization**: Every 10 minutes while the user is actively browsing",
|
|
1873
1769
|
"- **Profile synthesis**: Once daily, preferably at end of day",
|
|
1874
|
-
|
|
1770
|
+
'- **On demand**: User can ask "update my profile" or "what do you know about me"',
|
|
1875
1771
|
"",
|
|
1876
1772
|
].join("\n");
|
|
1877
1773
|
mkdirSync(profilerSkillDir, { recursive: true });
|
|
@@ -1908,6 +1804,19 @@ module.exports = {
|
|
|
1908
1804
|
}
|
|
1909
1805
|
const npxForPlist = join(dirname(nodeForPlist), "npx");
|
|
1910
1806
|
|
|
1807
|
+
// Build ProgramArguments — include named tunnel flags if set
|
|
1808
|
+
let tunnelPlistArgs = "";
|
|
1809
|
+
if (tunnelToken) {
|
|
1810
|
+
tunnelPlistArgs += `
|
|
1811
|
+
<string>--tunnel-token</string>
|
|
1812
|
+
<string>${tunnelToken}</string>`;
|
|
1813
|
+
}
|
|
1814
|
+
if (tunnelHostname) {
|
|
1815
|
+
tunnelPlistArgs += `
|
|
1816
|
+
<string>--tunnel-hostname</string>
|
|
1817
|
+
<string>${tunnelHostname}</string>`;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1911
1820
|
const plistContent = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1912
1821
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1913
1822
|
<plist version="1.0">
|
|
@@ -1920,7 +1829,7 @@ module.exports = {
|
|
|
1920
1829
|
<string>openclaw-navigator@latest</string>
|
|
1921
1830
|
<string>--mcp</string>
|
|
1922
1831
|
<string>--port</string>
|
|
1923
|
-
<string>${port}</string
|
|
1832
|
+
<string>${port}</string>${tunnelPlistArgs}
|
|
1924
1833
|
</array>
|
|
1925
1834
|
<key>EnvironmentVariables</key>
|
|
1926
1835
|
<dict>
|
|
@@ -1972,10 +1881,6 @@ module.exports = {
|
|
|
1972
1881
|
// ── Graceful shutdown ─────────────────────────────────────────────────
|
|
1973
1882
|
const shutdown = () => {
|
|
1974
1883
|
console.log(`\n${DIM}Shutting down bridge...${RESET}`);
|
|
1975
|
-
if (uiProcess) {
|
|
1976
|
-
uiProcess.kill();
|
|
1977
|
-
uiProcess = null;
|
|
1978
|
-
}
|
|
1979
1884
|
if (mcpProcess) {
|
|
1980
1885
|
mcpProcess.kill();
|
|
1981
1886
|
}
|
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.
|
|
@@ -18,8 +18,8 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
18
18
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
19
19
|
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
20
20
|
import { readFileSync } from "node:fs";
|
|
21
|
-
import { join } from "node:path";
|
|
22
21
|
import { homedir } from "node:os";
|
|
22
|
+
import { join } from "node:path";
|
|
23
23
|
|
|
24
24
|
// ── Configuration ─────────────────────────────────────────────────────────
|
|
25
25
|
|
|
@@ -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,59 @@ const TOOLS = [
|
|
|
596
596
|
required: ["profile"],
|
|
597
597
|
},
|
|
598
598
|
},
|
|
599
|
+
|
|
600
|
+
// ── Chat Tools ──
|
|
601
|
+
{
|
|
602
|
+
name: "navigator_get_chat_messages",
|
|
603
|
+
description:
|
|
604
|
+
"Get recent chat messages from the Navigator chat pane. Use this to see what the user is asking.",
|
|
605
|
+
inputSchema: {
|
|
606
|
+
type: "object",
|
|
607
|
+
properties: {
|
|
608
|
+
sessionKey: {
|
|
609
|
+
type: "string",
|
|
610
|
+
description: "Chat session key (default: main)",
|
|
611
|
+
default: "main",
|
|
612
|
+
},
|
|
613
|
+
limit: { type: "number", description: "Max messages to return (default: 20)", default: 20 },
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
name: "navigator_chat_respond",
|
|
619
|
+
description:
|
|
620
|
+
"Send a response message to the Navigator chat pane. The user will see this as an agent message.",
|
|
621
|
+
inputSchema: {
|
|
622
|
+
type: "object",
|
|
623
|
+
properties: {
|
|
624
|
+
message: { type: "string", description: "The response message to send" },
|
|
625
|
+
sessionKey: {
|
|
626
|
+
type: "string",
|
|
627
|
+
description: "Chat session key (default: main)",
|
|
628
|
+
default: "main",
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
required: ["message"],
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
name: "navigator_chat_stream",
|
|
636
|
+
description:
|
|
637
|
+
"Stream a partial response to the Navigator chat pane (for real-time typing effect).",
|
|
638
|
+
inputSchema: {
|
|
639
|
+
type: "object",
|
|
640
|
+
properties: {
|
|
641
|
+
text: { type: "string", description: "Partial text to stream" },
|
|
642
|
+
sessionKey: {
|
|
643
|
+
type: "string",
|
|
644
|
+
description: "Chat session key (default: main)",
|
|
645
|
+
default: "main",
|
|
646
|
+
},
|
|
647
|
+
runId: { type: "string", description: "Run ID for grouping streamed chunks" },
|
|
648
|
+
},
|
|
649
|
+
required: ["text"],
|
|
650
|
+
},
|
|
651
|
+
},
|
|
599
652
|
];
|
|
600
653
|
|
|
601
654
|
// ── Tool handler dispatch ─────────────────────────────────────────────────
|
|
@@ -720,9 +773,7 @@ async function handleTool(name, args) {
|
|
|
720
773
|
|
|
721
774
|
// ── AI Browser Intelligence ──
|
|
722
775
|
case "navigator_analyze_page":
|
|
723
|
-
return jsonResult(
|
|
724
|
-
await sendCommand("ai.analyze", {}, { commandName: "ai.analyze" }),
|
|
725
|
-
);
|
|
776
|
+
return jsonResult(await sendCommand("ai.analyze", {}, { commandName: "ai.analyze" }));
|
|
726
777
|
|
|
727
778
|
case "navigator_find_element":
|
|
728
779
|
return jsonResult(
|
|
@@ -730,9 +781,7 @@ async function handleTool(name, args) {
|
|
|
730
781
|
);
|
|
731
782
|
|
|
732
783
|
case "navigator_is_ready":
|
|
733
|
-
return jsonResult(
|
|
734
|
-
await sendCommand("ai.ready", {}, { commandName: "ai.ready" }),
|
|
735
|
-
);
|
|
784
|
+
return jsonResult(await sendCommand("ai.ready", {}, { commandName: "ai.ready" }));
|
|
736
785
|
|
|
737
786
|
case "navigator_wait_for_element":
|
|
738
787
|
return jsonResult(
|
|
@@ -754,36 +803,22 @@ async function handleTool(name, args) {
|
|
|
754
803
|
|
|
755
804
|
case "navigator_smart_fill":
|
|
756
805
|
return jsonResult(
|
|
757
|
-
await sendCommand(
|
|
758
|
-
"ai.fill",
|
|
759
|
-
{ data: args.data },
|
|
760
|
-
{ commandName: "ai.fill" },
|
|
761
|
-
),
|
|
806
|
+
await sendCommand("ai.fill", { data: args.data }, { commandName: "ai.fill" }),
|
|
762
807
|
);
|
|
763
808
|
|
|
764
809
|
case "navigator_intercept_api":
|
|
765
|
-
return jsonResult(
|
|
766
|
-
await sendCommand("ai.intercept", {}, { commandName: "ai.intercept" }),
|
|
767
|
-
);
|
|
810
|
+
return jsonResult(await sendCommand("ai.intercept", {}, { commandName: "ai.intercept" }));
|
|
768
811
|
|
|
769
812
|
case "navigator_set_cookies":
|
|
770
813
|
return jsonResult(
|
|
771
|
-
await sendCommand(
|
|
772
|
-
"ai.cookies",
|
|
773
|
-
{ cookies: args.cookies },
|
|
774
|
-
{ commandName: "ai.cookies" },
|
|
775
|
-
),
|
|
814
|
+
await sendCommand("ai.cookies", { cookies: args.cookies }, { commandName: "ai.cookies" }),
|
|
776
815
|
);
|
|
777
816
|
|
|
778
817
|
case "navigator_get_performance":
|
|
779
|
-
return jsonResult(
|
|
780
|
-
await sendCommand("ai.metrics", {}, { commandName: "ai.metrics" }),
|
|
781
|
-
);
|
|
818
|
+
return jsonResult(await sendCommand("ai.metrics", {}, { commandName: "ai.metrics" }));
|
|
782
819
|
|
|
783
820
|
case "navigator_get_page_state":
|
|
784
|
-
return jsonResult(
|
|
785
|
-
await sendCommand("ai.state", {}, { commandName: "ai.state" }),
|
|
786
|
-
);
|
|
821
|
+
return jsonResult(await sendCommand("ai.state", {}, { commandName: "ai.state" }));
|
|
787
822
|
|
|
788
823
|
// ── User Profiling Tools ──
|
|
789
824
|
case "navigator_get_page_visits": {
|
|
@@ -823,6 +858,36 @@ async function handleTool(name, args) {
|
|
|
823
858
|
}),
|
|
824
859
|
);
|
|
825
860
|
|
|
861
|
+
// ── Chat Tools ──
|
|
862
|
+
case "navigator_get_chat_messages": {
|
|
863
|
+
const sessionKey = args.sessionKey || "main";
|
|
864
|
+
const limit = args.limit ?? 20;
|
|
865
|
+
const data = await bridgeGet(
|
|
866
|
+
`/api/sessions/history?sessionKey=${encodeURIComponent(sessionKey)}`,
|
|
867
|
+
);
|
|
868
|
+
if (data.ok && Array.isArray(data.messages)) {
|
|
869
|
+
return jsonResult({ ok: true, messages: data.messages.slice(-limit) });
|
|
870
|
+
}
|
|
871
|
+
return jsonResult(data);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
case "navigator_chat_respond":
|
|
875
|
+
return jsonResult(
|
|
876
|
+
await bridgePost("/api/sessions/respond", {
|
|
877
|
+
content: args.message,
|
|
878
|
+
sessionKey: args.sessionKey || "main",
|
|
879
|
+
}),
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
case "navigator_chat_stream":
|
|
883
|
+
return jsonResult(
|
|
884
|
+
await bridgePost("/api/sessions/stream", {
|
|
885
|
+
text: args.text,
|
|
886
|
+
sessionKey: args.sessionKey || "main",
|
|
887
|
+
runId: args.runId || null,
|
|
888
|
+
}),
|
|
889
|
+
);
|
|
890
|
+
|
|
826
891
|
default:
|
|
827
892
|
return errorResult(`Unknown tool: ${name}`);
|
|
828
893
|
}
|
|
@@ -835,7 +900,7 @@ async function handleTool(name, args) {
|
|
|
835
900
|
// ── MCP server wiring ─────────────────────────────────────────────────────
|
|
836
901
|
|
|
837
902
|
const server = new Server(
|
|
838
|
-
{ name: "openclaw-navigator", version: "5.
|
|
903
|
+
{ name: "openclaw-navigator", version: "5.1.0" },
|
|
839
904
|
{ capabilities: { tools: {} } },
|
|
840
905
|
);
|
|
841
906
|
|