revert: Switch back to tsc for compiling.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Optional Docker-based setup and onboarding for OpenClaw"
|
||||
summary: 'Optional Docker-based setup and onboarding for OpenClaw'
|
||||
read_when:
|
||||
- You want a containerized gateway instead of local installs
|
||||
- You are validating the Docker flow
|
||||
@@ -16,6 +16,7 @@ Docker is **optional**. Use it only if you want a containerized gateway or to va
|
||||
- **Sandboxing note**: agent sandboxing uses Docker too, but it does **not** require the full gateway to run in Docker. See [Sandboxing](/gateway/sandboxing).
|
||||
|
||||
This guide covers:
|
||||
|
||||
- Containerized Gateway (full OpenClaw in Docker)
|
||||
- Per-session Agent Sandbox (host gateway + Docker-isolated agent tools)
|
||||
|
||||
@@ -37,6 +38,7 @@ From repo root:
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- builds the gateway image
|
||||
- runs the onboarding wizard
|
||||
- prints optional provider setup hints
|
||||
@@ -44,15 +46,18 @@ This script:
|
||||
- generates a gateway token and writes it to `.env`
|
||||
|
||||
Optional env vars:
|
||||
|
||||
- `OPENCLAW_DOCKER_APT_PACKAGES` — install extra apt packages during build
|
||||
- `OPENCLAW_EXTRA_MOUNTS` — add extra host bind mounts
|
||||
- `OPENCLAW_HOME_VOLUME` — persist `/home/node` in a named volume
|
||||
|
||||
After it finishes:
|
||||
|
||||
- Open `http://127.0.0.1:18789/` in your browser.
|
||||
- Paste the token into the Control UI (Settings → token).
|
||||
|
||||
It writes config/workspace on the host:
|
||||
|
||||
- `~/.openclaw/`
|
||||
- `~/.openclaw/workspace`
|
||||
|
||||
@@ -81,6 +86,7 @@ export OPENCLAW_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/ho
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Paths must be shared with Docker Desktop on macOS/Windows.
|
||||
- If you edit `OPENCLAW_EXTRA_MOUNTS`, rerun `docker-setup.sh` to regenerate the
|
||||
extra compose file.
|
||||
@@ -110,6 +116,7 @@ export OPENCLAW_EXTRA_MOUNTS="$HOME/.codex:/home/node/.codex:ro,$HOME/github:/ho
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- If you change `OPENCLAW_HOME_VOLUME`, rerun `docker-setup.sh` to regenerate the
|
||||
extra compose file.
|
||||
- The named volume persists until removed with `docker volume rm <name>`.
|
||||
@@ -129,6 +136,7 @@ export OPENCLAW_DOCKER_APT_PACKAGES="ffmpeg build-essential"
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This accepts a space-separated list of apt package names.
|
||||
- If you change `OPENCLAW_DOCKER_APT_PACKAGES`, rerun `docker-setup.sh` to rebuild
|
||||
the image.
|
||||
@@ -163,7 +171,7 @@ RUN pnpm ui:build
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node","dist/index.mjs"]
|
||||
CMD ["node","dist/index.js"]
|
||||
```
|
||||
|
||||
### Channel setup (optional)
|
||||
@@ -171,16 +179,19 @@ CMD ["node","dist/index.mjs"]
|
||||
Use the CLI container to configure channels, then restart the gateway if needed.
|
||||
|
||||
WhatsApp (QR):
|
||||
|
||||
```bash
|
||||
docker compose run --rm openclaw-cli channels login
|
||||
```
|
||||
|
||||
Telegram (bot token):
|
||||
|
||||
```bash
|
||||
docker compose run --rm openclaw-cli channels add --channel telegram --token "<token>"
|
||||
```
|
||||
|
||||
Discord (bot token):
|
||||
|
||||
```bash
|
||||
docker compose run --rm openclaw-cli channels add --channel discord --token "<token>"
|
||||
```
|
||||
@@ -190,7 +201,7 @@ Docs: [WhatsApp](/channels/whatsapp), [Telegram](/channels/telegram), [Discord](
|
||||
### Health check
|
||||
|
||||
```bash
|
||||
docker compose exec openclaw-gateway node dist/index.mjs health --token "$OPENCLAW_GATEWAY_TOKEN"
|
||||
docker compose exec openclaw-gateway node dist/index.js health --token "$OPENCLAW_GATEWAY_TOKEN"
|
||||
```
|
||||
|
||||
### E2E smoke test (Docker)
|
||||
@@ -218,6 +229,7 @@ Deep dive: [Sandboxing](/gateway/sandboxing)
|
||||
|
||||
When `agents.defaults.sandbox` is enabled, **non-main sessions** run tools inside a Docker
|
||||
container. The gateway stays on your host, but the tool execution is isolated:
|
||||
|
||||
- scope: `"agent"` by default (one container + workspace per agent)
|
||||
- scope: `"session"` for per-session isolation
|
||||
- per-scope workspace folder mounted at `/workspace`
|
||||
@@ -233,6 +245,7 @@ one container and one workspace.
|
||||
If you use multi-agent routing, each agent can override sandbox + tool settings:
|
||||
`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools`). This lets you run
|
||||
mixed access levels in one gateway:
|
||||
|
||||
- Full access (personal agent)
|
||||
- Read-only tools + read-only workspace (family/work agent)
|
||||
- No filesystem/shell tools (public agent)
|
||||
@@ -255,60 +268,72 @@ precedence, and troubleshooting.
|
||||
### Enable sandboxing
|
||||
|
||||
If you plan to install packages in `setupCommand`, note:
|
||||
|
||||
- Default `docker.network` is `"none"` (no egress).
|
||||
- `readOnlyRoot: true` blocks package installs.
|
||||
- `user` must be root for `apt-get` (omit `user` or set `user: "0:0"`).
|
||||
OpenClaw auto-recreates containers when `setupCommand` (or docker config) changes
|
||||
unless the container was **recently used** (within ~5 minutes). Hot containers
|
||||
log a warning with the exact `openclaw sandbox recreate ...` command.
|
||||
OpenClaw auto-recreates containers when `setupCommand` (or docker config) changes
|
||||
unless the container was **recently used** (within ~5 minutes). Hot containers
|
||||
log a warning with the exact `openclaw sandbox recreate ...` command.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
mode: "non-main", // off | non-main | all
|
||||
scope: "agent", // session | agent | shared (agent is default)
|
||||
workspaceAccess: "none", // none | ro | rw
|
||||
workspaceRoot: "~/.openclaw/sandboxes",
|
||||
mode: 'non-main', // off | non-main | all
|
||||
scope: 'agent', // session | agent | shared (agent is default)
|
||||
workspaceAccess: 'none', // none | ro | rw
|
||||
workspaceRoot: '~/.openclaw/sandboxes',
|
||||
docker: {
|
||||
image: "openclaw-sandbox:bookworm-slim",
|
||||
workdir: "/workspace",
|
||||
image: 'openclaw-sandbox:bookworm-slim',
|
||||
workdir: '/workspace',
|
||||
readOnlyRoot: true,
|
||||
tmpfs: ["/tmp", "/var/tmp", "/run"],
|
||||
network: "none",
|
||||
user: "1000:1000",
|
||||
capDrop: ["ALL"],
|
||||
env: { LANG: "C.UTF-8" },
|
||||
setupCommand: "apt-get update && apt-get install -y git curl jq",
|
||||
tmpfs: ['/tmp', '/var/tmp', '/run'],
|
||||
network: 'none',
|
||||
user: '1000:1000',
|
||||
capDrop: ['ALL'],
|
||||
env: { LANG: 'C.UTF-8' },
|
||||
setupCommand: 'apt-get update && apt-get install -y git curl jq',
|
||||
pidsLimit: 256,
|
||||
memory: "1g",
|
||||
memorySwap: "2g",
|
||||
memory: '1g',
|
||||
memorySwap: '2g',
|
||||
cpus: 1,
|
||||
ulimits: {
|
||||
nofile: { soft: 1024, hard: 2048 },
|
||||
nproc: 256
|
||||
nproc: 256,
|
||||
},
|
||||
seccompProfile: "/path/to/seccomp.json",
|
||||
apparmorProfile: "openclaw-sandbox",
|
||||
dns: ["1.1.1.1", "8.8.8.8"],
|
||||
extraHosts: ["internal.service:10.0.0.5"]
|
||||
seccompProfile: '/path/to/seccomp.json',
|
||||
apparmorProfile: 'openclaw-sandbox',
|
||||
dns: ['1.1.1.1', '8.8.8.8'],
|
||||
extraHosts: ['internal.service:10.0.0.5'],
|
||||
},
|
||||
prune: {
|
||||
idleHours: 24, // 0 disables idle pruning
|
||||
maxAgeDays: 7 // 0 disables max-age pruning
|
||||
}
|
||||
}
|
||||
}
|
||||
maxAgeDays: 7, // 0 disables max-age pruning
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
sandbox: {
|
||||
tools: {
|
||||
allow: ["exec", "process", "read", "write", "edit", "sessions_list", "sessions_history", "sessions_send", "sessions_spawn", "session_status"],
|
||||
deny: ["browser", "canvas", "nodes", "cron", "discord", "gateway"]
|
||||
}
|
||||
}
|
||||
}
|
||||
allow: [
|
||||
'exec',
|
||||
'process',
|
||||
'read',
|
||||
'write',
|
||||
'edit',
|
||||
'sessions_list',
|
||||
'sessions_history',
|
||||
'sessions_send',
|
||||
'sessions_spawn',
|
||||
'session_status',
|
||||
],
|
||||
deny: ['browser', 'canvas', 'nodes', 'cron', 'discord', 'gateway'],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -328,6 +353,7 @@ scripts/sandbox-setup.sh
|
||||
This builds `openclaw-sandbox:bookworm-slim` using `Dockerfile.sandbox`.
|
||||
|
||||
### Sandbox common image (optional)
|
||||
|
||||
If you want a sandbox image with common build tooling (Node, Go, Rust, etc.), build the common image:
|
||||
|
||||
```bash
|
||||
@@ -338,7 +364,11 @@ This builds `openclaw-sandbox-common:bookworm-slim`. To use it:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: { defaults: { sandbox: { docker: { image: "openclaw-sandbox-common:bookworm-slim" } } } }
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: { docker: { image: 'openclaw-sandbox-common:bookworm-slim' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -355,6 +385,7 @@ This builds `openclaw-sandbox-browser:bookworm-slim` using
|
||||
an optional noVNC observer (headful via Xvfb).
|
||||
|
||||
Notes:
|
||||
|
||||
- Headful (Xvfb) reduces bot blocking vs headless.
|
||||
- Headless can still be used by setting `agents.defaults.sandbox.browser.headless=true`.
|
||||
- No full desktop environment (GNOME) is needed; Xvfb provides the display.
|
||||
@@ -366,10 +397,10 @@ Use config:
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: {
|
||||
browser: { enabled: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
browser: { enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -379,13 +410,14 @@ Custom browser image:
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: { browser: { image: "my-openclaw-browser" } }
|
||||
}
|
||||
}
|
||||
sandbox: { browser: { image: 'my-openclaw-browser' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
When enabled, the agent receives:
|
||||
|
||||
- a sandbox browser control URL (for the `browser` tool)
|
||||
- a noVNC URL (if enabled and headless=false)
|
||||
|
||||
@@ -405,9 +437,9 @@ docker build -t my-openclaw-sbx -f Dockerfile.sandbox .
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
sandbox: { docker: { image: "my-openclaw-sbx" } }
|
||||
}
|
||||
}
|
||||
sandbox: { docker: { image: 'my-openclaw-sbx' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -420,10 +452,12 @@ docker build -t my-openclaw-sbx -f Dockerfile.sandbox .
|
||||
### Pruning strategy
|
||||
|
||||
Two knobs:
|
||||
|
||||
- `prune.idleHours`: remove containers not used in X hours (0 = disable)
|
||||
- `prune.maxAgeDays`: remove containers older than X days (0 = disable)
|
||||
|
||||
Example:
|
||||
|
||||
- Keep busy sessions but cap lifetime:
|
||||
`idleHours: 24`, `maxAgeDays: 7`
|
||||
- Never prune:
|
||||
@@ -431,8 +465,8 @@ Example:
|
||||
|
||||
### Security notes
|
||||
|
||||
- Hard wall only applies to **tools** (exec/read/write/edit/apply_patch).
|
||||
- Host-only tools like browser/camera/canvas are blocked by default.
|
||||
- Hard wall only applies to **tools** (exec/read/write/edit/apply_patch).
|
||||
- Host-only tools like browser/camera/canvas are blocked by default.
|
||||
- Allowing `browser` in sandbox **breaks isolation** (browser runs on host).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -57,7 +57,7 @@ primary_region = "iad"
|
||||
NODE_OPTIONS = "--max-old-space-size=1536"
|
||||
|
||||
[processes]
|
||||
app = "node dist/index.mjs gateway --allow-unconfigured --port 3000 --bind lan"
|
||||
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"
|
||||
|
||||
[http_service]
|
||||
internal_port = 3000
|
||||
@@ -78,13 +78,13 @@ primary_region = "iad"
|
||||
|
||||
**Key settings:**
|
||||
|
||||
| Setting | Why |
|
||||
|---------|-----|
|
||||
| `--bind lan` | Binds to `0.0.0.0` so Fly's proxy can reach the gateway |
|
||||
| `--allow-unconfigured` | Starts without a config file (you'll create one after) |
|
||||
| `internal_port = 3000` | Must match `--port 3000` (or `OPENCLAW_GATEWAY_PORT`) for Fly health checks |
|
||||
| `memory = "2048mb"` | 512MB is too small; 2GB recommended |
|
||||
| `OPENCLAW_STATE_DIR = "/data"` | Persists state on the volume |
|
||||
| Setting | Why |
|
||||
| ------------------------------ | --------------------------------------------------------------------------- |
|
||||
| `--bind lan` | Binds to `0.0.0.0` so Fly's proxy can reach the gateway |
|
||||
| `--allow-unconfigured` | Starts without a config file (you'll create one after) |
|
||||
| `internal_port = 3000` | Must match `--port 3000` (or `OPENCLAW_GATEWAY_PORT`) for Fly health checks |
|
||||
| `memory = "2048mb"` | 512MB is too small; 2GB recommended |
|
||||
| `OPENCLAW_STATE_DIR = "/data"` | Persists state on the volume |
|
||||
|
||||
## 3) Set secrets
|
||||
|
||||
@@ -104,6 +104,7 @@ fly secrets set DISCORD_BOT_TOKEN=MTQ...
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
- Non-loopback binds (`--bind lan`) require `OPENCLAW_GATEWAY_TOKEN` for security.
|
||||
- Treat these tokens like passwords.
|
||||
- **Prefer env vars over config file** for all API keys and tokens. This keeps secrets out of `openclaw.json` where they could be accidentally exposed or logged.
|
||||
@@ -117,12 +118,14 @@ fly deploy
|
||||
First deploy builds the Docker image (~2-3 minutes). Subsequent deploys are faster.
|
||||
|
||||
After deployment, verify:
|
||||
|
||||
```bash
|
||||
fly status
|
||||
fly logs
|
||||
```
|
||||
|
||||
You should see:
|
||||
|
||||
```
|
||||
[gateway] listening on ws://0.0.0.0:3000 (PID xxx)
|
||||
[discord] logged in to discord as xxx
|
||||
@@ -137,6 +140,7 @@ fly ssh console
|
||||
```
|
||||
|
||||
Create the config directory and file:
|
||||
|
||||
```bash
|
||||
mkdir -p /data
|
||||
cat > /data/openclaw.json << 'EOF'
|
||||
@@ -194,12 +198,14 @@ EOF
|
||||
**Note:** With `OPENCLAW_STATE_DIR=/data`, the config path is `/data/openclaw.json`.
|
||||
|
||||
**Note:** The Discord token can come from either:
|
||||
|
||||
- Environment variable: `DISCORD_BOT_TOKEN` (recommended for secrets)
|
||||
- Config file: `channels.discord.token`
|
||||
|
||||
If using env var, no need to add token to config. The gateway reads `DISCORD_BOT_TOKEN` automatically.
|
||||
|
||||
Restart to apply:
|
||||
|
||||
```bash
|
||||
exit
|
||||
fly machine restart <machine-id>
|
||||
@@ -210,6 +216,7 @@ fly machine restart <machine-id>
|
||||
### Control UI
|
||||
|
||||
Open in browser:
|
||||
|
||||
```bash
|
||||
fly open
|
||||
```
|
||||
@@ -250,12 +257,14 @@ Fly can't reach the gateway on the configured port.
|
||||
Container keeps restarting or getting killed. Signs: `SIGABRT`, `v8::internal::Runtime_AllocateInYoungGeneration`, or silent restarts.
|
||||
|
||||
**Fix:** Increase memory in `fly.toml`:
|
||||
|
||||
```toml
|
||||
[[vm]]
|
||||
memory = "2048mb"
|
||||
```
|
||||
|
||||
Or update an existing machine:
|
||||
|
||||
```bash
|
||||
fly machine update <machine-id> --vm-memory 2048 -y
|
||||
```
|
||||
@@ -269,6 +278,7 @@ Gateway refuses to start with "already running" errors.
|
||||
This happens when the container restarts but the PID lock file persists on the volume.
|
||||
|
||||
**Fix:** Delete the lock file:
|
||||
|
||||
```bash
|
||||
fly ssh console --command "rm -f /data/gateway.*.lock"
|
||||
fly machine restart <machine-id>
|
||||
@@ -281,6 +291,7 @@ The lock file is at `/data/gateway.*.lock` (not in a subdirectory).
|
||||
If using `--allow-unconfigured`, the gateway creates a minimal config. Your custom config at `/data/openclaw.json` should be read on restart.
|
||||
|
||||
Verify the config exists:
|
||||
|
||||
```bash
|
||||
fly ssh console --command "cat /data/openclaw.json"
|
||||
```
|
||||
@@ -299,6 +310,7 @@ fly sftp shell
|
||||
```
|
||||
|
||||
**Note:** `fly sftp` may fail if the file already exists. Delete first:
|
||||
|
||||
```bash
|
||||
fly ssh console --command "rm /data/openclaw.json"
|
||||
```
|
||||
@@ -332,10 +344,10 @@ If you need to change the startup command without a full redeploy:
|
||||
fly machines list
|
||||
|
||||
# Update command
|
||||
fly machine update <machine-id> --command "node dist/index.mjs gateway --port 3000 --bind lan" -y
|
||||
fly machine update <machine-id> --command "node dist/index.js gateway --port 3000 --bind lan" -y
|
||||
|
||||
# Or with memory increase
|
||||
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.mjs gateway --port 3000 --bind lan" -y
|
||||
fly machine update <machine-id> --vm-memory 2048 --command "node dist/index.js gateway --port 3000 --bind lan" -y
|
||||
```
|
||||
|
||||
**Note:** After `fly deploy`, the machine command may reset to what's in `fly.toml`. If you made manual changes, re-apply them after deploy.
|
||||
@@ -381,6 +393,7 @@ fly ips allocate-v6 --private -a my-openclaw
|
||||
```
|
||||
|
||||
After this, `fly ips list` should show only a `private` type IP:
|
||||
|
||||
```
|
||||
VERSION IP TYPE REGION
|
||||
v6 fdaa:x:x:x:x::x private global
|
||||
@@ -391,6 +404,7 @@ v6 fdaa:x:x:x:x::x private global
|
||||
Since there's no public URL, use one of these methods:
|
||||
|
||||
**Option 1: Local proxy (simplest)**
|
||||
|
||||
```bash
|
||||
# Forward local port 3000 to the app
|
||||
fly proxy 3000:3000 -a my-openclaw
|
||||
@@ -399,6 +413,7 @@ fly proxy 3000:3000 -a my-openclaw
|
||||
```
|
||||
|
||||
**Option 2: WireGuard VPN**
|
||||
|
||||
```bash
|
||||
# Create WireGuard config (one-time)
|
||||
fly wireguard create
|
||||
@@ -408,6 +423,7 @@ fly wireguard create
|
||||
```
|
||||
|
||||
**Option 3: SSH only**
|
||||
|
||||
```bash
|
||||
fly ssh console -a my-openclaw
|
||||
```
|
||||
@@ -421,6 +437,7 @@ If you need webhook callbacks (Twilio, Telnyx, etc.) without public exposure:
|
||||
3. **Outbound-only** - Some providers (Twilio) work fine for outbound calls without webhooks
|
||||
|
||||
Example voice-call config with ngrok:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
@@ -441,12 +458,12 @@ The ngrok tunnel runs inside the container and provides a public webhook URL wit
|
||||
|
||||
### Security benefits
|
||||
|
||||
| Aspect | Public | Private |
|
||||
|--------|--------|---------|
|
||||
| Internet scanners | Discoverable | Hidden |
|
||||
| Direct attacks | Possible | Blocked |
|
||||
| Control UI access | Browser | Proxy/VPN |
|
||||
| Webhook delivery | Direct | Via tunnel |
|
||||
| Aspect | Public | Private |
|
||||
| ----------------- | ------------ | ---------- |
|
||||
| Internet scanners | Discoverable | Hidden |
|
||||
| Direct attacks | Possible | Blocked |
|
||||
| Control UI access | Browser | Proxy/VPN |
|
||||
| Webhook delivery | Direct | Via tunnel |
|
||||
|
||||
## Notes
|
||||
|
||||
@@ -459,6 +476,7 @@ The ngrok tunnel runs inside the container and provides a public webhook URL wit
|
||||
## Cost
|
||||
|
||||
With the recommended config (`shared-cpu-2x`, 2GB RAM):
|
||||
|
||||
- ~$10-15/month depending on usage
|
||||
- Free tier includes some allowance
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Run OpenClaw Gateway 24/7 on a GCP Compute Engine VM (Docker) with durable state"
|
||||
summary: 'Run OpenClaw Gateway 24/7 on a GCP Compute Engine VM (Docker) with durable state'
|
||||
read_when:
|
||||
- You want OpenClaw running 24/7 on GCP
|
||||
- You want a production-grade, always-on Gateway on your own VM
|
||||
@@ -25,6 +25,7 @@ Pricing varies by machine type and region; pick the smallest VM that fits your w
|
||||
- Access the Control UI from your laptop via an SSH tunnel
|
||||
|
||||
The Gateway can be accessed via:
|
||||
|
||||
- SSH port forwarding from your laptop
|
||||
- Direct port exposure if you manage firewalling and tokens yourself
|
||||
|
||||
@@ -36,14 +37,14 @@ For the generic Docker flow, see [Docker](/install/docker).
|
||||
|
||||
## Quick path (experienced operators)
|
||||
|
||||
1) Create GCP project + enable Compute Engine API
|
||||
2) Create Compute Engine VM (e2-small, Debian 12, 20GB)
|
||||
3) SSH into the VM
|
||||
4) Install Docker
|
||||
5) Clone OpenClaw repository
|
||||
6) Create persistent host directories
|
||||
7) Configure `.env` and `docker-compose.yml`
|
||||
8) Bake required binaries, build, and launch
|
||||
1. Create GCP project + enable Compute Engine API
|
||||
2. Create Compute Engine VM (e2-small, Debian 12, 20GB)
|
||||
3. SSH into the VM
|
||||
4. Install Docker
|
||||
5. Clone OpenClaw repository
|
||||
6. Create persistent host directories
|
||||
7. Configure `.env` and `docker-compose.yml`
|
||||
8. Bake required binaries, build, and launch
|
||||
|
||||
---
|
||||
|
||||
@@ -112,9 +113,9 @@ gcloud services enable compute.googleapis.com
|
||||
|
||||
**Machine types:**
|
||||
|
||||
| Type | Specs | Cost | Notes |
|
||||
|------|-------|------|-------|
|
||||
| e2-small | 2 vCPU, 2GB RAM | ~$12/mo | Recommended |
|
||||
| Type | Specs | Cost | Notes |
|
||||
| -------- | ------------------------ | ------------------ | ------------------ |
|
||||
| e2-small | 2 vCPU, 2GB RAM | ~$12/mo | Recommended |
|
||||
| e2-micro | 2 vCPU (shared), 1GB RAM | Free tier eligible | May OOM under load |
|
||||
|
||||
**CLI:**
|
||||
@@ -263,20 +264,20 @@ services:
|
||||
ports:
|
||||
# Recommended: keep the Gateway loopback-only on the VM; access via SSH tunnel.
|
||||
# To expose it publicly, remove the `127.0.0.1:` prefix and firewall accordingly.
|
||||
- "127.0.0.1:${OPENCLAW_GATEWAY_PORT}:18789"
|
||||
- '127.0.0.1:${OPENCLAW_GATEWAY_PORT}:18789'
|
||||
|
||||
# Optional: only if you run iOS/Android nodes against this VM and need Canvas host.
|
||||
# If you expose this publicly, read /gateway/security and firewall accordingly.
|
||||
# - "18793:18793"
|
||||
command:
|
||||
[
|
||||
"node",
|
||||
"dist/index.mjs",
|
||||
"gateway",
|
||||
"--bind",
|
||||
"${OPENCLAW_GATEWAY_BIND}",
|
||||
"--port",
|
||||
"${OPENCLAW_GATEWAY_PORT}"
|
||||
'node',
|
||||
'dist/index.js',
|
||||
'gateway',
|
||||
'--bind',
|
||||
'${OPENCLAW_GATEWAY_BIND}',
|
||||
'--port',
|
||||
'${OPENCLAW_GATEWAY_PORT}',
|
||||
]
|
||||
```
|
||||
|
||||
@@ -290,6 +291,7 @@ Anything installed at runtime will be lost on restart.
|
||||
All external binaries required by skills must be installed at image build time.
|
||||
|
||||
The examples below show three common binaries only:
|
||||
|
||||
- `gog` for Gmail access
|
||||
- `goplaces` for Google Places
|
||||
- `wacli` for WhatsApp
|
||||
@@ -298,6 +300,7 @@ These are examples, not a complete list.
|
||||
You may install as many binaries as needed using the same pattern.
|
||||
|
||||
If you add new skills later that depend on additional binaries, you must:
|
||||
|
||||
1. Update the Dockerfile
|
||||
2. Rebuild the image
|
||||
3. Restart the containers
|
||||
@@ -338,7 +341,7 @@ RUN pnpm ui:build
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node","dist/index.mjs"]
|
||||
CMD ["node","dist/index.js"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -403,18 +406,18 @@ Paste your gateway token.
|
||||
OpenClaw runs in Docker, but Docker is not the source of truth.
|
||||
All long-lived state must survive restarts, rebuilds, and reboots.
|
||||
|
||||
| Component | Location | Persistence mechanism | Notes |
|
||||
|---|---|---|---|
|
||||
| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens |
|
||||
| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys |
|
||||
| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state |
|
||||
| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts |
|
||||
| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login |
|
||||
| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
|
||||
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
|
||||
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
|
||||
| OS packages | Container filesystem | Docker image | Do not install at runtime |
|
||||
| Docker container | Ephemeral | Restartable | Safe to destroy |
|
||||
| Component | Location | Persistence mechanism | Notes |
|
||||
| ------------------- | --------------------------------- | ---------------------- | -------------------------------- |
|
||||
| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens |
|
||||
| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys |
|
||||
| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state |
|
||||
| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts |
|
||||
| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login |
|
||||
| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
|
||||
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
|
||||
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
|
||||
| OS packages | Container filesystem | Docker image | Do not install at runtime |
|
||||
| Docker container | Ephemeral | Restartable | Safe to destroy |
|
||||
|
||||
---
|
||||
|
||||
@@ -473,6 +476,7 @@ For personal use, your default user account works fine.
|
||||
For automation or CI/CD pipelines, create a dedicated service account with minimal permissions:
|
||||
|
||||
1. Create a service account:
|
||||
|
||||
```bash
|
||||
gcloud iam service-accounts create openclaw-deploy \
|
||||
--display-name="OpenClaw Deployment"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Run OpenClaw Gateway 24/7 on a cheap Hetzner VPS (Docker) with durable state and baked-in binaries"
|
||||
summary: 'Run OpenClaw Gateway 24/7 on a cheap Hetzner VPS (Docker) with durable state and baked-in binaries'
|
||||
read_when:
|
||||
- You want OpenClaw running 24/7 on a cloud VPS (not your laptop)
|
||||
- You want a production-grade, always-on Gateway on your own VPS
|
||||
@@ -10,6 +10,7 @@ read_when:
|
||||
# OpenClaw on Hetzner (Docker, Production VPS Guide)
|
||||
|
||||
## Goal
|
||||
|
||||
Run a persistent OpenClaw Gateway on a Hetzner VPS using Docker, with durable state, baked-in binaries, and safe restart behavior.
|
||||
|
||||
If you want “OpenClaw 24/7 for ~$5”, this is the simplest reliable setup.
|
||||
@@ -24,6 +25,7 @@ Hetzner pricing changes; pick the smallest Debian/Ubuntu VPS and scale up if you
|
||||
- Access the Control UI from your laptop via an SSH tunnel
|
||||
|
||||
The Gateway can be accessed via:
|
||||
|
||||
- SSH port forwarding from your laptop
|
||||
- Direct port exposure if you manage firewalling and tokens yourself
|
||||
|
||||
@@ -35,29 +37,29 @@ For the generic Docker flow, see [Docker](/install/docker).
|
||||
|
||||
## Quick path (experienced operators)
|
||||
|
||||
1) Provision Hetzner VPS
|
||||
2) Install Docker
|
||||
3) Clone OpenClaw repository
|
||||
4) Create persistent host directories
|
||||
5) Configure `.env` and `docker-compose.yml`
|
||||
6) Bake required binaries into the image
|
||||
7) `docker compose up -d`
|
||||
8) Verify persistence and Gateway access
|
||||
1. Provision Hetzner VPS
|
||||
2. Install Docker
|
||||
3. Clone OpenClaw repository
|
||||
4. Create persistent host directories
|
||||
5. Configure `.env` and `docker-compose.yml`
|
||||
6. Bake required binaries into the image
|
||||
7. `docker compose up -d`
|
||||
8. Verify persistence and Gateway access
|
||||
|
||||
---
|
||||
|
||||
## What you need
|
||||
|
||||
- Hetzner VPS with root access
|
||||
- SSH access from your laptop
|
||||
- Basic comfort with SSH + copy/paste
|
||||
- ~20 minutes
|
||||
- Docker and Docker Compose
|
||||
- Model auth credentials
|
||||
- Optional provider credentials
|
||||
- WhatsApp QR
|
||||
- Telegram bot token
|
||||
- Gmail OAuth
|
||||
- Hetzner VPS with root access
|
||||
- SSH access from your laptop
|
||||
- Basic comfort with SSH + copy/paste
|
||||
- ~20 minutes
|
||||
- Docker and Docker Compose
|
||||
- Model auth credentials
|
||||
- Optional provider credentials
|
||||
- WhatsApp QR
|
||||
- Telegram bot token
|
||||
- Gmail OAuth
|
||||
|
||||
---
|
||||
|
||||
@@ -175,20 +177,20 @@ services:
|
||||
ports:
|
||||
# Recommended: keep the Gateway loopback-only on the VPS; access via SSH tunnel.
|
||||
# To expose it publicly, remove the `127.0.0.1:` prefix and firewall accordingly.
|
||||
- "127.0.0.1:${OPENCLAW_GATEWAY_PORT}:18789"
|
||||
- '127.0.0.1:${OPENCLAW_GATEWAY_PORT}:18789'
|
||||
|
||||
# Optional: only if you run iOS/Android nodes against this VPS and need Canvas host.
|
||||
# If you expose this publicly, read /gateway/security and firewall accordingly.
|
||||
# - "18793:18793"
|
||||
command:
|
||||
[
|
||||
"node",
|
||||
"dist/index.mjs",
|
||||
"gateway",
|
||||
"--bind",
|
||||
"${OPENCLAW_GATEWAY_BIND}",
|
||||
"--port",
|
||||
"${OPENCLAW_GATEWAY_PORT}"
|
||||
'node',
|
||||
'dist/index.js',
|
||||
'gateway',
|
||||
'--bind',
|
||||
'${OPENCLAW_GATEWAY_BIND}',
|
||||
'--port',
|
||||
'${OPENCLAW_GATEWAY_PORT}',
|
||||
]
|
||||
```
|
||||
|
||||
@@ -202,6 +204,7 @@ Anything installed at runtime will be lost on restart.
|
||||
All external binaries required by skills must be installed at image build time.
|
||||
|
||||
The examples below show three common binaries only:
|
||||
|
||||
- `gog` for Gmail access
|
||||
- `goplaces` for Google Places
|
||||
- `wacli` for WhatsApp
|
||||
@@ -210,6 +213,7 @@ These are examples, not a complete list.
|
||||
You may install as many binaries as needed using the same pattern.
|
||||
|
||||
If you add new skills later that depend on additional binaries, you must:
|
||||
|
||||
1. Update the Dockerfile
|
||||
2. Rebuild the image
|
||||
3. Restart the containers
|
||||
@@ -250,7 +254,7 @@ RUN pnpm ui:build
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
CMD ["node","dist/index.mjs"]
|
||||
CMD ["node","dist/index.js"]
|
||||
```
|
||||
|
||||
---
|
||||
@@ -311,15 +315,15 @@ Paste your gateway token.
|
||||
OpenClaw runs in Docker, but Docker is not the source of truth.
|
||||
All long-lived state must survive restarts, rebuilds, and reboots.
|
||||
|
||||
| Component | Location | Persistence mechanism | Notes |
|
||||
|---|---|---|---|
|
||||
| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens |
|
||||
| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys |
|
||||
| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state |
|
||||
| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts |
|
||||
| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login |
|
||||
| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
|
||||
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
|
||||
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
|
||||
| OS packages | Container filesystem | Docker image | Do not install at runtime |
|
||||
| Docker container | Ephemeral | Restartable | Safe to destroy |
|
||||
| Component | Location | Persistence mechanism | Notes |
|
||||
| ------------------- | --------------------------------- | ---------------------- | -------------------------------- |
|
||||
| Gateway config | `/home/node/.openclaw/` | Host volume mount | Includes `openclaw.json`, tokens |
|
||||
| Model auth profiles | `/home/node/.openclaw/` | Host volume mount | OAuth tokens, API keys |
|
||||
| Skill configs | `/home/node/.openclaw/skills/` | Host volume mount | Skill-level state |
|
||||
| Agent workspace | `/home/node/.openclaw/workspace/` | Host volume mount | Code and agent artifacts |
|
||||
| WhatsApp session | `/home/node/.openclaw/` | Host volume mount | Preserves QR login |
|
||||
| Gmail keyring | `/home/node/.openclaw/` | Host volume + password | Requires `GOG_KEYRING_PASSWORD` |
|
||||
| External binaries | `/usr/local/bin/` | Docker image | Must be baked at build time |
|
||||
| Node runtime | Container filesystem | Docker image | Rebuilt every image build |
|
||||
| OS packages | Container filesystem | Docker image | Do not install at runtime |
|
||||
| Docker container | Ephemeral | Restartable | Safe to destroy |
|
||||
|
||||
177
docs/plugin.md
177
docs/plugin.md
@@ -1,9 +1,10 @@
|
||||
---
|
||||
summary: "OpenClaw plugins/extensions: discovery, config, and safety"
|
||||
summary: 'OpenClaw plugins/extensions: discovery, config, and safety'
|
||||
read_when:
|
||||
- Adding or modifying plugins/extensions
|
||||
- Documenting plugin install or load rules
|
||||
---
|
||||
|
||||
# Plugins (Extensions)
|
||||
|
||||
## Quick start (new to plugins?)
|
||||
@@ -17,19 +18,19 @@ install).
|
||||
|
||||
Fast path:
|
||||
|
||||
1) See what’s already loaded:
|
||||
1. See what’s already loaded:
|
||||
|
||||
```bash
|
||||
openclaw plugins list
|
||||
```
|
||||
|
||||
2) Install an official plugin (example: Voice Call):
|
||||
2. Install an official plugin (example: Voice Call):
|
||||
|
||||
```bash
|
||||
openclaw plugins install @openclaw/voice-call
|
||||
```
|
||||
|
||||
3) Restart the Gateway, then configure under `plugins.entries.<id>.config`.
|
||||
3. Restart the Gateway, then configure under `plugins.entries.<id>.config`.
|
||||
|
||||
See [Voice Call](/plugins/voice-call) for a concrete example plugin.
|
||||
|
||||
@@ -73,12 +74,13 @@ Plugins can access selected core helpers via `api.runtime`. For telephony TTS:
|
||||
|
||||
```ts
|
||||
const result = await api.runtime.tts.textToSpeechTelephony({
|
||||
text: "Hello from OpenClaw",
|
||||
text: 'Hello from OpenClaw',
|
||||
cfg: api.config,
|
||||
});
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Uses core `messages.tts` configuration (OpenAI or ElevenLabs).
|
||||
- Returns PCM audio buffer + sample rate. Plugins must resample/encode for providers.
|
||||
- Edge TTS is not supported for telephony.
|
||||
@@ -87,18 +89,22 @@ Notes:
|
||||
|
||||
OpenClaw scans, in order:
|
||||
|
||||
1) Config paths
|
||||
1. Config paths
|
||||
|
||||
- `plugins.load.paths` (file or directory)
|
||||
|
||||
2) Workspace extensions
|
||||
2. Workspace extensions
|
||||
|
||||
- `<workspace>/.openclaw/extensions/*.ts`
|
||||
- `<workspace>/.openclaw/extensions/*/index.ts`
|
||||
|
||||
3) Global extensions
|
||||
3. Global extensions
|
||||
|
||||
- `~/.openclaw/extensions/*.ts`
|
||||
- `~/.openclaw/extensions/*/index.ts`
|
||||
|
||||
4) Bundled extensions (shipped with OpenClaw, **disabled by default**)
|
||||
4. Bundled extensions (shipped with OpenClaw, **disabled by default**)
|
||||
|
||||
- `<openclaw>/extensions/*`
|
||||
|
||||
Bundled plugins must be enabled explicitly via `plugins.entries.<id>.enabled`
|
||||
@@ -164,6 +170,7 @@ Example:
|
||||
|
||||
OpenClaw can also merge **external channel catalogs** (for example, an MPM
|
||||
registry export). Drop a JSON file at one of:
|
||||
|
||||
- `~/.openclaw/mpm/plugins.json`
|
||||
- `~/.openclaw/mpm/catalog.json`
|
||||
- `~/.openclaw/plugins/catalog.json`
|
||||
@@ -188,17 +195,18 @@ configured id.
|
||||
{
|
||||
plugins: {
|
||||
enabled: true,
|
||||
allow: ["voice-call"],
|
||||
deny: ["untrusted-plugin"],
|
||||
load: { paths: ["~/Projects/oss/voice-call-extension"] },
|
||||
allow: ['voice-call'],
|
||||
deny: ['untrusted-plugin'],
|
||||
load: { paths: ['~/Projects/oss/voice-call-extension'] },
|
||||
entries: {
|
||||
"voice-call": { enabled: true, config: { provider: "twilio" } }
|
||||
}
|
||||
}
|
||||
'voice-call': { enabled: true, config: { provider: 'twilio' } },
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `enabled`: master toggle (default: true)
|
||||
- `allow`: allowlist (optional)
|
||||
- `deny`: denylist (optional; deny wins)
|
||||
@@ -208,6 +216,7 @@ Fields:
|
||||
Config changes **require a gateway restart**.
|
||||
|
||||
Validation rules (strict):
|
||||
|
||||
- Unknown plugin ids in `entries`, `allow`, `deny`, or `slots` are **errors**.
|
||||
- Unknown `channels.<id>` keys are **errors** unless a plugin manifest declares
|
||||
the channel id.
|
||||
@@ -224,9 +233,9 @@ Some plugin categories are **exclusive** (only one active at a time). Use
|
||||
{
|
||||
plugins: {
|
||||
slots: {
|
||||
memory: "memory-core" // or "none" to disable memory plugins
|
||||
}
|
||||
}
|
||||
memory: 'memory-core', // or "none" to disable memory plugins
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -311,6 +320,7 @@ export default function register(api) {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Hook directories follow the normal hook structure (`HOOK.md` + `handler.ts`).
|
||||
- Hook eligibility rules still apply (OS/bins/env/config requirements).
|
||||
- Plugin-managed hooks show up in `openclaw hooks list` with `plugin:<id>`.
|
||||
@@ -330,29 +340,29 @@ Example:
|
||||
|
||||
```ts
|
||||
api.registerProvider({
|
||||
id: "acme",
|
||||
label: "AcmeAI",
|
||||
id: 'acme',
|
||||
label: 'AcmeAI',
|
||||
auth: [
|
||||
{
|
||||
id: "oauth",
|
||||
label: "OAuth",
|
||||
kind: "oauth",
|
||||
id: 'oauth',
|
||||
label: 'OAuth',
|
||||
kind: 'oauth',
|
||||
run: async (ctx) => {
|
||||
// Run OAuth flow and return auth profiles.
|
||||
return {
|
||||
profiles: [
|
||||
{
|
||||
profileId: "acme:default",
|
||||
profileId: 'acme:default',
|
||||
credential: {
|
||||
type: "oauth",
|
||||
provider: "acme",
|
||||
access: "...",
|
||||
refresh: "...",
|
||||
type: 'oauth',
|
||||
provider: 'acme',
|
||||
access: '...',
|
||||
refresh: '...',
|
||||
expires: Date.now() + 3600 * 1000,
|
||||
},
|
||||
},
|
||||
],
|
||||
defaultModel: "acme/opus-1",
|
||||
defaultModel: 'acme/opus-1',
|
||||
};
|
||||
},
|
||||
},
|
||||
@@ -361,6 +371,7 @@ api.registerProvider({
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `run` receives a `ProviderAuthContext` with `prompter`, `runtime`,
|
||||
`openUrl`, and `oauth.createVpsAwareHandlers` helpers.
|
||||
- Return `configPatch` when you need to add default models or provider config.
|
||||
@@ -374,23 +385,26 @@ validated by your channel plugin code.
|
||||
|
||||
```ts
|
||||
const myChannel = {
|
||||
id: "acmechat",
|
||||
id: 'acmechat',
|
||||
meta: {
|
||||
id: "acmechat",
|
||||
label: "AcmeChat",
|
||||
selectionLabel: "AcmeChat (API)",
|
||||
docsPath: "/channels/acmechat",
|
||||
blurb: "demo channel plugin.",
|
||||
aliases: ["acme"],
|
||||
id: 'acmechat',
|
||||
label: 'AcmeChat',
|
||||
selectionLabel: 'AcmeChat (API)',
|
||||
docsPath: '/channels/acmechat',
|
||||
blurb: 'demo channel plugin.',
|
||||
aliases: ['acme'],
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
capabilities: { chatTypes: ['direct'] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
|
||||
listAccountIds: (cfg) =>
|
||||
Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
(cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? { accountId }),
|
||||
cfg.channels?.acmechat?.accounts?.[accountId ?? 'default'] ?? {
|
||||
accountId,
|
||||
},
|
||||
},
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
deliveryMode: 'direct',
|
||||
sendText: async () => ({ ok: true }),
|
||||
},
|
||||
};
|
||||
@@ -401,6 +415,7 @@ export default function (api) {
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Put config under `channels.<id>` (not `plugins.entries`).
|
||||
- `meta.label` is used for labels in CLI/UI lists.
|
||||
- `meta.aliases` adds alternate ids for normalization and CLI inputs.
|
||||
@@ -412,27 +427,32 @@ Notes:
|
||||
Use this when you want a **new chat surface** (a “messaging channel”), not a model provider.
|
||||
Model provider docs live under `/providers/*`.
|
||||
|
||||
1) Pick an id + config shape
|
||||
1. Pick an id + config shape
|
||||
|
||||
- All channel config lives under `channels.<id>`.
|
||||
- Prefer `channels.<id>.accounts.<accountId>` for multi‑account setups.
|
||||
|
||||
2) Define the channel metadata
|
||||
2. Define the channel metadata
|
||||
|
||||
- `meta.label`, `meta.selectionLabel`, `meta.docsPath`, `meta.blurb` control CLI/UI lists.
|
||||
- `meta.docsPath` should point at a docs page like `/channels/<id>`.
|
||||
- `meta.preferOver` lets a plugin replace another channel (auto-enable prefers it).
|
||||
- `meta.detailLabel` and `meta.systemImage` are used by UIs for detail text/icons.
|
||||
|
||||
3) Implement the required adapters
|
||||
3. Implement the required adapters
|
||||
|
||||
- `config.listAccountIds` + `config.resolveAccount`
|
||||
- `capabilities` (chat types, media, threads, etc.)
|
||||
- `outbound.deliveryMode` + `outbound.sendText` (for basic send)
|
||||
|
||||
4) Add optional adapters as needed
|
||||
4. Add optional adapters as needed
|
||||
|
||||
- `setup` (wizard), `security` (DM policy), `status` (health/diagnostics)
|
||||
- `gateway` (start/stop/login), `mentions`, `threading`, `streaming`
|
||||
- `actions` (message actions), `commands` (native command behavior)
|
||||
|
||||
5) Register the channel in your plugin
|
||||
5. Register the channel in your plugin
|
||||
|
||||
- `api.registerChannel({ plugin })`
|
||||
|
||||
Minimal config example:
|
||||
@@ -442,10 +462,10 @@ Minimal config example:
|
||||
channels: {
|
||||
acmechat: {
|
||||
accounts: {
|
||||
default: { token: "ACME_TOKEN", enabled: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
default: { token: 'ACME_TOKEN', enabled: true },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
@@ -453,23 +473,26 @@ Minimal channel plugin (outbound‑only):
|
||||
|
||||
```ts
|
||||
const plugin = {
|
||||
id: "acmechat",
|
||||
id: 'acmechat',
|
||||
meta: {
|
||||
id: "acmechat",
|
||||
label: "AcmeChat",
|
||||
selectionLabel: "AcmeChat (API)",
|
||||
docsPath: "/channels/acmechat",
|
||||
blurb: "AcmeChat messaging channel.",
|
||||
aliases: ["acme"],
|
||||
id: 'acmechat',
|
||||
label: 'AcmeChat',
|
||||
selectionLabel: 'AcmeChat (API)',
|
||||
docsPath: '/channels/acmechat',
|
||||
blurb: 'AcmeChat messaging channel.',
|
||||
aliases: ['acme'],
|
||||
},
|
||||
capabilities: { chatTypes: ["direct"] },
|
||||
capabilities: { chatTypes: ['direct'] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
|
||||
listAccountIds: (cfg) =>
|
||||
Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
(cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? { accountId }),
|
||||
cfg.channels?.acmechat?.accounts?.[accountId ?? 'default'] ?? {
|
||||
accountId,
|
||||
},
|
||||
},
|
||||
outbound: {
|
||||
deliveryMode: "direct",
|
||||
deliveryMode: 'direct',
|
||||
sendText: async ({ text }) => {
|
||||
// deliver `text` to your channel here
|
||||
return { ok: true };
|
||||
@@ -493,7 +516,7 @@ See the dedicated guide: [Plugin agent tools](/plugins/agent-tools).
|
||||
|
||||
```ts
|
||||
export default function (api) {
|
||||
api.registerGatewayMethod("myplugin.status", ({ respond }) => {
|
||||
api.registerGatewayMethod('myplugin.status', ({ respond }) => {
|
||||
respond(true, { ok: true });
|
||||
});
|
||||
}
|
||||
@@ -503,11 +526,14 @@ export default function (api) {
|
||||
|
||||
```ts
|
||||
export default function (api) {
|
||||
api.registerCli(({ program }) => {
|
||||
program.command("mycmd").action(() => {
|
||||
console.log("Hello");
|
||||
});
|
||||
}, { commands: ["mycmd"] });
|
||||
api.registerCli(
|
||||
({ program }) => {
|
||||
program.command('mycmd').action(() => {
|
||||
console.log('Hello');
|
||||
});
|
||||
},
|
||||
{ commands: ['mycmd'] },
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -520,8 +546,8 @@ that don't need LLM processing.
|
||||
```ts
|
||||
export default function (api) {
|
||||
api.registerCommand({
|
||||
name: "mystatus",
|
||||
description: "Show plugin status",
|
||||
name: 'mystatus',
|
||||
description: 'Show plugin status',
|
||||
handler: (ctx) => ({
|
||||
text: `Plugin is running! Channel: ${ctx.channel}`,
|
||||
}),
|
||||
@@ -550,12 +576,12 @@ Example with authorization and arguments:
|
||||
|
||||
```ts
|
||||
api.registerCommand({
|
||||
name: "setmode",
|
||||
description: "Set plugin mode",
|
||||
name: 'setmode',
|
||||
description: 'Set plugin mode',
|
||||
acceptsArgs: true,
|
||||
requireAuth: true,
|
||||
handler: async (ctx) => {
|
||||
const mode = ctx.args?.trim() || "default";
|
||||
const mode = ctx.args?.trim() || 'default';
|
||||
await saveMode(mode);
|
||||
return { text: `Mode set to: ${mode}` };
|
||||
},
|
||||
@@ -563,6 +589,7 @@ api.registerCommand({
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- Plugin commands are processed **before** built-in commands and the AI agent
|
||||
- Commands are registered globally and work across all channels
|
||||
- Command names are case-insensitive (`/MyStatus` matches `/mystatus`)
|
||||
@@ -575,9 +602,9 @@ Notes:
|
||||
```ts
|
||||
export default function (api) {
|
||||
api.registerService({
|
||||
id: "my-service",
|
||||
start: () => api.logger.info("ready"),
|
||||
stop: () => api.logger.info("bye"),
|
||||
id: 'my-service',
|
||||
start: () => api.logger.info('ready'),
|
||||
stop: () => api.logger.info('bye'),
|
||||
});
|
||||
}
|
||||
```
|
||||
@@ -635,4 +662,4 @@ Plugins run in-process with the Gateway. Treat them as trusted code:
|
||||
Plugins can (and should) ship tests:
|
||||
|
||||
- In-repo plugins can keep Vitest tests under `src/**` (example: `src/plugins/voice-call.plugin.test.ts`).
|
||||
- Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.mjs`).
|
||||
- Separately published plugins should run their own CI (lint/build/test) and validate `openclaw.extensions` points at the built entrypoint (`dist/index.js`).
|
||||
|
||||
Reference in New Issue
Block a user