@shaoweiz/agenttown-pairing-server
v0.2.2
Published
AgentTown runtime pairing rendezvous server.
Readme
AgentTown Pairing Server
Small rendezvous and relay server for ClawPilot-style AgentTown pairing.
The preferred flow is relay mode: the runtime registers with this server, receives a short code, then keeps an outbound WebSocket open to the server. The AgentTown App redeems the code and sends runtime RPCs through this server. The user only types AT-XXXX-XXXX-XXXX; the App does not need direct access to 127.0.0.1 on the runtime machine.
The older descriptor rendezvous endpoints are still present for direct-network deployments.
API
GET /healthPOST /api/runtime-pairings- Runtime observer registers
{ type, pairingCode, descriptor }. - If
AGENTTOWN_PAIRING_TOKENis set on the server, this endpoint requiresAuthorization: Bearer <token>.
- Runtime observer registers
POST /api/runtime-pairings/redeem- App sends
{ "pairingCode": "AT-XXXX-XXXX-XXXX" }. - Server returns
{ type, descriptor }once, then removes the code.
- App sends
POST /api/relay/register- Runtime registers
{ type, runtimeType, displayName, machineId, capabilities }. - If
AGENTTOWN_PAIRING_TOKENis set on the server, this endpoint requiresAuthorization: Bearer <token>. - Server returns
{ gatewayId, relaySecret, accessCode, relayUrl }.
- Runtime registers
GET /relay/<gatewayId>?secret=<relaySecret>- Runtime opens this WebSocket and waits for commands.
POST /api/relay/redeem- App sends
{ "accessCode": "AT-XXXX-XXXX-XXXX" }. - Server returns
{ gatewayId, clientToken, relayUrl, connected }.
- App sends
POST /api/relay/rpc- App sends
{ gatewayId, method, params }withAuthorization: Bearer <clientToken>. - Server forwards the command to the connected runtime WebSocket.
- App sends
GET /monitor- Browser monitor page for live server events.
GET /api/monitor/events- Returns recent sanitized monitor events.
GET /api/monitor/stream- Streams sanitized monitor events as Server-Sent Events.
Descriptors are validated before storage. They must be agenttown.runtime_pairing.v1, must not contain plaintext pairingCode, and must not contain token/password/apiKey/cookie/Bearer/SSH/private-key style secrets.
Monitor endpoints require the same bearer token as AGENTTOWN_PAIRING_TOKEN when a registration token is configured. If the server was intentionally started without a token, monitor API access is limited to loopback clients. Monitor output is sanitized: pairing codes, access codes, relay secrets, client tokens, bearer values, and credential-looking fields are masked.
Install On Linux
If published to npm:
sudo npm install -g @shaoweiz/agenttown-pairing-serverIf npm is missing on OpenCloudOS/CentOS/RHEL:
dnf install -y nodejs npmIf the distro Node.js is older than 20:
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash -
dnf install -y nodejsPublished Docker image:
docker pull ghcr.io/zsw12abc/agenttown-pairing-server:0.1.0From this repository:
git clone https://github.com/zsw12abc/AgentTownPlugin.git
cd AgentTownPlugin/agenttown-pairing-server
sudo npm install -g .Create a strong registration token:
PAIRING_TOKEN=$(openssl rand -hex 32)
echo "$PAIRING_TOKEN"Install and start the systemd service:
sudo agenttown-pairing-server install \
--public-url http://<server-public-ip>:8787 \
--port 8787 \
--register-token "$PAIRING_TOKEN"Open TCP 8787 on both the server firewall and the cloud provider security group:
firewall-cmd --permanent --add-port=8787/tcp
firewall-cmd --reloadManual start without systemd:
AGENTTOWN_PAIRING_TOKEN=<PASTE_TOKEN_HERE> \
AGENTTOWN_PAIRING_PUBLIC_URL=https://pair.example.com \
agenttown-pairing-server serve --host 0.0.0.0 --port 8787Put Nginx/Caddy/Cloudflare in front of port 8787 and expose HTTPS at https://pair.example.com.
Check:
agenttown-pairing-server status
curl http://127.0.0.1:8787/health
curl http://<server-public-ip>:8787/healthOpen the live monitor:
http://<server-public-ip>:8787/monitorPaste AGENTTOWN_PAIRING_TOKEN into the page to watch plugin/App traffic in real time.
For production, prefer a domain with HTTPS, for example https://pair.example.com, reverse-proxied to http://127.0.0.1:8787.
Docker
From this repository:
git clone https://github.com/zsw12abc/AgentTownPlugin.git
cd AgentTownPlugin/agenttown-pairing-server
docker build -t shaoweiz/agenttown-pairing-server:0.1.0 .Run with Docker:
docker volume create agenttown-pairing-data
docker run -d \
--name agenttown-pairing-server \
--restart unless-stopped \
-p 8787:8787 \
-e AGENTTOWN_PAIRING_PUBLIC_URL=https://pair.example.com \
-e AGENTTOWN_PAIRING_TOKEN=<PASTE_TOKEN_HERE> \
-v agenttown-pairing-data:/data \
shaoweiz/agenttown-pairing-server:0.1.0Or use Docker Compose:
export AGENTTOWN_PAIRING_TOKEN=<PASTE_TOKEN_HERE>
docker compose up -d --buildCheck the server:
curl https://pair.example.com/health
docker logs agenttown-pairing-serverDocker upgrade from this repository:
cd AgentTownPlugin
git pull --ff-only
cd agenttown-pairing-server
docker compose up -d --buildDocker uninstall:
docker compose down
docker volume rm agenttown-pairing-dataThe volume stores pending short-lived pairings. Removing it clears outstanding pairing codes.
Publish Releases
GitHub Actions can publish both npm and Docker when a version tag is pushed.
Required repository setup:
- Add GitHub secret
NPM_TOKENwith npm publish permission. - Ensure the npm package scope in
package.jsonexists. The default is@shaoweiz/agenttown-pairing-server; change it if you publish under your own npm scope. - Enable GitHub Actions package write permission for this repository if it is not already enabled.
Release:
cd AgentTownPlugin/agenttown-pairing-server
npm version patch --no-git-tag-version
cd ..
git add agenttown-pairing-server/package.json
git commit -m "Release AgentTown observers v0.1.3"
git tag v0.1.3
git push origin main --tagsThe workflow publishes:
- npm:
@shaoweiz/agenttown-pairing-server - npm:
@shaoweiz/hermes-agenttown-observer - npm:
@shaoweiz/openclaw-agenttown-observer - Docker:
ghcr.io/<github-owner>/agenttown-pairing-server:<version> - GitHub Release assets:
hermes-agenttown-observer-<version>.tar.gzopenclaw-agenttown-observer-<version>.tar.gzAPP-ADAPTATION.mdCHANGELOG.md
You can also run the workflow manually from GitHub Actions with a version input, but the version must match agenttown-pairing-server/package.json.
Runtime Pairing With Relay
On the OpenClaw/Hermes runtime machine, set the same token for registration:
export AGENTTOWN_PAIRING_TOKEN=<PASTE_TOKEN_HERE>Install OpenClaw observer from npm:
npm install -g @shaoweiz/openclaw-agenttown-observer
openclaw plugins install @shaoweiz/openclaw-agenttown-observer
openclaw plugins enable agenttown-observer
openclaw gateway restartOpenClaw:
agenttown relay-pair --agenttown-url https://pair.example.com --endpoint http://127.0.0.1:18789 --code-only
agenttown relay --endpoint http://127.0.0.1:18789Install Hermes observer from npm:
npm install -g @shaoweiz/hermes-agenttown-observer
agenttown-hermes-observer install --plugins-dir <Hermes plugins dir>
hermes plugins enable agenttown-observer
hermes gatewayHermes direct descriptor mode is still available until the Hermes observer grows an outbound relay client:
agenttown pair --agenttown-url https://pair.example.com --endpoint <AgentTown能访问到的Hermes地址> --code-onlyThe command prints only:
AT-XXXX-XXXX-XXXXThe AgentTown App should ask the user only for that code. In OpenClaw relay mode, the App should redeem /api/relay/redeem and then call /api/relay/rpc; it should not try to fetch http://127.0.0.1:18789 from the App device.
Direct descriptor endpoint examples:
- OpenClaw same machine or SSH tunnel:
http://127.0.0.1:18789 - Hermes same machine or SSH tunnel:
http://127.0.0.1:8642 - Remote runtime: use a LAN/VPN/Tailscale/SSH tunnel address reachable from the AgentTown App machine.
Do not expose Hermes/OpenClaw gateway directly to the public internet just for pairing.
Verify observer status:
agenttown statusApp Redeem
Relay mode:
curl -s https://pair.example.com/api/relay/redeem \
-H 'Content-Type: application/json' \
-d '{"accessCode":"AT-XXXX-XXXX-XXXX"}'Then call the runtime through the relay:
curl -s https://pair.example.com/api/relay/rpc \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <clientToken>' \
-d '{"gatewayId":"<gatewayId>","method":"agenttown.status","params":{}}'Direct descriptor mode:
curl -s https://pair.example.com/api/runtime-pairings/redeem \
-H 'Content-Type: application/json' \
-d '{"pairingCode":"AT-XXXX-XXXX-XXXX"}'The server returns the sanitized descriptor once. The App should then probe the runtime observer status and confirm pairing.challengeId and pairing.codeHash.
Live Monitor
The pairing server keeps the latest 500 sanitized events in memory and streams new events to the monitor page.
Browser:
https://pair.example.com/monitorHistory API:
curl -s https://pair.example.com/api/monitor/events \
-H "Authorization: Bearer <AGENTTOWN_PAIRING_TOKEN>"Realtime SSE stream:
curl -N https://pair.example.com/api/monitor/stream \
-H "Authorization: Bearer <AGENTTOWN_PAIRING_TOKEN>"Useful event types include:
relay.register.requestrelay.register.createdrelay.websocket.connectedrelay.websocket.messagerelay.redeem.requestrelay.redeem.successrelay.rpc.requestrelay.rpc.command_sentrelay.rpc.successpairing.register.requestpairing.redeem.request
The monitor is for diagnosis. Do not treat it as an audit log; events are in memory and reset when the server restarts.
Upgrade
If installed from npm:
sudo agenttown-pairing-server upgradeupgrade and update are aliases. Both run npm install -g @shaoweiz/agenttown-pairing-server and then restart the agenttown-pairing-server systemd service automatically:
sudo agenttown-pairing-server updateIf installed from this repository before publishing, update the checkout and reinstall:
cd AgentTownPlugin
git pull --ff-only
cd agenttown-pairing-server
sudo npm install -g .
sudo systemctl restart agenttown-pairing-serverUninstall
sudo agenttown-pairing-server uninstall
sudo npm uninstall -g @shaoweiz/agenttown-pairing-serverStatus
agenttown-pairing-server status
curl https://pair.example.com/health
docker logs agenttown-pairing-server