openclaw-navigator 5.8.1 → 5.8.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.
Files changed (2) hide show
  1. package/cli.mjs +249 -176
  2. package/package.json +1 -1
package/cli.mjs CHANGED
@@ -381,7 +381,9 @@ function getChatSession(sessionKey = "main") {
381
381
  let bffCookieJar = "";
382
382
 
383
383
  function captureBFFCookies(setCookieHeaders) {
384
- if (!setCookieHeaders) return;
384
+ if (!setCookieHeaders) {
385
+ return;
386
+ }
385
387
  const cookies = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders];
386
388
  // Extract just the cookie name=value (strip attributes like path, domain, etc.)
387
389
  const extracted = cookies.map((c) => c.split(";")[0].trim()).filter(Boolean);
@@ -1010,11 +1012,10 @@ function handleRequest(req, res) {
1010
1012
  }
1011
1013
 
1012
1014
  // ── Reverse proxy: /ui/* → OC Web UI (localhost:ocUIPort) ──────────────
1013
- // Strip /ui prefixthe Next.js app serves at root on port 4000.
1014
- // /ui/ /, /ui/dashboard/dashboard, /ui/_next/*/_next/*
1015
+ // Forward /ui/* paths as-is — Next.js has basePath: "/ui" so it expects
1016
+ // the /ui prefix on all routes: /ui/ → home, /ui/api/chatAPI, etc.
1015
1017
  if (path === "/ui" || path.startsWith("/ui/")) {
1016
- const strippedPath = path === "/ui" ? "/" : path.slice(3); // remove "/ui"
1017
- const targetURL = `${strippedPath}${url.search}`;
1018
+ const targetURL = `${path}${url.search}`;
1018
1019
  const incomingHost = req.headers.host || "localhost";
1019
1020
 
1020
1021
  const proxyOpts = {
@@ -1040,20 +1041,12 @@ function handleRequest(req, res) {
1040
1041
  headers["access-control-allow-headers"] = "Content-Type, Authorization, Cookie";
1041
1042
  headers["access-control-allow-credentials"] = "true";
1042
1043
 
1043
- // Fix redirects — rewrite Location: strip localhost, add /ui prefix back
1044
- // Since we stripped /ui before proxying, any redirect from the app
1045
- // (e.g. /login, /dashboard) needs the /ui prefix added back for the
1046
- // browser to stay within our /ui/* proxy.
1044
+ // Fix redirects — strip localhost so browser stays on tunnel URL.
1045
+ // Next.js basePath "/ui" means all redirects already include /ui/.
1047
1046
  if (headers.location) {
1048
- let loc = headers.location
1047
+ headers.location = headers.location
1049
1048
  .replace(`http://127.0.0.1:${ocUIPort}`, "")
1050
1049
  .replace(`http://localhost:${ocUIPort}`, "");
1051
- // If the redirect is a relative path (starts with /), add /ui prefix
1052
- // BUT don't double-prefix if it already starts with /ui
1053
- if (loc.startsWith("/") && !loc.startsWith("/ui")) {
1054
- loc = "/ui" + loc;
1055
- }
1056
- headers.location = loc;
1057
1050
  }
1058
1051
 
1059
1052
  // Fix cookies — remove domain restriction so they work through tunnel
@@ -1225,7 +1218,7 @@ function handleRequest(req, res) {
1225
1218
  const proxyOpts = {
1226
1219
  hostname: "127.0.0.1",
1227
1220
  port: ocUIPort,
1228
- path: `/api/chat`,
1221
+ path: `/ui/api/chat`,
1229
1222
  method: "POST",
1230
1223
  timeout: 120000, // 2 min — agent may take a while
1231
1224
  headers: {
@@ -1235,176 +1228,223 @@ function handleRequest(req, res) {
1235
1228
  },
1236
1229
  };
1237
1230
 
1238
- console.log(` ${DIM}→ Relaying to BFF /api/chat (port ${ocUIPort}) with ${chatHistory.length} messages${relayCookie ? " +cookie" : " NO-COOKIE"}${RESET}`);
1239
- console.log(` ${DIM} Body: ${proxyBody.substring(0, 300)}${proxyBody.length > 300 ? "..." : ""}${RESET}`);
1231
+ console.log(
1232
+ ` ${DIM} Relaying to BFF /ui/api/chat (port ${ocUIPort}) with ${chatHistory.length} messages${relayCookie ? " +cookie" : " NO-COOKIE"}${RESET}`,
1233
+ );
1234
+ console.log(
1235
+ ` ${DIM} Body: ${proxyBody.substring(0, 300)}${proxyBody.length > 300 ? "..." : ""}${RESET}`,
1236
+ );
1240
1237
 
1241
1238
  // Helper: send the relay request (with optional retry after login)
1242
1239
  function sendBFFRelay(opts, body, retryCount = 0) {
1243
- const proxyReq = httpRequest(opts, (proxyRes) => {
1244
- // Capture any cookies the BFF sends (login session, etc.)
1245
- if (proxyRes.headers["set-cookie"]) {
1246
- captureBFFCookies(proxyRes.headers["set-cookie"]);
1247
- }
1240
+ const proxyReq = httpRequest(opts, (proxyRes) => {
1241
+ // Capture any cookies the BFF sends (login session, etc.)
1242
+ if (proxyRes.headers["set-cookie"]) {
1243
+ captureBFFCookies(proxyRes.headers["set-cookie"]);
1244
+ }
1248
1245
 
1249
- // If redirect (307/302 to /login) — follow it to seed cookies, then retry
1250
- if ((proxyRes.statusCode === 307 || proxyRes.statusCode === 302) && retryCount < 2) {
1251
- proxyRes.resume(); // drain
1252
- const loginPath = proxyRes.headers.location || "/login";
1253
- console.log(` ${DIM}← BFF redirect → ${loginPath} — following to seed cookies...${RESET}`);
1246
+ // If redirect (307/302 to /login) — follow it to seed cookies, then retry
1247
+ if ((proxyRes.statusCode === 307 || proxyRes.statusCode === 302) && retryCount < 2) {
1248
+ proxyRes.resume(); // drain
1249
+ const loginPath = proxyRes.headers.location || "/login";
1250
+ console.log(
1251
+ ` ${DIM}← BFF redirect → ${loginPath} — following to seed cookies...${RESET}`,
1252
+ );
1253
+
1254
+ // Hit the login page to get session cookies
1255
+ const loginReq = httpRequest(
1256
+ {
1257
+ hostname: "127.0.0.1",
1258
+ port: ocUIPort,
1259
+ path: loginPath,
1260
+ method: "GET",
1261
+ timeout: 5000,
1262
+ headers: bffCookieJar ? { cookie: bffCookieJar } : {},
1263
+ },
1264
+ (loginRes) => {
1265
+ if (loginRes.headers["set-cookie"]) {
1266
+ captureBFFCookies(loginRes.headers["set-cookie"]);
1267
+ }
1268
+ loginRes.resume(); // drain
1269
+ console.log(
1270
+ ` ${DIM}← Login page: ${loginRes.statusCode} — cookies: ${bffCookieJar ? "yes" : "no"}${RESET}`,
1271
+ );
1272
+
1273
+ // Retry the original request with new cookies
1274
+ if (bffCookieJar) {
1275
+ opts.headers = { ...opts.headers, cookie: bffCookieJar };
1276
+ console.log(` ${DIM}→ Retrying /ui/api/chat with cookies...${RESET}`);
1277
+ sendBFFRelay(opts, body, retryCount + 1);
1278
+ } else {
1279
+ console.log(
1280
+ ` ${DIM}No cookies from login — BFF may require real auth${RESET}`,
1281
+ );
1282
+ broadcastToWS({
1283
+ type: "chat.final",
1284
+ text: "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1285
+ content:
1286
+ "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1287
+ sessionKey,
1288
+ role: "assistant",
1289
+ timestamp: Date.now(),
1290
+ });
1291
+ }
1292
+ },
1293
+ );
1294
+ loginReq.on("error", () => {
1295
+ console.log(` ${DIM}Login page unreachable${RESET}`);
1296
+ });
1297
+ loginReq.end();
1298
+ return;
1299
+ }
1254
1300
 
1255
- // Hit the login page to get session cookies
1256
- const loginReq = httpRequest(
1257
- {
1258
- hostname: "127.0.0.1",
1259
- port: ocUIPort,
1260
- path: loginPath,
1261
- method: "GET",
1262
- timeout: 5000,
1263
- headers: bffCookieJar ? { cookie: bffCookieJar } : {},
1264
- },
1265
- (loginRes) => {
1266
- if (loginRes.headers["set-cookie"]) {
1267
- captureBFFCookies(loginRes.headers["set-cookie"]);
1268
- }
1269
- loginRes.resume(); // drain
1270
- console.log(` ${DIM}← Login page: ${loginRes.statusCode} — cookies: ${bffCookieJar ? "yes" : "no"}${RESET}`);
1271
-
1272
- // Retry the original request with new cookies
1273
- if (bffCookieJar) {
1274
- opts.headers = { ...opts.headers, cookie: bffCookieJar };
1275
- console.log(` ${DIM}→ Retrying /api/chat with cookies...${RESET}`);
1276
- sendBFFRelay(opts, body, retryCount + 1);
1277
- } else {
1278
- console.log(` ${DIM}No cookies from login — BFF may require real auth${RESET}`);
1279
- broadcastToWS({
1280
- type: "chat.final",
1281
- text: "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1282
- content: "⚠️ Could not authenticate with OC — try opening the web UI first to log in.",
1283
- sessionKey,
1284
- role: "assistant",
1285
- timestamp: Date.now(),
1286
- });
1287
- }
1288
- },
1301
+ const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
1302
+ const isSSE = contentType.includes("text/event-stream");
1303
+ console.log(
1304
+ ` ${DIM}← BFF response: ${proxyRes.statusCode} ${contentType || "no-content-type"}${RESET}`,
1289
1305
  );
1290
- loginReq.on("error", () => {
1291
- console.log(` ${DIM}Login page unreachable${RESET}`);
1292
- });
1293
- loginReq.end();
1294
- return;
1295
- }
1296
1306
 
1297
- const contentType = (proxyRes.headers["content-type"] || "").toLowerCase();
1298
- const isSSE = contentType.includes("text/event-stream");
1299
- console.log(` ${DIM}← BFF response: ${proxyRes.statusCode} ${contentType || "no-content-type"}${RESET}`);
1300
-
1301
- // Collect the response (SSE or JSON) and extract the assistant's text
1302
- let fullText = "";
1303
- let sseBuffer = "";
1304
-
1305
- proxyRes.setEncoding("utf-8");
1306
- proxyRes.on("data", (chunk) => {
1307
- if (isSSE) {
1308
- sseBuffer += chunk;
1309
- const lines = sseBuffer.split("\n");
1310
- sseBuffer = lines.pop() || "";
1311
- for (const line of lines) {
1312
- if (line.startsWith("data: ")) {
1313
- const raw = line.slice(6).trim();
1314
- if (raw === "[DONE]" || !raw) continue;
1315
- try {
1316
- const evt = JSON.parse(raw);
1317
- const delta =
1318
- evt.choices?.[0]?.delta?.content ||
1319
- evt.delta?.text ||
1320
- evt.text ||
1321
- evt.content ||
1322
- "";
1323
- if (delta) {
1324
- fullText += delta;
1325
- broadcastToWS({
1326
- type: "chat.delta",
1327
- text: fullText,
1328
- delta,
1329
- sessionKey,
1330
- timestamp: Date.now(),
1331
- });
1307
+ // Collect the response (SSE or JSON) and extract the assistant's text
1308
+ let fullText = "";
1309
+ let sseBuffer = "";
1310
+
1311
+ proxyRes.setEncoding("utf-8");
1312
+ proxyRes.on("data", (chunk) => {
1313
+ if (isSSE) {
1314
+ sseBuffer += chunk;
1315
+ const lines = sseBuffer.split("\n");
1316
+ sseBuffer = lines.pop() || "";
1317
+ for (const line of lines) {
1318
+ if (line.startsWith("data: ")) {
1319
+ const raw = line.slice(6).trim();
1320
+ if (raw === "[DONE]" || !raw) {
1321
+ continue;
1322
+ }
1323
+ try {
1324
+ const evt = JSON.parse(raw);
1325
+ const delta =
1326
+ evt.choices?.[0]?.delta?.content ||
1327
+ evt.delta?.text ||
1328
+ evt.text ||
1329
+ evt.content ||
1330
+ "";
1331
+ if (delta) {
1332
+ fullText += delta;
1333
+ broadcastToWS({
1334
+ type: "chat.delta",
1335
+ text: fullText,
1336
+ delta,
1337
+ sessionKey,
1338
+ timestamp: Date.now(),
1339
+ });
1340
+ }
1341
+ } catch {
1342
+ if (raw && raw !== "[DONE]") {
1343
+ fullText += raw;
1344
+ }
1332
1345
  }
1333
- } catch {
1334
- if (raw && raw !== "[DONE]") fullText += raw;
1335
1346
  }
1336
1347
  }
1348
+ } else {
1349
+ sseBuffer += chunk; // Collect JSON body
1337
1350
  }
1338
- } else {
1339
- sseBuffer += chunk; // Collect JSON body
1340
- }
1341
- });
1351
+ });
1342
1352
 
1343
- proxyRes.on("end", () => {
1344
- // Handle remaining buffer for SSE
1345
- if (isSSE && sseBuffer) {
1346
- for (const line of sseBuffer.split("\n")) {
1347
- if (line.startsWith("data: ")) {
1348
- const raw = line.slice(6).trim();
1349
- if (raw && raw !== "[DONE]") {
1350
- try {
1351
- const evt = JSON.parse(raw);
1352
- const delta = evt.choices?.[0]?.delta?.content || evt.delta?.text || evt.text || evt.content || "";
1353
- if (delta) fullText += delta;
1354
- } catch { fullText += raw; }
1353
+ proxyRes.on("end", () => {
1354
+ // Handle remaining buffer for SSE
1355
+ if (isSSE && sseBuffer) {
1356
+ for (const line of sseBuffer.split("\n")) {
1357
+ if (line.startsWith("data: ")) {
1358
+ const raw = line.slice(6).trim();
1359
+ if (raw && raw !== "[DONE]") {
1360
+ try {
1361
+ const evt = JSON.parse(raw);
1362
+ const delta =
1363
+ evt.choices?.[0]?.delta?.content ||
1364
+ evt.delta?.text ||
1365
+ evt.text ||
1366
+ evt.content ||
1367
+ "";
1368
+ if (delta) {
1369
+ fullText += delta;
1370
+ }
1371
+ } catch {
1372
+ fullText += raw;
1373
+ }
1374
+ }
1355
1375
  }
1356
1376
  }
1357
1377
  }
1358
- }
1359
1378
 
1360
- // Handle JSON response
1361
- if (!isSSE && sseBuffer) {
1362
- try {
1363
- const jsonBody = JSON.parse(sseBuffer);
1364
- // Check for error response (BFF returns 400/500 with error details)
1365
- if (proxyRes.statusCode >= 400) {
1366
- const errMsg = jsonBody.error?.message || jsonBody.error || jsonBody.message || JSON.stringify(jsonBody);
1367
- console.log(` ${RED}✗${RESET} BFF error ${proxyRes.statusCode}: ${String(errMsg).substring(0, 300)}`);
1368
- // Send error to Navigator so user sees it
1369
- fullText = `⚠️ Error from AI: ${String(errMsg).substring(0, 200)}`;
1370
- } else {
1371
- fullText = jsonBody.choices?.[0]?.message?.content || jsonBody.response || jsonBody.message || jsonBody.text || "";
1379
+ // Handle JSON response
1380
+ if (!isSSE && sseBuffer) {
1381
+ try {
1382
+ const jsonBody = JSON.parse(sseBuffer);
1383
+ // Check for error response (BFF returns 400/500 with error details)
1384
+ if (proxyRes.statusCode >= 400) {
1385
+ const errMsg =
1386
+ jsonBody.error?.message ||
1387
+ jsonBody.error ||
1388
+ jsonBody.message ||
1389
+ JSON.stringify(jsonBody);
1390
+ console.log(
1391
+ ` ${RED}✗${RESET} BFF error ${proxyRes.statusCode}: ${String(errMsg).substring(0, 300)}`,
1392
+ );
1393
+ // Send error to Navigator so user sees it
1394
+ fullText = `⚠️ Error from AI: ${String(errMsg).substring(0, 200)}`;
1395
+ } else {
1396
+ fullText =
1397
+ jsonBody.choices?.[0]?.message?.content ||
1398
+ jsonBody.response ||
1399
+ jsonBody.message ||
1400
+ jsonBody.text ||
1401
+ "";
1402
+ }
1403
+ } catch {
1404
+ console.log(
1405
+ ` ${DIM}BFF returned non-JSON (${sseBuffer.length} bytes): ${sseBuffer.substring(0, 200)}${RESET}`,
1406
+ );
1372
1407
  }
1373
- } catch {
1374
- console.log(` ${DIM}BFF returned non-JSON (${sseBuffer.length} bytes): ${sseBuffer.substring(0, 200)}${RESET}`);
1375
1408
  }
1376
- }
1377
1409
 
1378
- if (fullText) {
1379
- session.messages.push({ role: "assistant", content: fullText, timestamp: Date.now() });
1380
- broadcastToWS({
1381
- type: "chat.final",
1382
- text: fullText,
1383
- content: fullText,
1384
- sessionKey,
1385
- role: "assistant",
1386
- timestamp: Date.now(),
1387
- });
1388
- console.log(` ${GREEN}✓${RESET} AI response (${fullText.length} chars): ${fullText.substring(0, 80)}...`);
1389
- } else {
1390
- console.log(` ${DIM}BFF returned no content (status ${proxyRes.statusCode})${RESET}`);
1391
- }
1392
- });
1410
+ if (fullText) {
1411
+ session.messages.push({
1412
+ role: "assistant",
1413
+ content: fullText,
1414
+ timestamp: Date.now(),
1415
+ });
1416
+ broadcastToWS({
1417
+ type: "chat.final",
1418
+ text: fullText,
1419
+ content: fullText,
1420
+ sessionKey,
1421
+ role: "assistant",
1422
+ timestamp: Date.now(),
1423
+ });
1424
+ console.log(
1425
+ ` ${GREEN}✓${RESET} AI response (${fullText.length} chars): ${fullText.substring(0, 80)}...`,
1426
+ );
1427
+ } else {
1428
+ console.log(
1429
+ ` ${DIM}BFF returned no content (status ${proxyRes.statusCode})${RESET}`,
1430
+ );
1431
+ }
1432
+ });
1393
1433
 
1394
- proxyRes.on("error", () => {});
1395
- });
1434
+ proxyRes.on("error", () => {});
1435
+ });
1396
1436
 
1397
- proxyReq.on("timeout", () => {
1398
- proxyReq.destroy();
1399
- console.log(` ${DIM}BFF relay timed out${RESET}`);
1400
- });
1437
+ proxyReq.on("timeout", () => {
1438
+ proxyReq.destroy();
1439
+ console.log(` ${DIM}BFF relay timed out${RESET}`);
1440
+ });
1401
1441
 
1402
- proxyReq.on("error", (err) => {
1403
- console.log(` ${DIM}BFF relay failed: ${err.message}${RESET}`);
1404
- });
1442
+ proxyReq.on("error", (err) => {
1443
+ console.log(` ${DIM}BFF relay failed: ${err.message}${RESET}`);
1444
+ });
1405
1445
 
1406
- proxyReq.write(body);
1407
- proxyReq.end();
1446
+ proxyReq.write(body);
1447
+ proxyReq.end();
1408
1448
  } // end sendBFFRelay
1409
1449
 
1410
1450
  sendBFFRelay(proxyOpts, proxyBody);
@@ -1477,12 +1517,21 @@ function handleRequest(req, res) {
1477
1517
  // SSE safety: If the web UI returns text/event-stream but the browser
1478
1518
  // expects JSON (e.g. /api/agents), we collect the SSE events and return
1479
1519
  // them as a JSON array. This prevents "data: ... is not valid JSON" errors.
1520
+ //
1521
+ // Next.js basePath is "/ui", so we prefix fallback paths with /ui/ to match.
1522
+ // Paths already starting with /ui/ won't reach here (handled by /ui/* proxy above).
1480
1523
  {
1481
- const targetURL = `${path}${url.search}`;
1524
+ // Add /ui prefix for Next.js basePath — browser legacy paths like /login, /api/*,
1525
+ // /_next/* need the basePath to match Next.js routes on port 4000.
1526
+ const needsBasePath = path.startsWith("/api/") || path.startsWith("/_next/") ||
1527
+ path === "/login" || path.startsWith("/login?") ||
1528
+ path === "/favicon.ico" || path.startsWith("/static/");
1529
+ const prefixedPath = needsBasePath ? `/ui${path}` : path;
1530
+ const targetURL = `${prefixedPath}${url.search}`;
1482
1531
  const incomingHost = req.headers.host || "localhost";
1483
1532
  // Log API calls for diagnostics (helps debug web UI chat)
1484
1533
  if (path.startsWith("/api/")) {
1485
- console.log(` ${DIM}→ Proxy ${req.method} ${path} → localhost:${ocUIPort}${RESET}`);
1534
+ console.log(` ${DIM}→ Proxy ${req.method} ${path} → localhost:${ocUIPort}${prefixedPath !== path ? ` (→ ${prefixedPath})` : ""}${RESET}`);
1486
1535
  }
1487
1536
 
1488
1537
  // Encourage JSON responses from the web UI's API routes
@@ -1620,35 +1669,48 @@ function handleRequest(req, res) {
1620
1669
  if (isSSE && isStreamingEndpoint) {
1621
1670
  let sseData = "";
1622
1671
  proxyRes.setEncoding("utf-8");
1623
- proxyRes.on("data", (chunk) => { sseData += chunk; });
1672
+ proxyRes.on("data", (chunk) => {
1673
+ sseData += chunk;
1674
+ });
1624
1675
  proxyRes.on("end", () => {
1625
1676
  // Extract text from SSE events
1626
1677
  let fullText = "";
1627
1678
  for (const line of sseData.split("\n")) {
1628
1679
  if (line.startsWith("data: ")) {
1629
1680
  const raw = line.slice(6).trim();
1630
- if (raw === "[DONE]" || !raw) continue;
1681
+ if (raw === "[DONE]" || !raw) {
1682
+ continue;
1683
+ }
1631
1684
  try {
1632
1685
  const evt = JSON.parse(raw);
1633
1686
  const delta =
1634
1687
  evt.choices?.[0]?.delta?.content ||
1635
1688
  evt.delta?.text ||
1636
1689
  evt.text ||
1637
- evt.content || "";
1638
- if (delta) fullText += delta;
1690
+ evt.content ||
1691
+ "";
1692
+ if (delta) {
1693
+ fullText += delta;
1694
+ }
1639
1695
  } catch {
1640
- if (raw) fullText += raw;
1696
+ if (raw) {
1697
+ fullText += raw;
1698
+ }
1641
1699
  }
1642
1700
  }
1643
1701
  // Also handle Vercel AI stream format (0:"text") passthrough
1644
1702
  if (line.startsWith("0:")) {
1645
1703
  try {
1646
1704
  fullText += JSON.parse(line.slice(2));
1647
- } catch { /* skip */ }
1705
+ } catch {
1706
+ /* skip */
1707
+ }
1648
1708
  }
1649
1709
  }
1650
1710
 
1651
- console.log(` ${GREEN}✓${RESET} Chat (${fullText.length} chars): ${fullText.substring(0, 80)}...`);
1711
+ console.log(
1712
+ ` ${GREEN}✓${RESET} Chat (${fullText.length} chars): ${fullText.substring(0, 80)}...`,
1713
+ );
1652
1714
 
1653
1715
  // Return as flat JSON with text in every common field name.
1654
1716
  // The frontend does JSON.parse() and looks for the text at top level.
@@ -1659,7 +1721,13 @@ function handleRequest(req, res) {
1659
1721
  message: fullText,
1660
1722
  answer: fullText,
1661
1723
  reply: fullText,
1662
- choices: [{ index: 0, message: { role: "assistant", content: fullText }, finish_reason: "stop" }],
1724
+ choices: [
1725
+ {
1726
+ index: 0,
1727
+ message: { role: "assistant", content: fullText },
1728
+ finish_reason: "stop",
1729
+ },
1730
+ ],
1663
1731
  });
1664
1732
  headers["content-type"] = "application/json";
1665
1733
  delete headers["content-length"];
@@ -1680,7 +1748,10 @@ function handleRequest(req, res) {
1680
1748
  if (path.startsWith("/api/")) {
1681
1749
  console.log(` ${DIM}✗ Proxy error for ${path}: ${err.message}${RESET}`);
1682
1750
  }
1683
- sendJSON(res, 502, { ok: false, error: `Web UI (port ${ocUIPort}) not reachable: ${err.message}` });
1751
+ sendJSON(res, 502, {
1752
+ ok: false,
1753
+ error: `Web UI (port ${ocUIPort}) not reachable: ${err.message}`,
1754
+ });
1684
1755
  });
1685
1756
 
1686
1757
  req.pipe(proxyReq, { end: true });
@@ -1723,7 +1794,9 @@ async function registerWithRelay(code, url, token, name) {
1723
1794
  signal: c2.signal,
1724
1795
  });
1725
1796
  clearTimeout(t2);
1726
- } catch { /* non-critical */ }
1797
+ } catch {
1798
+ /* non-critical */
1799
+ }
1727
1800
 
1728
1801
  return data.ok === true;
1729
1802
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-navigator",
3
- "version": "5.8.1",
3
+ "version": "5.8.2",
4
4
  "description": "One-command bridge + tunnel for the Navigator browser — works on any machine, any OS",
5
5
  "keywords": [
6
6
  "browser",