# claw-bot Jetson Global Plugin Demo Runbook

This runbook follows the current claw-bot operating model: durable development
happens on the canonical workstation checkout, and GitHub CI deploys `main`
on Jetson. Demo/runtime execution uses the globally installed npm plugin.
The CI source checkout is the release input, not the live demo entrypoint.

中文：开发在规范工作站 checkout 完成，Jetson 仅由 GitHub CI 部署 `main`；
demo/runtime 使用全局安装后的 claw-bot plugin。CI source checkout 是发布输入，
不是现场 demo 的运行入口。

## Scope

- Jetson is the `realworld` environment and live acceptance host.
- `claw-bot` owns the Agent Harness, MCP tools, voice control plane,
  dashboard, benchmark harness, packaged skill, and acceptance records.
- The robot or Jetson host owns hardware state, sensor configuration,
  controller execution, safety loops, trained skills, and vendor runtimes.
- Durable fixes go into the canonical source checkout and through GitHub CI;
  the runner refreshes the global npm plugin with
  `claw-bot install --yes --package . --json`.
- The dashboard is a long-running HTTP/WebSocket service. `claw-bot-mcp` is a
  stdio MCP server started by Codex from the active profile, not a systemd
  daemon.

中文范围：claw-bot 只负责让 Agent 看见能力、调用感知和行动、记录反馈并继续规划。
底层传感器、控制器、策略网络和模拟器/机器人 runtime 不属于本仓库。

## Local Environment Contract

Use these optional variables when validating local Jetson services:

```bash
export CLAW_BOT_SERVICE=claw-bot.service
export CLAW_BOT_SYSTEMCTL_SCOPE=user
export CLAW_BOT_DASHBOARD_SERVICE=claw-bot-dashboard.service
export CLAW_BOT_DASHBOARD_PORT=8787
export CLAW_BOT_DASHBOARD_TOKEN=<token>
export CLAW_BOT_DASHBOARD_PUBLIC_HOST=jetson.local
```

Supported values:

- `CLAW_BOT_SERVICE`: optional systemd service to validate for voice or
  operator control plane runs.
- `CLAW_BOT_SYSTEMCTL_SCOPE`: `user` for `systemctl --user`, or `system` for
  `sudo systemctl`.
- `CLAW_BOT_DASHBOARD_SERVICE`: dashboard systemd service name, default
  `claw-bot-dashboard.service`.
- `CLAW_BOT_DASHBOARD_TOKEN`: required by dashboard validation and LAN access.
- `CLAW_BOT_REQUIRE_AUDIO_INPUT=1`: fail validation if no physical
  non-monitor microphone reaches signal thresholds.

## Agent One-Line Install

For a published npm package, install and configure the Jetson host in one
command:

```bash
npm exec --yes --package @bozhi-ai/claw-bot@latest -- claw-bot install --yes --json
```

For GitHub distribution, use the repository as both the temporary installer
package and the global package spec:

```bash
CLAW_BOT_INSTALL_PACKAGE_SPEC=github:bozhi-ai/claw-bot \
npm exec --yes --package github:bozhi-ai/claw-bot -- claw-bot install --yes --json
```

The installer globally installs or refreshes `claw-bot`, writes
`~/.config/claw-bot/*`, writes the Codex profile, writes the systemd user
service, and starts the dashboard service. It does not configure robot firmware,
sensor drivers, trained skills, or vendor runtimes.

The npm package is the canonical distribution artifact. It includes
`server.json` and `package.json#mcpName` for MCP Registry discovery. Registry
clients that only need the stdio MCP server can launch `npx -y
@bozhi-ai/claw-bot@latest mcp`; full Jetson/local acceptance should use
`claw-bot install --yes --json` so the dashboard service and profile are
written.

中文：Agent 安装入口是一行命令。安装器负责全局安装或刷新 `claw-bot`，写入配置、
Codex profile、systemd user service，并启动 dashboard。底层机器人 runtime、
传感器驱动和技能训练不属于安装器。

## Maintainer Source Refresh

The following refresh runs on the Jetson CI runner against the checked GitHub
commit. Do not install a workstation working tree over SSH. Use the actual
runner checkout; `~/Projects/claw-bot` is not a required path:

```bash
cd "$GITHUB_WORKSPACE"
npm ci
npm test
npm run pack:check
claw-bot install --yes --package . --json
npm run benchmark:robot -- --runner mcp --case all --iterations 3 --json
bash deploy/jetson/accept-fast-slow-load.sh
```

Focused checks are useful while iterating:

```bash
npm run test:robot
npm run test:mcp
npm run test:skill
npm run test:voice
```

中文：`npm test` 和 `npm run pack:check` 是维护者合并前门槛。benchmark 用来验证
Agent Harness 的 MCP 决策接口，不代表真机可见动作已经验收通过。

## GitHub CI Deployment

Sol is the development workstation and GitHub is the source of truth for
durable Jetson deployments. Pushes to `main` run
`.github/workflows/jetson-ci-deploy.yml` on the `jetson-robot-use`
self-hosted runner.

The runner installs the checked GitHub commit on Jetson with:

```bash
bash deploy/jetson/deploy.sh
```

That script runs the standard checks, then refreshes the globally installed
plugin with:

```bash
claw-bot install --yes --package . --json
```

Do not SSH/rsync an uncommitted Sol working tree to Jetson as a deployment
candidate. For durable fixes, patch on Sol, commit, push to `main`, let CI
refresh the global plugin on Jetson, then run the readiness and live acceptance
checks below.

## Dashboard Service

For foreground debugging:

```bash
export CLAW_BOT_DASHBOARD_HOST=0.0.0.0
export CLAW_BOT_DASHBOARD_PORT=8787
export CLAW_BOT_DASHBOARD_TOKEN=<token>
claw-bot-dashboard serve
```

For a persistent user service:

```bash
claw-bot install --yes --package . --json
```

Validate locally:

```bash
bash deploy/jetson/validate-dashboard.sh
```

Then open:

```text
http://jetson.local:8787/
```

Enter the configured Dashboard access token in the page's password field. The
page exchanges it for a short-lived HttpOnly session cookie and does not put it
in browser history, API URLs, WebSocket URLs, or local storage. Existing
`?token=` links remain supported temporarily; do not create or share new ones.

If LAN name resolution is unstable, set `CLAW_BOT_DASHBOARD_PUBLIC_HOST` to the
reachable Jetson hostname or IP before running validation.

## Jetson Readiness

Run the local Jetson validation script:

```bash
bash deploy/jetson/validate.sh
```

For final microphone acceptance, require a physical input:

```bash
CLAW_BOT_REQUIRE_AUDIO_INPUT=1 bash deploy/jetson/validate.sh
CLAW_BOT_SWEETWATER_INSTALLATION=/absolute/path/installation.json \
CLAW_BOT_SWEETWATER_ROLE=<role-key> \
bash deploy/jetson/accept-realtime-voice.sh
```

The validation script checks local service status when configured, local
`claw-bot voice status`, OpenAI Realtime bridge readiness, microphone signal,
gate status, and the Jetson TUI snapshot. It does not SSH to another host.
The final Realtime acceptance uses an isolated runtime/state directory and the
installed global binaries. It requires the selected Sweetwater role to use
`provider=codex`, `model=gpt-6.1-sol`, `effort=medium`, then proves two distinct
Realtime sessions each reach terminal successful G1 action feedback plus fresh
post-action perception. A third spoken command without the `来福` wake word must
record `wake_word_missing` without increasing the dispatch count. Failure
artifacts are preserved; a successful temporary run cleans its scratch files.

## Refresh Codex Session

Codex does not hot-reload a plugin update into an existing decision session.
After the one-line installer or maintainer source refresh, restart or create
the Codex app-server session that will receive dashboard voice transcripts.
That session must load the `claw-bot` profile, which starts global
`claw-bot-mcp` as its stdio MCP server.

Record the current app-server URL and thread id in the dashboard service env:

```bash
cat >> ~/.config/claw-bot/dashboard.env <<'EOF'
CLAW_BOT_CODEX_APP_SERVER_URL=ws://127.0.0.1:4500
CLAW_BOT_CODEX_THREAD_ID=<current-claw-bot-thread-id>
EOF
systemctl --user restart claw-bot-dashboard.service
```

Then verify the installed control plane:

```bash
claw-bot doctor --json
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}' | claw-bot-mcp
```

中文：dashboard 麦克风入口会把 transcript 发到指定 Codex session。该 session 必须
是在刷新全局 plugin 之后新建或重启的 session，否则可能继续使用旧 skill、旧 MCP
server 或旧 profile。

## Realtime Voice

The OpenAI Realtime bridge is the default operator audio perception path. It
loads its API key from the process environment first, then from the claw-bot
env file `~/.config/claw-bot/openai.env`, then from the
Codex-scoped `~/.codex/openai.env` unless
`CLAW_BOT_OPENAI_ENV_FILE` points elsewhere. Keep normal Jetson Codex login on
ChatGPT auth; the API key should only be read by the Realtime bridge process.

Readiness and smoke checks:

```bash
claw-bot-openai-realtime-bridge check --json
claw-bot-openai-realtime-bridge voice2-smoke --text "Say forward acknowledged." --json
claw-bot-openai-realtime-bridge voice2-smoke --capture-seconds 5 --audio-device auto --json
```

The direct microphone smoke must return `ok: true`, a real `inputTranscript`,
and `inputAudio.rms >= CLAW_BOT_OPENAI_VOICE2_SMOKE_MIN_RMS`. If Realtime
returns a transcript but capture RMS stays near zero, treat that as a
hardware/input-source blocker, not a microphone pass.

Run the read-only source diagnostic when microphone input is uncertain:

```bash
node scripts/diagnose-jetson-audio-input.js --seconds 2 --json
```

No source should be accepted for final voice validation unless the diagnostic
marks at least one non-monitor input `ok: true`. Use the reported
`recommendedAudioDevice` for `CLAW_BOT_AUDIO_DEVICE` or `voice2-smoke
--audio-device`.

Start the mic-gated realtime path:

```bash
export CLAW_BOT_CODEX_APP_SERVER_URL=ws://127.0.0.1:4500
export CLAW_BOT_CODEX_THREAD_ID=<existing-claw-bot-thread-id>
export CLAW_BOT_AUDIO_DEVICE=auto
export CLAW_BOT_OPENAI_GATE_ROUTE=codex
export CLAW_BOT_WAKE_WORDS=来福
claw-bot-openai-realtime-bridge gate --environment realworld --controller auto-low-speed
```

`CLAW_BOT_OPENAI_GATE_ROUTE=codex` is the default production route: local mic
gating opens OpenAI Realtime only to obtain the final transcript, then
claw-bot sends text into the existing Codex App Server thread. Set
`CLAW_BOT_OPENAI_GATE_ROUTE=hmi` only when intentionally testing the older
Realtime HMI direct tool-call path.

Wake-word filtering is enforced before Codex receives a robot intent. The
default wake word is `来福`; override it with comma-separated
`CLAW_BOT_WAKE_WORDS`, preserve the wake word in the Codex prompt with
`CLAW_BOT_WAKE_WORD_STRIP=0`, or disable this safety gate for controlled tests
with `CLAW_BOT_WAKE_WORD_REQUIRED=0`.

中文：唤醒词是 operator intent 的安全门。没有通过唤醒词的语音可以被记录为反馈，
但不应进入行动派发。

## Bluetooth Audio

Bluetooth audio is available as an alternate physical microphone path after
running:

```bash
bash deploy/jetson/enable-bluetooth-audio.sh
bash deploy/jetson/find-bluetooth-microphones.sh
bash deploy/jetson/pair-bluetooth-microphone.sh <MAC>
```

After connecting a headset, `pactl list short sources` should show a non-monitor
`bluez` source, and the diagnostic should report a non-null
`recommendedAudioDevice`. Roll back the systemd override with:

```bash
bash deploy/jetson/disable-bluetooth-audio.sh
```

Use `bash deploy/jetson/wait-for-audio-input.sh` to loop until the diagnostic
reports a passing source and prints the exact `voice2-smoke` command.

## Live Acceptance

Use the dashboard as the product-facing observation plane. The Realtime panel
shows route, wake-word configuration, transcript, Codex plan or fallback
timeline, dispatch, controller feedback, and recent debug events. For a local
terminal fallback:

```bash
claw-bot-jetson-tui --openai-gate --environment realworld --controller auto-low-speed
```

Minimum final acceptance:

- Service active, OpenAI bridge check passes, dashboard shows key presence
  without printing it.
- Muted path does not open a Realtime connection.
- Unmuted speech opens Realtime, records a final transcript, shows wake-word
  acceptance, Codex plan or fallback, and a low-speed safe dispatch.
- Muting again closes Realtime within the silence window and returns the voice
  session to idle.
- A second voice command can be accepted after the mute/stop cycle.
- The robot's visible behavior matches the intended command, or the failure is
  recorded with enough evidence to patch the source checkout and refresh the
  global plugin again.

Recommended spoken test:

```text
来福，机器人向前走一步，然后停下
```

Also test the same command without `来福`; the dashboard should show feedback
with `reason=wake_word_missing` and no dispatch.

Do not report completion from logs, service health, or local tests alone.
Completion requires spoken command, transcript, agent plan, action payload,
controller feedback, and visible robot result.

中文：真机语音验收必须看见结果。日志、HTTP 200、测试通过都只是辅助证据。

## Failure Recovery

- Local tests fail: fix the source checkout before refreshing the global plugin.
- Service status fails: inspect `systemctl status` and recent `journalctl`, then
  patch source or service config, refresh the global plugin, and restart the
  service.
- Voice session fails to start: capture
  `claw-bot-openai-realtime-bridge gate-status --json` and
  `claw-bot voice status --json`.
- Realtime bridge fails readiness: run
  `claw-bot-openai-realtime-bridge check --json` and fix missing
  API key/env file, Node version, network, or audio device settings.
- Visual behavior is wrong: record the transcript, dispatched command,
  feedback, and observed motion; patch source, refresh the global plugin, and
  restart the demo session.

中文恢复原则：失败时先停安全路径，保留证据，再修源代码。不要把一次性运行态改动
当作长期修复。

## Acceptance Record Template

```text
Date:
Commit/tree:
Source checkout:
Service:

Local prechecks:
- npm exec --yes --package @bozhi-ai/claw-bot@latest -- claw-bot install --yes --json:
- CLAW_BOT_INSTALL_PACKAGE_SPEC=github:bozhi-ai/claw-bot npm exec --yes --package github:bozhi-ai/claw-bot -- claw-bot install --yes --json:

Maintainer source refresh, if testing local changes:
- npm test:
- npm run pack:check:
- claw-bot install --yes --package . --json:
- npm run benchmark:robot -- --runner mcp --case all --iterations 3 --json:

Codex plugin refresh:
- plugin refresh confirmed:
- Codex app-server URL:
- Codex thread id:
- claw-bot doctor --json:
- claw-bot-mcp initialize:

Jetson validation:
- bash deploy/jetson/validate.sh:
- bash deploy/jetson/validate-dashboard.sh:
- bash deploy/jetson/accept-fast-slow-load.sh:
- Fast/Slow throughput + p95/p99 latency:
- peak RSS/system memory/load:
- thermal sensor maxima + critical-trip headroom:
- bash deploy/jetson/accept-realtime-voice.sh:

Realtime voice:
- start/status output:
- spoken command:
- transcript:
- agent plan:
- dispatch/action payload:
- controller feedback:
- visible robot result:
- interrupt/stop result:
- final status:

Decision:
- pass/fail:
- remaining non-blockers:
```

中文验收记录至少包含：日期、commit/tree、验证命令、语音原句、transcript、
agent plan、action payload、controller feedback、可见机器人结果和最终判定。

## Jetson DNS/时钟固化

本节限定为 Jetson 主机 DNS、NTP 和 runner 网络恢复，不涉及语音、唤醒词或机器人
动作验收，也不改变 plugin 的 GitHub-CI-only 发布路径。

### 2026-10-14 现场基线与配置

- 主机 `jetson`，NetworkManager 1.36.6、systemd 249、Tailscale 1.102.2。
- `Bozhi` 是自动连接的 Wi-Fi profile，UUID
  `19dfc638-d7ad-4260-bc51-63d70e2a3379`，设备 `wlP1p1s0`；IPv4 为静态配置，
  IPv6 已禁用。这次只修改它的 IPv4 DNS 和 `ignore-auto-dns`。
- 修改前 `/etc/resolv.conf` 是 10-13 留下的普通手写文件，DNS 为
  `192.168.110.1 / 223.5.5.5`；profile 的 DNS 却为 `8.8.8.8 / 1.1.1.1`。
  NetworkManager 原来使用 `dns=systemd-resolved`，resolved 还存在透明 TUN 的
  `198.18.0.2` DNS。不能直接链接 resolved 的 uplink resolv.conf，否则会带入该地址。
- timesyncd 原来已 `enabled/active` 且同步成功，NTP 源来自 NVIDIA fallback
  drop-in。Tailscale `CorpDNS=false`（即 `accept-dns=false`），此次保持不变。
- GitHub runner `jetson-robot-use` 修改前在线且空闲。

修改前在 Jetson 建立 root-only 备份目录（0700）：

```text
/var/backups/claw-bot/jetson-dns-clock-20261014T171611+0800.bak/
  Bozhi.nmconnection.bak
  NetworkManager.conf.bak
  resolv.conf.bak
  timesyncd.conf.bak
  nv-fallback-ntp.conf.bak
  original-state.txt
```

`Bozhi.nmconnection.bak` 保留 0600，包含原 Wi-Fi 配置，只用于原机恢复，不上传或
打印。两个新增 drop-in 原来不存在，已在 `original-state.txt` 记录。

持久 profile 设置：

```bash
sudo nmcli connection modify uuid 19dfc638-d7ad-4260-bc51-63d70e2a3379 \
  ipv4.dns '192.168.110.1,223.5.5.5' ipv4.ignore-auto-dns yes
```

新增 `/etc/NetworkManager/conf.d/90-claw-bot-dns.conf`（0644）：

```ini
[main]
dns=default
rc-manager=file
```

由 NetworkManager 根据持久连接生成普通 `/etc/resolv.conf`，避免手写文件与
profile 分离，也避免引用包含额外 TUN DNS 的 resolved uplink 文件。保留现有
透明 TUN 路由和 Tailscale 连接。配置机制见
[NetworkManager 官方说明](https://networkmanager.dev/docs/api/latest/NetworkManager.conf.html)。
该 DNS 管理模式影响 NetworkManager 管理的连接；未来增加其它 uplink/VPN 时，应
再次核对其 DNS 与最终 resolv.conf，不应推断任意新连接也满足本节验收。

新增 `/etc/systemd/timesyncd.conf.d/zz-claw-bot-ntp.conf`（0644）：

```ini
[Time]
NTP=0.pool.ntp.org 1.pool.ntp.org
FallbackNTP=ntp.ubuntu.com
```

原 NVIDIA drop-in 保留；fallback 列表会包含它的三个 pool 源和新增 Ubuntu 源。
已显式启用 timesyncd，并重启它验证新配置确实收到 NTP 响应。恢复/应用命令：

```bash
sudo systemctl enable systemd-timesyncd
sudo systemctl restart systemd-timesyncd
sudo nmcli general reload
sudo nmcli device reapply wlP1p1s0
sudo systemctl restart --no-block NetworkManager
```

NetworkManager 重启可能暂时中断 SSH；应重新连接后读回，不能以 SSH 命令返回
代替重启后验证。此次没有重启整机，没有更改 IP、网关、Wi-Fi 凭据或 TUN 路由。

### 验证与现场结果

在 Jetson 执行（不打印完整 Tailscale prefs 或任何凭据）：

```bash
systemctl show NetworkManager -p ActiveState -p ActiveEnterTimestamp
cat /etc/resolv.conf
nmcli -f connection.autoconnect,ipv4.dns,ipv4.ignore-auto-dns \
  connection show uuid 19dfc638-d7ad-4260-bc51-63d70e2a3379
nmcli -f GENERAL.CONNECTION,IP4.DNS device show wlP1p1s0
getent ahostsv4 github.com
timedatectl status
timedatectl timesync-status
timedatectl show-timesync -p SystemNTPServers -p FallbackNTPServers
systemctl is-enabled systemd-timesyncd
tailscale debug prefs | python3 -c \
  'import sys,json; p=json.load(sys.stdin); print({"accept_dns":p.get("CorpDNS")})'
```

在维护工作站核对 runner（不是仅看主机进程）：

```bash
gh api repos/bozhi-ai/claw-bot/actions/runners \
  --jq '.runners[] | {name,status,busy}'
```

2026-10-14 17:16:49 上海时间读回：NetworkManager 在 17:16:30 重新进入 active；
resolv.conf 显示 `# Generated by NetworkManager`，只有两个 nameserver
`192.168.110.1 / 223.5.5.5`；profile 与设备 DNS 一致，自动连接和忽略自动 DNS 均为
yes；`github.com` 解析为 `20.205.243.166`。timesyncd 为 enabled，系统同步 yes，
新 NTP 会话的服务器为 `111.230.189.174 (0.pool.ntp.org)`，Packet count=2，
offset=+18.258ms。Tailscale accept_dns=false，runner online/busy=false。
这些地址和偏移量是当时观测值，不是未来必须相同的固定值。

NetworkManager 重启后的 Jetson 门禁重跑：

```bash
gh run rerun 38339454678 --repo bozhi-ai/claw-bot
gh run view 38339454678 --repo bozhi-ai/claw-bot \
  --json databaseId,attempt,headSha,status,conclusion,jobs,url
npm test
npm run pack:check -- --cache "$HOME/Library/Caches/claw-bot/npm"
git diff --check
```

[CI run 38339454678 / attempt 2](https://github.com/bozhi-ai/claw-bot/actions/runs/38339454678/attempts/2)
已 completed/success，运行候选为 main
`096976803fa1fc7779a0ba7ac6d1f23745864a13`。候选新鲜度、测试、包检查、CI 安装、
安装提交一致性、runtime、dashboard、MCP benchmark、Fast/Slow 负载验收和证据
上传均 success，没有跳过部署。这里证明 DNS/校时维护后的既有 main 门禁；文档
PR 本身不要求部署，也未合并。工作站 `npm test` 为 309/309 通过。

### 回滚

在 Jetson 执行；保留所有备份和新增配置，不删除文件。将新增 drop-in 移出其加载
目录，再恢复原 profile。NetworkManager 重启后如 SSH 断开，先重新连接再执行
最后的 resolv.conf 恢复与验证。

```bash
backup_dir=/var/backups/claw-bot/jetson-dns-clock-20261014T171611+0800.bak
rollback_stamp=$(date +%Y%m%dT%H%M%S%z)
sudo mv /etc/NetworkManager/conf.d/90-claw-bot-dns.conf \
  "$backup_dir/90-claw-bot-dns.conf.rolled-back.$rollback_stamp"
sudo mv /etc/systemd/timesyncd.conf.d/zz-claw-bot-ntp.conf \
  "$backup_dir/zz-claw-bot-ntp.conf.rolled-back.$rollback_stamp"
sudo cp -a "$backup_dir/Bozhi.nmconnection.bak" \
  /etc/NetworkManager/system-connections/Bozhi.nmconnection
sudo nmcli connection reload
sudo nmcli general reload
sudo nmcli device reapply wlP1p1s0
sudo systemctl restart systemd-timesyncd
sudo systemctl restart --no-block NetworkManager
```

确认 NetworkManager 已重新 active 后：

```bash
backup_dir=/var/backups/claw-bot/jetson-dns-clock-20261014T171611+0800.bak
sudo cp -a "$backup_dir/resolv.conf.bak" /etc/resolv.conf
cat /etc/resolv.conf
getent ahostsv4 github.com
timedatectl status
timedatectl timesync-status
```

主配置和 NVIDIA drop-in 此次未改，备份用于核查，无需覆盖后来可能出现的合法
改动。timesyncd 原来已启用，回滚也保持启用；Tailscale 原来已 accept-dns=false，
无需恢复为 true。此回滚会恢复原先 profile/resolv.conf 不一致的现场，仅用于撤销
本次改动，不能把它作为长期 DNS 方案。
