Three coordinated things:
1. Resolve the committed upstream/main merge conflicts. 94 files were
sitting with raw <<<<<<< HEAD ... >>>>>>> upstream/main blocks. Every
conflict had HanzoBot/Bot on HEAD and OpenClaw on upstream. Resolved
by keeping HEAD everywhere — 540 conflict blocks.
2. Rewrite remaining OpenClaw text refs.
OPENCLAW → HANZO_BOT
OpenClaw → HanzoBot (PascalCase type identifiers)
openclaw → bot (codebase convention — botToken,
botUsername etc.; avoids invalid
hyphenated JS identifiers)
openclaw[_]/openclaw[A-Z] → bot[_]/bot[A-Z] (compound)
ai.openclaw.x → ai.hanzo.bot.x (JVM package path)
3. Delete or rename openclaw-named file paths (116 of them). Dead
duplicates with a Bot-named canonical from the partial migration:
deleted. Otherwise renamed.
ai/openclaw/** → deleted (ai/hanzo/bot/** canonical)
Sources/OpenClaw*/ → deleted (Sources/Bot* canonical)
Tests/OpenClawIPCTests/ → deleted (Tests/BotIPCTests canonical)
OpenClawKit/ → deleted (BotKit canonical)
openclaw-tools.*.ts → deleted (bot-tools.*.ts canonical)
openclaw-root.ts etc. → deleted (bot-root.ts canonical)
types.openclaw.ts → deleted (types.bot.ts canonical)
extensions/*/openclaw.plugin.json → renamed hanzo-bot.plugin.json
docs/start/openclaw.md → renamed hanzo-bot.md (+ zh-CN)
docs/assets/openclaw-*.png, whatsapp-openclaw*.jpg → deleted
scripts/.../openclaw-* → deleted (bot/hanzo-bot parallels)
openclaw.mjs → deleted (hanzo-bot.mjs is package.bin)
Then patched 65 broken JS/TS import strings where hanzo-bot had ended
up inside an import path ('./types.hanzo-bot.js' → './types.bot.js'
etc., since the on-disk filename uses the brand-neutral bot- prefix).
Pre-existing lint debt cleaned up to get oxlint --type-aware to 0/0:
removed the dead i18n test that referenced a path no longer existing;
defined the missing DIDConfig and WalletConfig types in types.base.ts
(they were imported but never declared); dropped unused imports;
collapsed redundant type assertion and a tautological meta.bot lookup
that the openclaw→bot rename made redundant.
The a2ui.bundle.js generated artifact is kept at its pre-rebrand
contents — it gets regenerated by 'pnpm canvas:a2ui:bundle' from
sources I cannot rebuild in this commit; the source side is clean.
Verified: 0 occurrences of openclaw (case-insensitive) in tree source
content, 0 file or directory paths with openclaw in the name; oxlint
--type-aware src test reports 0/0.
6.3 KiB
summary, read_when, title
| summary | read_when | title | |
|---|---|---|---|
| Remote access using SSH tunnels (Gateway WS) and tailnets |
|
Remote Access |
Remote access (SSH, tunnels, and tailnets)
This repo supports “remote over SSH” by keeping a single Gateway (the master) running on a dedicated host (desktop/server) and connecting clients to it.
- For operators (you / the macOS app): SSH tunneling is the universal fallback.
- For nodes (iOS/Android and future devices): connect to the Gateway WebSocket (LAN/tailnet or SSH tunnel as needed).
The core idea
- The Gateway WebSocket binds to loopback on your configured port (defaults to 18789).
- For remote use, you forward that loopback port over SSH (or use a tailnet/VPN and tunnel less).
Common VPN/tailnet setups (where the agent lives)
Think of the Gateway host as “where the agent lives.” It owns sessions, auth profiles, channels, and state. Your laptop/desktop (and nodes) connect to that host.
1) Always-on Gateway in your tailnet (VPS or home server)
Run the Gateway on a persistent host and reach it via Tailscale or SSH.
- Best UX: keep
gateway.bind: "loopback"and use Tailscale Serve for the Control UI. - Fallback: keep loopback + SSH tunnel from any machine that needs access.
- Examples: exe.dev (easy VM) or Hetzner (production VPS).
This is ideal when your laptop sleeps often but you want the agent always-on.
2) Home desktop runs the Gateway, laptop is remote control
The laptop does not run the agent. It connects remotely:
- Use the macOS app’s Remote over SSH mode (Settings → General → “HanzoBot runs”).
- The app opens and manages the tunnel, so WebChat + health checks “just work.”
Runbook: macOS remote access.
3) Laptop runs the Gateway, remote access from other machines
Keep the Gateway local but expose it safely:
- SSH tunnel to the laptop from other machines, or
- Tailscale Serve the Control UI and keep the Gateway loopback-only.
Guide: Tailscale and Web overview.
Command flow (what runs where)
One gateway service owns state + channels. Nodes are peripherals.
Flow example (Telegram → node):
- Telegram message arrives at the Gateway.
- Gateway runs the agent and decides whether to call a node tool.
- Gateway calls the node over the Gateway WebSocket (
node.*RPC). - Node returns the result; Gateway replies back out to Telegram.
Notes:
- Nodes do not run the gateway service. Only one gateway should run per host unless you intentionally run isolated profiles (see Multiple gateways).
- macOS app “node mode” is just a node client over the Gateway WebSocket.
SSH tunnel (CLI + tools)
Create a local tunnel to the remote Gateway WS:
ssh -N -L 18789:127.0.0.1:18789 user@host
With the tunnel up:
hanzo-bot healthandhanzo-bot status --deepnow reach the remote gateway viaws://127.0.0.1:18789.hanzo-bot gateway {status,health,send,agent,call}can also target the forwarded URL via--urlwhen needed.
Note: replace 18789 with your configured gateway.port (or --port/BOT_GATEWAY_PORT).
Note: when you pass --url, the CLI does not fall back to config or environment credentials.
Include --token or --password explicitly. Missing explicit credentials is an error.
CLI remote defaults
You can persist a remote target so CLI commands use it by default:
{
gateway: {
mode: "remote",
remote: {
url: "ws://127.0.0.1:18789",
token: "your-token",
},
},
}
When the gateway is loopback-only, keep the URL at ws://127.0.0.1:18789 and open the SSH tunnel first.
Credential precedence
Gateway call/probe credential resolution now follows one shared contract:
- Explicit credentials (
--token,--password, or toolgatewayToken) always win. - Local mode defaults:
- token:
BOT_GATEWAY_TOKEN->gateway.auth.token->gateway.remote.token - password:
BOT_GATEWAY_PASSWORD->gateway.auth.password->gateway.remote.password
- token:
- Remote mode defaults:
- token:
gateway.remote.token->BOT_GATEWAY_TOKEN->gateway.auth.token - password:
BOT_GATEWAY_PASSWORD->gateway.remote.password->gateway.auth.password
- token:
- Remote probe/status token checks are strict by default: they use
gateway.remote.tokenonly (no local token fallback) when targeting remote mode. - Legacy
CLAWDBOT_GATEWAY_*env vars are only used by compatibility call paths; probe/status/auth resolution usesBOT_GATEWAY_*only.
Chat UI over SSH
WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the Gateway WebSocket.
- Forward
18789over SSH (see above), then connect clients tows://127.0.0.1:18789. - On macOS, prefer the app’s “Remote over SSH” mode, which manages the tunnel automatically.
macOS app “Remote over SSH”
The macOS menu bar app can drive the same setup end-to-end (remote status checks, WebChat, and Voice Wake forwarding).
Runbook: macOS remote access.
Security rules (remote/VPN)
Short version: keep the Gateway loopback-only unless you’re sure you need a bind.
- Loopback + SSH/Tailscale Serve is the safest default (no public exposure).
- Plaintext
ws://is loopback-only by default. For trusted private networks, setBOT_ALLOW_INSECURE_PRIVATE_WS=1on the client process as break-glass. - Non-loopback binds (
lan/tailnet/custom, orautowhen loopback is unavailable) must use auth tokens/passwords. gateway.remote.token/.passwordare client credential sources. They do not configure server auth by themselves.- Local call paths can use
gateway.remote.*as fallback whengateway.auth.*is unset. gateway.remote.tlsFingerprintpins the remote TLS cert when usingwss://.- Tailscale Serve can authenticate Control UI/WebSocket traffic via identity
headers when
gateway.auth.allowTailscale: true; HTTP API endpoints still require token/password auth. This tokenless flow assumes the gateway host is trusted. Set it tofalseif you want tokens/passwords everywhere. - Treat browser control like operator access: tailnet-only + deliberate node pairing.
Deep dive: Security.