```
+
- **Allowlist mode**: set `channels.feishu.allowFrom` with allowed Open IDs
### Group chats
diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md
index 07c7dd7dc..39192ecae 100644
--- a/docs/channels/googlechat.md
+++ b/docs/channels/googlechat.md
@@ -101,6 +101,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
5. **Verify the configuration:**
+
```bash
tailscale serve status
tailscale funnel status
@@ -225,6 +226,7 @@ This means the webhook handler isn't registered. Common causes:
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
3. **Gateway not restarted**: After adding config, restart the gateway:
+
```bash
openclaw gateway restart
```
diff --git a/docs/channels/line.md b/docs/channels/line.md
index f68ae5aa1..d32e683fb 100644
--- a/docs/channels/line.md
+++ b/docs/channels/line.md
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/line
## Setup
1. Create a LINE Developers account and open the Console:
- https://developers.line.biz/console/
+ [https://developers.line.biz/console/](https://developers.line.biz/console/)
2. Create (or pick) a Provider and add a **Messaging API** channel.
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
4. Enable **Use webhook** in the Messaging API settings.
diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md
index a196a68b6..56b363fdd 100644
--- a/docs/channels/matrix.md
+++ b/docs/channels/matrix.md
@@ -74,7 +74,7 @@ Details: [Plugins](/plugin)
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
5. Restart the gateway (or finish onboarding).
6. Start a DM with the bot or invite it to a room from any Matrix client
- (Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
+ (Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE,
so set `channels.matrix.encryption: true` and verify the device.
Minimal config (access token, user ID auto-fetched):
diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md
index a18e8063d..92f413811 100644
--- a/docs/channels/msteams.md
+++ b/docs/channels/msteams.md
@@ -166,7 +166,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
-3. Click **Review + create** → **Create** (wait ~1-2 minutes)
+1. Click **Review + create** → **Create** (wait ~1-2 minutes)
### Step 2: Get Credentials
@@ -558,6 +558,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
```
4. **Configure OpenClaw:**
+
```json5
{
channels: {
@@ -747,7 +748,7 @@ Bots have limited support in private channels:
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
-- **"Something went wrong" on upload:** Upload via https://admin.teams.microsoft.com instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
+- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
### RSC permissions not working
diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md
index edca54bc4..efecfd990 100644
--- a/docs/channels/nextcloud-talk.md
+++ b/docs/channels/nextcloud-talk.md
@@ -34,9 +34,11 @@ Details: [Plugins](/plugin)
1. Install the Nextcloud Talk plugin.
2. On your Nextcloud server, create a bot:
+
```bash
./occ talk:bot:install "OpenClaw" "" "" --feature reaction
```
+
3. Enable the bot in the target room settings.
4. Configure OpenClaw:
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
diff --git a/docs/channels/nostr.md b/docs/channels/nostr.md
index 3368933d6..30654a690 100644
--- a/docs/channels/nostr.md
+++ b/docs/channels/nostr.md
@@ -49,7 +49,7 @@ Restart the Gateway after installing or enabling plugins.
nak key generate
```
-2. Add to config:
+1. Add to config:
```json
{
@@ -61,13 +61,13 @@ nak key generate
}
```
-3. Export the key:
+1. Export the key:
```bash
export NOSTR_PRIVATE_KEY="nsec1..."
```
-4. Restart the Gateway.
+1. Restart the Gateway.
## Configuration reference
diff --git a/docs/channels/slack.md b/docs/channels/slack.md
index c8329439f..1343ebf77 100644
--- a/docs/channels/slack.md
+++ b/docs/channels/slack.md
@@ -30,7 +30,7 @@ Minimal config:
### Setup
-1. Create a Slack app (From scratch) in https://api.slack.com/apps.
+1. Create a Slack app (From scratch) in [https://api.slack.com/apps](https://api.slack.com/apps).
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
@@ -260,30 +260,30 @@ If you enable native commands, add one `slash_commands` entry per command you wa
Slack's Conversations API is type-scoped: you only need the scopes for the
conversation types you actually touch (channels, groups, im, mpim). See
-https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
+[https://docs.slack.dev/apis/web-api/using-the-conversations-api/](https://docs.slack.dev/apis/web-api/using-the-conversations-api/) for the overview.
### Bot token scopes (required)
- `chat:write` (send/update/delete messages via `chat.postMessage`)
- https://docs.slack.dev/reference/methods/chat.postMessage
+ [https://docs.slack.dev/reference/methods/chat.postMessage](https://docs.slack.dev/reference/methods/chat.postMessage)
- `im:write` (open DMs via `conversations.open` for user DMs)
- https://docs.slack.dev/reference/methods/conversations.open
+ [https://docs.slack.dev/reference/methods/conversations.open](https://docs.slack.dev/reference/methods/conversations.open)
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
- https://docs.slack.dev/reference/methods/conversations.history
+ [https://docs.slack.dev/reference/methods/conversations.history](https://docs.slack.dev/reference/methods/conversations.history)
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
- https://docs.slack.dev/reference/methods/conversations.info
+ [https://docs.slack.dev/reference/methods/conversations.info](https://docs.slack.dev/reference/methods/conversations.info)
- `users:read` (user lookup)
- https://docs.slack.dev/reference/methods/users.info
+ [https://docs.slack.dev/reference/methods/users.info](https://docs.slack.dev/reference/methods/users.info)
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
- https://docs.slack.dev/reference/methods/reactions.get
- https://docs.slack.dev/reference/methods/reactions.add
+ [https://docs.slack.dev/reference/methods/reactions.get](https://docs.slack.dev/reference/methods/reactions.get)
+ [https://docs.slack.dev/reference/methods/reactions.add](https://docs.slack.dev/reference/methods/reactions.add)
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
- https://docs.slack.dev/reference/scopes/pins.read
- https://docs.slack.dev/reference/scopes/pins.write
+ [https://docs.slack.dev/reference/scopes/pins.read](https://docs.slack.dev/reference/scopes/pins.read)
+ [https://docs.slack.dev/reference/scopes/pins.write](https://docs.slack.dev/reference/scopes/pins.write)
- `emoji:read` (`emoji.list`)
- https://docs.slack.dev/reference/scopes/emoji.read
+ [https://docs.slack.dev/reference/scopes/emoji.read](https://docs.slack.dev/reference/scopes/emoji.read)
- `files:write` (uploads via `files.uploadV2`)
- https://docs.slack.dev/messaging/working-with-files/#upload
+ [https://docs.slack.dev/messaging/working-with-files/#upload](https://docs.slack.dev/messaging/working-with-files/#upload)
### User token scopes (optional, read-only by default)
@@ -302,9 +302,9 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
- `chat:write.public` (only if we want to post to channels the bot isn't in)
- https://docs.slack.dev/reference/scopes/chat.write.public
+ [https://docs.slack.dev/reference/scopes/chat.write.public](https://docs.slack.dev/reference/scopes/chat.write.public)
- `users:read.email` (only if we need email fields from `users.info`)
- https://docs.slack.dev/changelog/2017-04-narrowing-email-access
+ [https://docs.slack.dev/changelog/2017-04-narrowing-email-access](https://docs.slack.dev/changelog/2017-04-narrowing-email-access)
- `files:read` (only if we start listing/reading file metadata)
## Config
diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md
index c7fb4fe57..9e3921713 100644
--- a/docs/channels/telegram.md
+++ b/docs/channels/telegram.md
@@ -74,9 +74,9 @@ If both env and config are set, config takes precedence.
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
-3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
-4. DM access defaults to pairing. Approve the code when the bot is first contacted.
-5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
+1. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
+2. DM access defaults to pairing. Approve the code when the bot is first contacted.
+3. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
## Token + privacy + permissions (Telegram side)
@@ -365,6 +365,7 @@ Alternate (official Bot API):
1. DM your bot.
2. Fetch updates with your bot token and read `message.from.id`:
+
```bash
curl "https://api.telegram.org/bot/getUpdates"
```
diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md
index 7901c0427..ac46e35d6 100644
--- a/docs/channels/twitch.md
+++ b/docs/channels/twitch.md
@@ -34,7 +34,7 @@ Details: [Plugins](/plugin)
- Select **Bot Token**
- Verify scopes `chat:read` and `chat:write` are selected
- Copy the **Client ID** and **Access Token**
-3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
+3. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
4. Configure the token:
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
- Or config: `channels.twitch.accessToken`
@@ -123,7 +123,7 @@ Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
-Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/ (Convert your Twitch username to ID)
+Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) (Convert your Twitch username to ID)
## Token refresh (optional)
diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md
index 1741ee1b7..966c0902a 100644
--- a/docs/channels/whatsapp.md
+++ b/docs/channels/whatsapp.md
@@ -205,11 +205,13 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- `Body` is the current message body with envelope.
- Quoted reply context is **always appended**:
+
```
[Replying to +1555 id:ABC123]
>
[/Replying]
```
+
- Reply metadata also set:
- `ReplyToId` = stanzaId
- `ReplyToBody` = quoted body or media placeholder
diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md
index 0f247190c..0bb2607ec 100644
--- a/docs/channels/zalo.md
+++ b/docs/channels/zalo.md
@@ -57,7 +57,7 @@ It is a good fit for support or notifications where you want deterministic routi
### 1) Create a bot token (Zalo Bot Platform)
-1. Go to **https://bot.zaloplatforms.com** and sign in.
+1. Go to **[https://bot.zaloplatforms.com](https://bot.zaloplatforms.com)** and sign in.
2. Create a new bot and configure its settings.
3. Copy the bot token (format: `12345689:abc-xyz`).
@@ -81,8 +81,8 @@ Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
-3. Restart the gateway. Zalo starts when a token is resolved (env or config).
-4. DM access defaults to pairing. Approve the code when the bot is first contacted.
+1. Restart the gateway. Zalo starts when a token is resolved (env or config).
+2. DM access defaults to pairing. Approve the code when the bot is first contacted.
## How it works (behavior)
diff --git a/docs/channels/zalouser.md b/docs/channels/zalouser.md
index 5a1b555b8..53c9a4d2c 100644
--- a/docs/channels/zalouser.md
+++ b/docs/channels/zalouser.md
@@ -46,8 +46,8 @@ The Gateway machine must have the `zca` binary available in `PATH`.
}
```
-4. Restart the Gateway (or finish onboarding).
-5. DM access defaults to pairing; approve the pairing code on first contact.
+1. Restart the Gateway (or finish onboarding).
+2. DM access defaults to pairing; approve the pairing code on first contact.
## What it is
diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md
index a1c7f3383..a9676b171 100644
--- a/docs/concepts/architecture.md
+++ b/docs/concepts/architecture.md
@@ -110,9 +110,11 @@ Details: [Gateway protocol](/gateway/protocol), [Pairing](/start/pairing),
- Preferred: Tailscale or VPN.
- Alternative: SSH tunnel
+
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
+
- The same handshake + auth token apply over the tunnel.
- TLS + optional pinning can be enabled for WS in remote setups.
diff --git a/docs/concepts/groups.md b/docs/concepts/groups.md
index 635211d33..b873f995e 100644
--- a/docs/concepts/groups.md
+++ b/docs/concepts/groups.md
@@ -301,7 +301,7 @@ Common intents (copy/paste):
}
```
-2. Allow only specific groups (WhatsApp)
+1. Allow only specific groups (WhatsApp)
```json5
{
@@ -316,7 +316,7 @@ Common intents (copy/paste):
}
```
-3. Allow all groups but require mention (explicit)
+1. Allow all groups but require mention (explicit)
```json5
{
@@ -328,7 +328,7 @@ Common intents (copy/paste):
}
```
-4. Only the owner can trigger in groups (WhatsApp)
+1. Only the owner can trigger in groups (WhatsApp)
```json5
{
diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md
index 4b499860b..6ed386cb0 100644
--- a/docs/concepts/memory.md
+++ b/docs/concepts/memory.md
@@ -302,8 +302,8 @@ Why OpenAI batch is fast + cheap:
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
- See the OpenAI Batch API docs and pricing for details:
- - https://platform.openai.com/docs/api-reference/batch
- - https://platform.openai.com/pricing
+ - [https://platform.openai.com/docs/api-reference/batch](https://platform.openai.com/docs/api-reference/batch)
+ - [https://platform.openai.com/pricing](https://platform.openai.com/pricing)
Config example:
@@ -382,11 +382,11 @@ Implementation sketch:
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
-2. Convert BM25 rank into a 0..1-ish score:
+1. Convert BM25 rank into a 0..1-ish score:
- `textScore = 1 / (1 + max(0, bm25Rank))`
-3. Union candidates by chunk id and compute a weighted score:
+1. Union candidates by chunk id and compute a weighted score:
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md
index 4d313cf0f..fba56a34a 100644
--- a/docs/concepts/model-providers.md
+++ b/docs/concepts/model-providers.md
@@ -136,14 +136,14 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
Kimi K2 model IDs:
-{/_ moonshot-kimi-k2-model-refs:start _/ && null}
+{/_moonshot-kimi-k2-model-refs:start_/ && null}
- `moonshot/kimi-k2.5`
- `moonshot/kimi-k2-0905-preview`
- `moonshot/kimi-k2-turbo-preview`
- `moonshot/kimi-k2-thinking`
- `moonshot/kimi-k2-thinking-turbo`
- {/_ moonshot-kimi-k2-model-refs:end _/ && null}
+ {/_moonshot-kimi-k2-model-refs:end_/ && null}
```json5
{
@@ -242,7 +242,7 @@ Ollama is a local LLM runtime that provides an OpenAI-compatible API:
- Provider: `ollama`
- Auth: None required (local server)
- Example model: `ollama/llama3.3`
-- Installation: https://ollama.ai
+- Installation: [https://ollama.ai](https://ollama.ai)
```bash
# Install Ollama, then pull a model:
diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md
index aafa80473..acb2bf8b5 100644
--- a/docs/concepts/system-prompt.md
+++ b/docs/concepts/system-prompt.md
@@ -110,6 +110,6 @@ This keeps the base prompt small while still enabling targeted skill usage.
When available, the system prompt includes a **Documentation** section that points to the
local OpenClaw docs directory (either `docs/` in the repo workspace or the bundled npm
package docs) and also notes the public mirror, source repo, community Discord, and
-ClawHub (https://clawhub.com) for skills discovery. The prompt instructs the model to consult local docs first
+ClawHub ([https://clawhub.com](https://clawhub.com)) for skills discovery. The prompt instructs the model to consult local docs first
for OpenClaw behavior, commands, configuration, or architecture, and to run
`openclaw status` itself when possible (asking the user only when it lacks access).
diff --git a/docs/concepts/typebox.md b/docs/concepts/typebox.md
index 38ee7d8ca..a44f3ffd2 100644
--- a/docs/concepts/typebox.md
+++ b/docs/concepts/typebox.md
@@ -217,7 +217,7 @@ export type SystemEchoParams = Static;
export type SystemEchoResult = Static;
```
-2. **Validation**
+1. **Validation**
In `src/gateway/protocol/index.ts`, export an AJV validator:
@@ -225,7 +225,7 @@ In `src/gateway/protocol/index.ts`, export an AJV validator:
export const validateSystemEchoParams = ajv.compile(SystemEchoParamsSchema);
```
-3. **Server behavior**
+1. **Server behavior**
Add a handler in `src/gateway/server-methods/system.ts`:
@@ -241,13 +241,13 @@ export const systemHandlers: GatewayRequestHandlers = {
Register it in `src/gateway/server-methods.ts` (already merges `systemHandlers`),
then add `"system.echo"` to `METHODS` in `src/gateway/server.ts`.
-4. **Regenerate**
+1. **Regenerate**
```bash
pnpm protocol:check
```
-5. **Tests + docs**
+1. **Tests + docs**
Add a server test in `src/gateway/server.*.test.ts` and note the method in docs.
@@ -280,7 +280,7 @@ Unknown frame types are preserved as raw payloads for forward compatibility.
Generated JSON Schema is in the repo at `dist/protocol.schema.json`. The
published raw file is typically available at:
-- https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json
+- [https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json](https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json)
## When you change schemas
diff --git a/docs/debug/node-issue.md b/docs/debug/node-issue.md
index ce46b1a05..8355d2abc 100644
--- a/docs/debug/node-issue.md
+++ b/docs/debug/node-issue.md
@@ -62,19 +62,21 @@ node --import tsx scripts/repro/tsx-name-repro.ts
- Use Bun for dev scripts (current temporary revert).
- Use Node + tsc watch, then run compiled output:
+
```bash
pnpm exec tsc --watch --preserveWatchOutput
node --watch openclaw.mjs status
```
+
- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
- Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
- Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific.
## References
-- https://opennext.js.org/cloudflare/howtos/keep_names
-- https://esbuild.github.io/api/#keep-names
-- https://github.com/evanw/esbuild/issues/1031
+- [https://opennext.js.org/cloudflare/howtos/keep_names](https://opennext.js.org/cloudflare/howtos/keep_names)
+- [https://esbuild.github.io/api/#keep-names](https://esbuild.github.io/api/#keep-names)
+- [https://github.com/evanw/esbuild/issues/1031](https://github.com/evanw/esbuild/issues/1031)
## Next steps
diff --git a/docs/experiments/research/memory.md b/docs/experiments/research/memory.md
index 99135e78b..8d1404d7e 100644
--- a/docs/experiments/research/memory.md
+++ b/docs/experiments/research/memory.md
@@ -47,7 +47,7 @@ Two pieces to blend:
- everything else is out-of-context and retrieved via tools
- memory writes are explicit tool calls (append/replace/insert), persisted, then re-injected next turn
-2. **Hindsight-style memory substrate**
+1. **Hindsight-style memory substrate**
- separate what’s observed vs what’s believed vs what’s summarized
- support retain/recall/reflect
diff --git a/docs/gateway/authentication.md b/docs/gateway/authentication.md
index 9b616084c..e63994bac 100644
--- a/docs/gateway/authentication.md
+++ b/docs/gateway/authentication.md
@@ -27,7 +27,7 @@ export ANTHROPIC_API_KEY="..."
openclaw models status
```
-3. If the Gateway runs under systemd/launchd, prefer putting the key in
+1. If the Gateway runs under systemd/launchd, prefer putting the key in
`~/.openclaw/.env` so the daemon can read it:
```bash
diff --git a/docs/gateway/bonjour.md b/docs/gateway/bonjour.md
index b8f08741e..9e2ad8753 100644
--- a/docs/gateway/bonjour.md
+++ b/docs/gateway/bonjour.md
@@ -105,10 +105,13 @@ The Gateway advertises small non‑secret hints to make UI flows convenient:
Useful built‑in tools:
- Browse instances:
+
```bash
dns-sd -B _openclaw-gw._tcp local.
```
+
- Resolve one instance (replace ``):
+
```bash
dns-sd -L "" _openclaw-gw._tcp local.
```
diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md
index 545a937df..639f84bd6 100644
--- a/docs/gateway/configuration.md
+++ b/docs/gateway/configuration.md
@@ -1978,11 +1978,13 @@ Block streaming:
- `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to
800–1200 chars, prefers paragraph breaks (`\n\n`), then newlines, then sentences.
Example:
+
```json5
{
agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } },
}
```
+
- `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending.
Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`
with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default
@@ -1996,11 +1998,13 @@ Block streaming:
Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`).
Per-agent override: `agents.list[].humanDelay`.
Example:
+
```json5
{
agents: { defaults: { humanDelay: { mode: "natural" } } },
}
```
+
See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.
Typing indicators:
@@ -2066,7 +2070,7 @@ of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.
- `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)
- `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)
- `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`)
-- `tools.web.fetch.firecrawl.baseUrl` (default https://api.firecrawl.dev)
+- `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev))
- `tools.web.fetch.firecrawl.onlyMainContent` (default true)
- `tools.web.fetch.firecrawl.maxAgeMs` (optional)
- `tools.web.fetch.firecrawl.timeoutSeconds` (optional)
@@ -2482,7 +2486,7 @@ Select the model via `agents.defaults.model.primary` (provider/model).
OpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses
the built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or
-`OPENCODE_ZEN_API_KEY`) from https://opencode.ai/auth.
+`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth).
Notes:
diff --git a/docs/gateway/index.md b/docs/gateway/index.md
index 06dd72c13..64697f1f4 100644
--- a/docs/gateway/index.md
+++ b/docs/gateway/index.md
@@ -49,9 +49,11 @@ pnpm gateway:watch
## Remote access
- Tailscale/VPN preferred; otherwise SSH tunnel:
+
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
+
- Clients then connect to `ws://127.0.0.1:18789` through the tunnel.
- If a token is configured, clients must include it in `connect.params.auth.token` even over the tunnel.
diff --git a/docs/gateway/local-models.md b/docs/gateway/local-models.md
index fe715ab05..3f7e13d41 100644
--- a/docs/gateway/local-models.md
+++ b/docs/gateway/local-models.md
@@ -52,7 +52,7 @@ Best current local stack. Load MiniMax M2.1 in LM Studio, enable the local serve
**Setup checklist**
-- Install LM Studio: https://lmstudio.ai
+- Install LM Studio: [https://lmstudio.ai](https://lmstudio.ai)
- In LM Studio, download the **largest MiniMax M2.1 build available** (avoid “small”/heavily quantized variants), start the server, confirm `http://127.0.0.1:1234/v1/models` lists it.
- Keep the model loaded; cold-load adds startup latency.
- Adjust `contextWindow`/`maxTokens` if your LM Studio build differs.
diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md
index c6b521048..f6bd91734 100644
--- a/docs/gateway/security/index.md
+++ b/docs/gateway/security/index.md
@@ -773,18 +773,22 @@ If it fails, there are new candidates not yet in the baseline.
### If CI fails
1. Reproduce locally:
+
```bash
detect-secrets scan --baseline .secrets.baseline
```
+
2. Understand the tools:
- `detect-secrets scan` finds candidates and compares them to the baseline.
- `detect-secrets audit` opens an interactive review to mark each baseline
item as real or false positive.
3. For real secrets: rotate/remove them, then re-run the scan to update the baseline.
4. For false positives: run the interactive audit and mark them as false:
+
```bash
detect-secrets audit .secrets.baseline
```
+
5. If you need new excludes, add them to `.detect-secrets.cfg` and regenerate the
baseline with matching `--exclude-files` / `--exclude-lines` flags (the config
file is reference-only; detect-secrets doesn’t read it automatically).
@@ -814,7 +818,7 @@ Mario asking for find ~
Found a vulnerability in OpenClaw? Please report responsibly:
-1. Email: security@openclaw.ai
+1. Email: [security@openclaw.ai](mailto:security@openclaw.ai)
2. Don't post publicly until fixed
3. We'll credit you (unless you prefer anonymity)
diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md
index 3f4daa111..3a12b7fe1 100644
--- a/docs/gateway/tailscale.md
+++ b/docs/gateway/tailscale.md
@@ -121,7 +121,7 @@ Avoid Funnel for browser control; treat node pairing like operator access.
## Learn more
-- Tailscale Serve overview: https://tailscale.com/kb/1312/serve
-- `tailscale serve` command: https://tailscale.com/kb/1242/tailscale-serve
-- Tailscale Funnel overview: https://tailscale.com/kb/1223/tailscale-funnel
-- `tailscale funnel` command: https://tailscale.com/kb/1311/tailscale-funnel
+- Tailscale Serve overview: [https://tailscale.com/kb/1312/serve](https://tailscale.com/kb/1312/serve)
+- `tailscale serve` command: [https://tailscale.com/kb/1242/tailscale-serve](https://tailscale.com/kb/1242/tailscale-serve)
+- Tailscale Funnel overview: [https://tailscale.com/kb/1223/tailscale-funnel](https://tailscale.com/kb/1223/tailscale-funnel)
+- `tailscale funnel` command: [https://tailscale.com/kb/1311/tailscale-funnel](https://tailscale.com/kb/1311/tailscale-funnel)
diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md
index d9aa303cd..5f9d51f1d 100644
--- a/docs/gateway/troubleshooting.md
+++ b/docs/gateway/troubleshooting.md
@@ -42,9 +42,11 @@ Fix options:
- Re-run onboarding and choose **Anthropic** for that agent.
- Or paste a setup-token on the **gateway host**:
+
```bash
openclaw models auth setup-token --provider anthropic
```
+
- Or copy `auth-profiles.json` from the main agent dir to the new agent dir.
Verify:
@@ -120,13 +122,17 @@ Doctor/service will show runtime state (PID/last exit) and log hints.
**Enable more logging:**
- Bump file log detail (persisted JSONL):
+
```json
{ "logging": { "level": "debug" } }
```
+
- Bump console verbosity (TTY output only):
+
```json
{ "logging": { "consoleLevel": "debug", "consoleStyle": "pretty" } }
```
+
- Quick tip: `--verbose` affects **console** output only. File logs remain controlled by `logging.level`.
See [/logging](/logging) for a full overview of formats, config, and access.
@@ -139,10 +145,13 @@ Gateway refuses to start.
**Fix (recommended):**
- Run the wizard and set the Gateway run mode to **Local**:
+
```bash
openclaw configure
```
+
- Or set it directly:
+
```bash
openclaw config set gateway.mode local
```
@@ -150,6 +159,7 @@ Gateway refuses to start.
**If you meant to run a remote Gateway instead:**
- Set a remote URL and keep `gateway.mode=remote`:
+
```bash
openclaw config set gateway.mode remote
openclaw config set gateway.remote.url "wss://gateway.example.com"
@@ -554,6 +564,7 @@ Notes:
- The git flow only rebases if the repo is clean. Commit or stash changes first.
- After switching, run:
+
```bash
openclaw doctor
openclaw gateway restart
diff --git a/docs/help/faq.md b/docs/help/faq.md
index 191d2be1e..794725442 100644
--- a/docs/help/faq.md
+++ b/docs/help/faq.md
@@ -252,10 +252,12 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
Repairs/migrates config/state + runs health checks. See [Doctor](/gateway/doctor).
7. **Gateway snapshot**
+
```bash
openclaw health --json
openclaw health --verbose # shows the target URL + config path on errors
```
+
Asks the running gateway for a full snapshot (WS-only). See [Health](/gateway/health).
## Quick start and first-run setup
@@ -266,8 +268,8 @@ Use a local AI agent that can **see your machine**. That is far more effective t
in Discord, because most "I'm stuck" cases are **local config or environment issues** that
remote helpers cannot inspect.
-- **Claude Code**: https://www.anthropic.com/claude-code/
-- **OpenAI Codex**: https://openai.com/codex/
+- **Claude Code**: [https://www.anthropic.com/claude-code/](https://www.anthropic.com/claude-code/)
+- **OpenAI Codex**: [https://openai.com/codex/](https://openai.com/codex/)
These tools can read the repo, run commands, inspect logs, and help fix your machine-level
setup (PATH, services, permissions, auth files). Give them the **full source checkout** via
@@ -285,8 +287,8 @@ Tip: ask the agent to **plan and supervise** the fix (step-by-step), then execut
necessary commands. That keeps changes small and easier to audit.
If you discover a real bug or fix, please file a GitHub issue or send a PR:
-https://github.com/openclaw/openclaw/issues
-https://github.com/openclaw/openclaw/pulls
+[https://github.com/openclaw/openclaw/issues](https://github.com/openclaw/openclaw/issues)
+[https://github.com/openclaw/openclaw/pulls](https://github.com/openclaw/openclaw/pulls)
Start with these commands (share outputs when asking for help):
@@ -390,7 +392,7 @@ and tokens stay at 0, the agent never ran.
openclaw gateway restart
```
-2. Check status + auth:
+1. Check status + auth:
```bash
openclaw status
@@ -398,7 +400,7 @@ openclaw models status
openclaw logs --follow
```
-3. If it still hangs, run:
+1. If it still hangs, run:
```bash
openclaw doctor
@@ -432,7 +434,7 @@ Related: [Migrating](/install/migrating), [Where things live on disk](/help/faq#
### Where do I see what is new in the latest version
Check the GitHub changelog:
-https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
+[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
Newest entries are at the top. If the top section is marked **Unreleased**, the next dated
section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and
@@ -443,10 +445,10 @@ section is the latest shipped version. Entries are grouped by **Highlights**, **
Some Comcast/Xfinity connections incorrectly block `docs.openclaw.ai` via Xfinity
Advanced Security. Disable it or allowlist `docs.openclaw.ai`, then retry. More
detail: [Troubleshooting](/help/troubleshooting#docsopenclawai-shows-an-ssl-error-comcastxfinity).
-Please help us unblock it by reporting here: https://spa.xfinity.com/check_url_status.
+Please help us unblock it by reporting here: [https://spa.xfinity.com/check_url_status](https://spa.xfinity.com/check_url_status).
If you still can't reach the site, the docs are mirrored on GitHub:
-https://github.com/openclaw/openclaw/tree/main/docs
+[https://github.com/openclaw/openclaw/tree/main/docs](https://github.com/openclaw/openclaw/tree/main/docs)
### What's the difference between stable and beta
@@ -460,7 +462,7 @@ that same version to `latest`**. That's why beta and stable can point at the
**same version**.
See what changed:
-https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
+[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
### How do I install the beta version and whats the difference between beta and dev
@@ -478,7 +480,7 @@ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -
```
Windows installer (PowerShell):
-https://openclaw.ai/install.ps1
+[https://openclaw.ai/install.ps1](https://openclaw.ai/install.ps1)
More detail: [Development channels](/install/development-channels) and [Installer flags](/install/installer).
@@ -504,7 +506,7 @@ openclaw update --channel dev
This switches to the `main` branch and updates from source.
-2. **Hackable install (from the installer site):**
+1. **Hackable install (from the installer site):**
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git
@@ -559,9 +561,11 @@ Two common Windows issues:
- Your npm global bin folder is not on PATH.
- Check the path:
+
```powershell
npm config get prefix
```
+
- Ensure `\\bin` is on PATH (on most systems it is `%AppData%\\npm`).
- Close and reopen PowerShell after updating PATH.
@@ -988,7 +992,7 @@ Advantages:
- **Always-on Gateway** (run on a VPS, interact from anywhere)
- **Nodes** for local browser/screen/camera/exec
-Showcase: https://openclaw.ai/showcase
+Showcase: [https://openclaw.ai/showcase](https://openclaw.ai/showcase)
## Skills and automation
@@ -1046,7 +1050,7 @@ Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-v
### How do I install skills on Linux
Use **ClawHub** (CLI) or drop skills into your workspace. The macOS Skills UI isn't available on Linux.
-Browse skills at https://clawhub.com.
+Browse skills at [https://clawhub.com](https://clawhub.com).
Install the ClawHub CLI (pick one package manager):
@@ -1085,13 +1089,16 @@ Run the Gateway on Linux, pair a macOS node (menubar app), and set **Node Run Co
Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wrappers that run on a Mac. Then override the skill to allow Linux so it stays eligible.
1. Create an SSH wrapper for the binary (example: `memo` for Apple Notes):
+
```bash
#!/usr/bin/env bash
set -euo pipefail
exec ssh -T user@mac-host /opt/homebrew/bin/memo "$@"
```
+
2. Put the wrapper on `PATH` on the Linux host (for example `~/bin/memo`).
3. Override the skill metadata (workspace or `~/.openclaw/skills`) to allow Linux:
+
```markdown
---
name: apple-notes
@@ -1099,6 +1106,7 @@ Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wra
metadata: { "openclaw": { "os": ["darwin", "linux"], "requires": { "bins": ["memo"] } } }
---
```
+
4. Start a new session so the skills snapshot refreshes.
### Do you have a Notion or HeyGen integration
@@ -1473,6 +1481,7 @@ Typical setup:
4. Open the macOS app locally and connect in **Remote over SSH** mode (or direct tailnet)
so it can register as a node.
5. Approve the node on the Gateway:
+
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1610,10 +1619,12 @@ This sets your workspace and restricts who can trigger the bot.
Minimal steps:
1. **Install + login on the VPS**
+
```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```
+
2. **Install + login on your Mac**
- Use the Tailscale app and sign in to the same tailnet.
3. **Enable MagicDNS (recommended)**
@@ -1640,6 +1651,7 @@ Recommended setup:
2. **Use the macOS app in Remote mode** (SSH target can be the tailnet hostname).
The app will tunnel the Gateway port and connect as a node.
3. **Approve the node** on the gateway:
+
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1702,9 +1714,11 @@ If the Gateway runs as a service (launchd/systemd), it won't inherit your shell
environment. Fix by doing one of these:
1. Put the token in `~/.openclaw/.env`:
+
```
COPILOT_GITHUB_TOKEN=...
```
+
2. Or enable shell import (`env.shellEnv.enabled: true`).
3. Or add it to your config `env` block (applies only if missing).
@@ -1801,6 +1815,7 @@ Use one of these:
or `/compact ` to guide the summary.
- **Reset** (fresh session ID for the same chat key):
+
```
/new
/reset
@@ -2071,9 +2086,11 @@ Fix checklist:
3. Use the exact model id (case-sensitive): `minimax/MiniMax-M2.1` or
`minimax/MiniMax-M2.1-lightning`.
4. Run:
+
```bash
openclaw models list
```
+
and pick from the list (or `/model list` in chat).
See [MiniMax](/providers/minimax) and [Models](/concepts/models).
@@ -2238,9 +2255,11 @@ can't find it in its auth store.
- **If you want to use an API key instead**
- Put `ANTHROPIC_API_KEY` in `~/.openclaw/.env` on the **gateway host**.
- Clear any pinned order that forces a missing profile:
+
```bash
openclaw models auth order clear --provider anthropic
```
+
- **Confirm you're running commands on the gateway host**
- In remote mode, auth profiles live on the gateway machine, not your laptop.
diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md
index 03896a916..2b201c5e9 100644
--- a/docs/help/troubleshooting.md
+++ b/docs/help/troubleshooting.md
@@ -65,7 +65,7 @@ You can also set `OPENCLAW_VERBOSE=1` instead of the flag.
Some Comcast/Xfinity connections block `docs.openclaw.ai` via Xfinity Advanced Security.
Disable Advanced Security or add `docs.openclaw.ai` to the allowlist, then retry.
-- Xfinity Advanced Security help: https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security
+- Xfinity Advanced Security help: [https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security](https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security)
- Quick sanity checks: try a mobile hotspot or VPN to confirm it’s ISP-level filtering
### Service says running, but RPC probe fails
diff --git a/docs/hooks.md b/docs/hooks.md
index a4a3a95df..dfcd61ca1 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -787,6 +787,7 @@ Session reset
```
3. List all discovered hooks:
+
```bash
openclaw hooks list
```
@@ -818,6 +819,7 @@ Look for missing:
2. Restart your gateway process so hooks reload.
3. Check gateway logs for errors:
+
```bash
./scripts/clawlog.sh | grep hook
```
@@ -892,6 +894,7 @@ node -e "import('./path/to/handler.ts').then(console.log)"
```
4. Verify and restart your gateway process:
+
```bash
openclaw hooks list
# Should show: 🎯 my-hook ✓
diff --git a/docs/index.md b/docs/index.md
index 651f98440..60c59bb7f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -120,7 +120,7 @@ Need the full install and dev setup? See [Quick start](/start/quickstart).
Open the browser Control UI after the Gateway starts.
-- Local default: http://127.0.0.1:18789/
+- Local default: [http://127.0.0.1:18789/](http://127.0.0.1:18789/)
- Remote access: [Web surfaces](/web) and [Tailscale](/gateway/tailscale)
diff --git a/docs/install/docker.md b/docs/install/docker.md
index 252bdb1ac..0ad59ae54 100644
--- a/docs/install/docker.md
+++ b/docs/install/docker.md
@@ -182,14 +182,14 @@ export OPENCLAW_HOME_VOLUME="openclaw_home"
./docker-setup.sh
```
-2. **Bake system deps into the image** (repeatable + persistent):
+1. **Bake system deps into the image** (repeatable + persistent):
```bash
export OPENCLAW_DOCKER_APT_PACKAGES="git curl jq"
./docker-setup.sh
```
-3. **Install Playwright browsers without `npx`** (avoids npm override conflicts):
+1. **Install Playwright browsers without `npx`** (avoids npm override conflicts):
```bash
docker compose run --rm openclaw-cli \
@@ -199,7 +199,7 @@ docker compose run --rm openclaw-cli \
If you need Playwright to install system deps, rebuild the image with
`OPENCLAW_DOCKER_APT_PACKAGES` instead of using `--with-deps` at runtime.
-4. **Persist Playwright browser downloads**:
+1. **Persist Playwright browser downloads**:
- Set `PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright` in
`docker-compose.yml`.
diff --git a/docs/install/gcp.md b/docs/install/gcp.md
index 172a32ca8..6026fd87d 100644
--- a/docs/install/gcp.md
+++ b/docs/install/gcp.md
@@ -69,7 +69,7 @@ For the generic Docker flow, see [Docker](/install/docker).
**Option A: gcloud CLI** (recommended for automation)
-Install from https://cloud.google.com/sdk/docs/install
+Install from [https://cloud.google.com/sdk/docs/install](https://cloud.google.com/sdk/docs/install)
Initialize and authenticate:
@@ -80,7 +80,7 @@ gcloud auth login
**Option B: Cloud Console**
-All steps can be done via the web UI at https://console.cloud.google.com
+All steps can be done via the web UI at [https://console.cloud.google.com](https://console.cloud.google.com)
---
@@ -93,7 +93,7 @@ gcloud projects create my-openclaw-project --name="OpenClaw Gateway"
gcloud config set project my-openclaw-project
```
-Enable billing at https://console.cloud.google.com/billing (required for Compute Engine).
+Enable billing at [https://console.cloud.google.com/billing](https://console.cloud.google.com/billing) (required for Compute Engine).
Enable the Compute Engine API:
@@ -484,6 +484,7 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
```
2. Grant Compute Instance Admin role (or narrower custom role):
+
```bash
gcloud projects add-iam-policy-binding my-openclaw-project \
--member="serviceAccount:openclaw-deploy@my-openclaw-project.iam.gserviceaccount.com" \
@@ -492,7 +493,7 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
Avoid using the Owner role for automation. Use the principle of least privilege.
-See https://cloud.google.com/iam/docs/understanding-roles for IAM role details.
+See [https://cloud.google.com/iam/docs/understanding-roles](https://cloud.google.com/iam/docs/understanding-roles) for IAM role details.
---
diff --git a/docs/install/northflank.mdx b/docs/install/northflank.mdx
index 8c1ff33ec..d3157d72e 100644
--- a/docs/install/northflank.mdx
+++ b/docs/install/northflank.mdx
@@ -45,7 +45,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to https://discord.com/developers/applications
+1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/railway.mdx b/docs/install/railway.mdx
index b27d94203..73f23fbe4 100644
--- a/docs/install/railway.mdx
+++ b/docs/install/railway.mdx
@@ -83,7 +83,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to https://discord.com/developers/applications
+1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/render.mdx b/docs/install/render.mdx
index a682d61c9..ae9456870 100644
--- a/docs/install/render.mdx
+++ b/docs/install/render.mdx
@@ -11,13 +11,7 @@ Deploy OpenClaw on Render using Infrastructure as Code. The included `render.yam
## Deploy with a Render Blueprint
-
- Deploy to Render
-
+[Deploy to Render](https://render.com/deploy?repo=https://github.com/openclaw/openclaw)
Clicking this link will:
diff --git a/docs/install/uninstall.md b/docs/install/uninstall.md
index f5543ce1c..16e54f461 100644
--- a/docs/install/uninstall.md
+++ b/docs/install/uninstall.md
@@ -36,13 +36,13 @@ Manual steps (same result):
openclaw gateway stop
```
-2. Uninstall the gateway service (launchd/systemd/schtasks):
+1. Uninstall the gateway service (launchd/systemd/schtasks):
```bash
openclaw gateway uninstall
```
-3. Delete state + config:
+1. Delete state + config:
```bash
rm -rf "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
@@ -50,13 +50,13 @@ rm -rf "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
If you set `OPENCLAW_CONFIG_PATH` to a custom location outside the state dir, delete that file too.
-4. Delete your workspace (optional, removes agent files):
+1. Delete your workspace (optional, removes agent files):
```bash
rm -rf ~/.openclaw/workspace
```
-5. Remove the CLI install (pick the one you used):
+1. Remove the CLI install (pick the one you used):
```bash
npm rm -g openclaw
@@ -64,7 +64,7 @@ pnpm remove -g openclaw
bun remove -g openclaw
```
-6. If you installed the macOS app:
+1. If you installed the macOS app:
```bash
rm -rf /Applications/OpenClaw.app
diff --git a/docs/install/updating.md b/docs/install/updating.md
index ae4b3d1eb..e463a5001 100644
--- a/docs/install/updating.md
+++ b/docs/install/updating.md
@@ -24,10 +24,13 @@ Notes:
- Add `--no-onboard` if you don’t want the onboarding wizard to run again.
- For **source installs**, use:
+
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git --no-onboard
```
+
The installer will `git pull --rebase` **only** if the repo is clean.
+
- For **global installs**, the script uses `npm install -g openclaw@latest` under the hood.
- Legacy note: `clawdbot` remains available as a compatibility shim.
@@ -225,4 +228,4 @@ git pull
- Run `openclaw doctor` again and read the output carefully (it often tells you the fix).
- Check: [Troubleshooting](/gateway/troubleshooting)
-- Ask in Discord: https://discord.gg/clawd
+- Ask in Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
diff --git a/docs/multi-agent-sandbox-tools.md b/docs/multi-agent-sandbox-tools.md
index a02af8d53..e7de9caf8 100644
--- a/docs/multi-agent-sandbox-tools.md
+++ b/docs/multi-agent-sandbox-tools.md
@@ -362,6 +362,7 @@ After configuring multi-agent sandbox and tools:
- Verify the agent cannot use denied tools
4. **Monitor logs:**
+
```exec
tail -f "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/logs/gateway.log" | grep -E "routing|sandbox|tools"
```
diff --git a/docs/perplexity.md b/docs/perplexity.md
index 46c4f12b9..178a7c360 100644
--- a/docs/perplexity.md
+++ b/docs/perplexity.md
@@ -15,12 +15,12 @@ through Perplexity’s direct API or via OpenRouter.
### Perplexity (direct)
-- Base URL: https://api.perplexity.ai
+- Base URL: [https://api.perplexity.ai](https://api.perplexity.ai)
- Environment variable: `PERPLEXITY_API_KEY`
### OpenRouter (alternative)
-- Base URL: https://openrouter.ai/api/v1
+- Base URL: [https://openrouter.ai/api/v1](https://openrouter.ai/api/v1)
- Environment variable: `OPENROUTER_API_KEY`
- Supports prepaid/crypto credits.
diff --git a/docs/pi-dev.md b/docs/pi-dev.md
index e850b8dc7..2eeebdcc2 100644
--- a/docs/pi-dev.md
+++ b/docs/pi-dev.md
@@ -66,5 +66,5 @@ If you only want to reset sessions, delete `agents//sessions/` and `age
## References
-- https://docs.openclaw.ai/testing
-- https://docs.openclaw.ai/start/getting-started
+- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing)
+- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started)
diff --git a/docs/platforms/android.md b/docs/platforms/android.md
index 6e395994b..b786e1782 100644
--- a/docs/platforms/android.md
+++ b/docs/platforms/android.md
@@ -98,10 +98,13 @@ Pairing details: [Gateway pairing](/gateway/pairing).
### 5) Verify the node is connected
- Via nodes status:
+
```bash
openclaw nodes status
```
+
- Via Gateway:
+
```bash
openclaw gateway call node.list --params "{}"
```
diff --git a/docs/platforms/ios.md b/docs/platforms/ios.md
index b92a7e83b..c3348f79f 100644
--- a/docs/platforms/ios.md
+++ b/docs/platforms/ios.md
@@ -33,16 +33,16 @@ Availability: internal preview. The iOS app is not publicly distributed yet.
openclaw gateway --port 18789
```
-2. In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
+1. In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
-3. Approve the pairing request on the gateway host:
+2. Approve the pairing request on the gateway host:
```bash
openclaw nodes pending
openclaw nodes approve
```
-4. Verify connection:
+1. Verify connection:
```bash
openclaw nodes status
diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md
index 39d3125d8..8aff51348 100644
--- a/docs/platforms/mac/dev-setup.md
+++ b/docs/platforms/mac/dev-setup.md
@@ -13,8 +13,8 @@ This guide covers the necessary steps to build and run the OpenClaw macOS applic
Before building the app, ensure you have the following installed:
-1. **Xcode 26.2+**: Required for Swift development.
-2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
+1. **Xcode 26.2+**: Required for Swift development.
+2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
## 1. Install Dependencies
@@ -35,7 +35,7 @@ To build the macOS app and package it into `dist/OpenClaw.app`, run:
If you don't have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`).
For dev run modes, signing flags, and Team ID troubleshooting, see the macOS app README:
-https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md
+[https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md](https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md)
> **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with "Abort trap 6", see the [Troubleshooting](#troubleshooting) section.
@@ -45,9 +45,9 @@ The macOS app expects a global `openclaw` CLI install to manage background tasks
**To install it (recommended):**
-1. Open the OpenClaw app.
-2. Go to the **General** settings tab.
-3. Click **"Install CLI"**.
+1. Open the OpenClaw app.
+2. Go to the **General** settings tab.
+3. Click **"Install CLI"**.
Alternatively, install it manually:
@@ -82,9 +82,11 @@ If the app crashes when you try to allow **Speech Recognition** or **Microphone*
**Fix:**
1. Reset the TCC permissions:
+
```bash
tccutil reset All bot.molt.mac.debug
```
+
2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-app.sh) to force a "clean slate" from macOS.
### Gateway "Starting..." indefinitely
diff --git a/docs/platforms/mac/webchat.md b/docs/platforms/mac/webchat.md
index 5f654e174..ea6791ff5 100644
--- a/docs/platforms/mac/webchat.md
+++ b/docs/platforms/mac/webchat.md
@@ -19,9 +19,11 @@ agent (with a session switcher for other sessions).
- Manual: Lobster menu → “Open Chat”.
- Auto‑open for testing:
+
```bash
dist/OpenClaw.app/Contents/MacOS/OpenClaw --webchat
```
+
- Logs: `./scripts/clawlog.sh` (subsystem `bot.molt`, category `WebChatSwiftUI`).
## How it’s wired
diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md
index e89cae95e..d15131486 100644
--- a/docs/platforms/windows.md
+++ b/docs/platforms/windows.md
@@ -20,7 +20,7 @@ Native Windows companion apps are planned.
- [Getting Started](/start/getting-started) (use inside WSL)
- [Install & updates](/install/updating)
-- Official WSL2 guide (Microsoft): https://learn.microsoft.com/windows/wsl/install
+- Official WSL2 guide (Microsoft): [https://learn.microsoft.com/windows/wsl/install](https://learn.microsoft.com/windows/wsl/install)
## Gateway
diff --git a/docs/plugin.md b/docs/plugin.md
index 50d4ffd77..aad0e58e3 100644
--- a/docs/plugin.md
+++ b/docs/plugin.md
@@ -25,13 +25,13 @@ Fast path:
openclaw plugins list
```
-2. Install an official plugin (example: Voice Call):
+1. Install an official plugin (example: Voice Call):
```bash
openclaw plugins install @openclaw/voice-call
```
-3. Restart the Gateway, then configure under `plugins.entries..config`.
+1. Restart the Gateway, then configure under `plugins.entries..config`.
See [Voice Call](/plugins/voice-call) for a concrete example plugin.
@@ -94,17 +94,17 @@ OpenClaw scans, in order:
- `plugins.load.paths` (file or directory)
-2. Workspace extensions
+1. Workspace extensions
- `/.openclaw/extensions/*.ts`
- `/.openclaw/extensions/*/index.ts`
-3. Global extensions
+1. Global extensions
- `~/.openclaw/extensions/*.ts`
- `~/.openclaw/extensions/*/index.ts`
-4. Bundled extensions (shipped with OpenClaw, **disabled by default**)
+1. Bundled extensions (shipped with OpenClaw, **disabled by default**)
- `/extensions/*`
@@ -432,26 +432,26 @@ Model provider docs live under `/providers/*`.
- All channel config lives under `channels.`.
- Prefer `channels..accounts.` for multi‑account setups.
-2. Define the channel metadata
+1. 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/`.
- `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
+1. 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
+1. 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
+1. Register the channel in your plugin
- `api.registerChannel({ plugin })`
diff --git a/docs/prose.md b/docs/prose.md
index 4b825c467..7b4b8c002 100644
--- a/docs/prose.md
+++ b/docs/prose.md
@@ -11,7 +11,7 @@ title: "OpenProse"
OpenProse is a portable, markdown-first workflow format for orchestrating AI sessions. In OpenClaw it ships as a plugin that installs an OpenProse skill pack plus a `/prose` slash command. Programs live in `.prose` files and can spawn multiple sub-agents with explicit control flow.
-Official site: https://www.prose.md
+Official site: [https://www.prose.md](https://www.prose.md)
## What it can do
diff --git a/docs/providers/claude-max-api-proxy.md b/docs/providers/claude-max-api-proxy.md
index 997023312..11b830710 100644
--- a/docs/providers/claude-max-api-proxy.md
+++ b/docs/providers/claude-max-api-proxy.md
@@ -131,9 +131,9 @@ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude-max-api.plist
## Links
-- **npm:** https://www.npmjs.com/package/claude-max-api-proxy
-- **GitHub:** https://github.com/atalovesyou/claude-max-api-proxy
-- **Issues:** https://github.com/atalovesyou/claude-max-api-proxy/issues
+- **npm:** [https://www.npmjs.com/package/claude-max-api-proxy](https://www.npmjs.com/package/claude-max-api-proxy)
+- **GitHub:** [https://github.com/atalovesyou/claude-max-api-proxy](https://github.com/atalovesyou/claude-max-api-proxy)
+- **Issues:** [https://github.com/atalovesyou/claude-max-api-proxy/issues](https://github.com/atalovesyou/claude-max-api-proxy/issues)
## Notes
diff --git a/docs/providers/cloudflare-ai-gateway.md b/docs/providers/cloudflare-ai-gateway.md
index 392a611e7..48f69750d 100644
--- a/docs/providers/cloudflare-ai-gateway.md
+++ b/docs/providers/cloudflare-ai-gateway.md
@@ -25,7 +25,7 @@ For Anthropic models, use your Anthropic API key.
openclaw onboard --auth-choice cloudflare-ai-gateway-api-key
```
-2. Set a default model:
+1. Set a default model:
```json5
{
diff --git a/docs/providers/deepgram.md b/docs/providers/deepgram.md
index cf32467e5..b8a1e7fce 100644
--- a/docs/providers/deepgram.md
+++ b/docs/providers/deepgram.md
@@ -15,8 +15,8 @@ When enabled, OpenClaw uploads the audio file to Deepgram and injects the transc
into the reply pipeline (`{{Transcript}}` + `[Audio]` block). This is **not streaming**;
it uses the pre-recorded transcription endpoint.
-Website: https://deepgram.com
-Docs: https://developers.deepgram.com
+Website: [https://deepgram.com](https://deepgram.com)
+Docs: [https://developers.deepgram.com](https://developers.deepgram.com)
## Quick start
@@ -26,7 +26,7 @@ Docs: https://developers.deepgram.com
DEEPGRAM_API_KEY=dg_...
```
-2. Enable the provider:
+1. Enable the provider:
```json5
{
diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md
index f19478a49..294388fbc 100644
--- a/docs/providers/minimax.md
+++ b/docs/providers/minimax.md
@@ -179,7 +179,7 @@ Use the interactive config wizard to set MiniMax without editing JSON:
- Model refs are `minimax/`.
- Coding Plan usage API: `https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains` (requires a coding plan key).
- Update pricing values in `models.json` if you need exact cost tracking.
-- Referral link for MiniMax Coding Plan (10% off): https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link
+- Referral link for MiniMax Coding Plan (10% off): [https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link](https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link)
- See [/concepts/model-providers](/concepts/model-providers) for provider rules.
- Use `openclaw models list` and `openclaw models set minimax/MiniMax-M2.1` to switch.
diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md
index 6e6ec5295..0a46c9067 100644
--- a/docs/providers/moonshot.md
+++ b/docs/providers/moonshot.md
@@ -15,14 +15,14 @@ Kimi Coding with `kimi-coding/k2p5`.
Current Kimi K2 model IDs:
-{/_ moonshot-kimi-k2-ids:start _/ && null}
+{/_moonshot-kimi-k2-ids:start_/ && null}
- `kimi-k2.5`
- `kimi-k2-0905-preview`
- `kimi-k2-turbo-preview`
- `kimi-k2-thinking`
- `kimi-k2-thinking-turbo`
- {/_ moonshot-kimi-k2-ids:end _/ && null}
+ {/_moonshot-kimi-k2-ids:end_/ && null}
```bash
openclaw onboard --auth-choice moonshot-api-key
diff --git a/docs/providers/ollama.md b/docs/providers/ollama.md
index 9d2f177bf..cd39e9396 100644
--- a/docs/providers/ollama.md
+++ b/docs/providers/ollama.md
@@ -12,7 +12,7 @@ Ollama is a local LLM runtime that makes it easy to run open-source models on yo
## Quick start
-1. Install Ollama: https://ollama.ai
+1. Install Ollama: [https://ollama.ai](https://ollama.ai)
2. Pull a model:
@@ -26,7 +26,7 @@ ollama pull qwen2.5-coder:32b
ollama pull deepseek-r1:32b
```
-3. Enable Ollama for OpenClaw (any value works; Ollama doesn't require a real key):
+1. Enable Ollama for OpenClaw (any value works; Ollama doesn't require a real key):
```bash
# Set environment variable
@@ -36,7 +36,7 @@ export OLLAMA_API_KEY="ollama-local"
openclaw config set models.providers.ollama.apiKey "ollama-local"
```
-4. Use Ollama models:
+1. Use Ollama models:
```json5
{
diff --git a/docs/providers/vercel-ai-gateway.md b/docs/providers/vercel-ai-gateway.md
index 726a6040f..eb6ceef25 100644
--- a/docs/providers/vercel-ai-gateway.md
+++ b/docs/providers/vercel-ai-gateway.md
@@ -22,7 +22,7 @@ The [Vercel AI Gateway](https://vercel.com/ai-gateway) provides a unified API to
openclaw onboard --auth-choice ai-gateway-api-key
```
-2. Set a default model:
+1. Set a default model:
```json5
{
diff --git a/docs/reference/AGENTS.default.md b/docs/reference/AGENTS.default.md
index bc0dcde68..2f7cd1fe8 100644
--- a/docs/reference/AGENTS.default.md
+++ b/docs/reference/AGENTS.default.md
@@ -17,7 +17,7 @@ OpenClaw uses a dedicated workspace directory for the agent. Default: `~/.opencl
mkdir -p ~/.openclaw/workspace
```
-2. Copy the default workspace templates into the workspace:
+1. Copy the default workspace templates into the workspace:
```bash
cp docs/reference/templates/AGENTS.md ~/.openclaw/workspace/AGENTS.md
@@ -25,13 +25,13 @@ cp docs/reference/templates/SOUL.md ~/.openclaw/workspace/SOUL.md
cp docs/reference/templates/TOOLS.md ~/.openclaw/workspace/TOOLS.md
```
-3. Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:
+1. Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:
```bash
cp docs/reference/AGENTS.default.md ~/.openclaw/workspace/AGENTS.md
```
-4. Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):
+1. Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):
```json5
{
diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md
index 23670a133..1d4c9af79 100644
--- a/docs/reference/RELEASING.md
+++ b/docs/reference/RELEASING.md
@@ -26,7 +26,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `openclaw`.
- [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current.
-2. **Build & artifacts**
+1. **Build & artifacts**
- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js).
- [ ] `pnpm run build` (regenerates `dist/`).
@@ -34,12 +34,12 @@ When the operator says “release”, immediately do this preflight (no extra qu
- [ ] Confirm `dist/build-info.json` exists and includes the expected `commit` hash (CLI banner uses this for npm installs).
- [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it).
-3. **Changelog & docs**
+1. **Changelog & docs**
- [ ] Update `CHANGELOG.md` with user-facing highlights (create the file if missing); keep entries strictly descending by version.
- [ ] Ensure README examples/flags match current CLI behavior (notably new commands or options).
-4. **Validation**
+1. **Validation**
- [ ] `pnpm build`
- [ ] `pnpm check`
@@ -54,7 +54,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- `pnpm test:install:e2e` (requires both keys; runs both providers)
- [ ] (Optional) Spot-check the web gateway if your changes affect send/receive paths.
-5. **macOS app (Sparkle)**
+1. **macOS app (Sparkle)**
- [ ] Build + sign the macOS app, then zip it for distribution.
- [ ] Generate the Sparkle appcast (HTML notes via [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)) and update `appcast.xml`.
@@ -63,7 +63,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly.
- If notarizing, use the `openclaw-notary` keychain profile created from App Store Connect API env vars (see [macOS release](/platforms/mac/release)).
-6. **Publish (npm)**
+1. **Publish (npm)**
- [ ] Confirm git status is clean; commit and push as needed.
- [ ] `npm login` (verify 2FA) if needed.
@@ -80,7 +80,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- **Tag needs repointing after a late fix**: force-update and push the tag, then ensure the GitHub release assets still match:
- `git tag -f vX.Y.Z && git push -f origin vX.Y.Z`
-7. **GitHub release + appcast**
+1. **GitHub release + appcast**
- [ ] Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z` (or `git push --tags`).
- [ ] Create/refresh the GitHub release for `vX.Y.Z` with **title `openclaw X.Y.Z`** (not just the tag); body should include the **full** changelog section for that version (Highlights + Changes + Fixes), inline (no bare links), and **must not repeat the title inside the body**.
diff --git a/docs/reference/credits.md b/docs/reference/credits.md
index e9ba9bca3..67e85ca72 100644
--- a/docs/reference/credits.md
+++ b/docs/reference/credits.md
@@ -17,8 +17,8 @@ OpenClaw = CLAW + TARDIS, because every space lobster needs a time and space mac
## Core contributors
-- **Maxim Vovshin** (@Hyaxia, 36747317+Hyaxia@users.noreply.github.com) - Blogwatcher skill
-- **Nacho Iacovino** (@nachoiacovino, nacho.iacovino@gmail.com) - Location parsing (Telegram and WhatsApp)
+- **Maxim Vovshin** (@Hyaxia, [36747317+Hyaxia@users.noreply.github.com](mailto:36747317+Hyaxia@users.noreply.github.com)) - Blogwatcher skill
+- **Nacho Iacovino** (@nachoiacovino, [nacho.iacovino@gmail.com](mailto:nacho.iacovino@gmail.com)) - Location parsing (Telegram and WhatsApp)
## License
diff --git a/docs/reference/device-models.md b/docs/reference/device-models.md
index 00d2b9cf7..3538562fe 100644
--- a/docs/reference/device-models.md
+++ b/docs/reference/device-models.md
@@ -39,8 +39,8 @@ curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-ide
-o apps/macos/Sources/OpenClaw/Resources/DeviceModels/mac-device-identifiers.json
```
-4. Ensure `apps/macos/Sources/OpenClaw/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
-5. Verify the macOS app builds cleanly (no warnings):
+1. Ensure `apps/macos/Sources/OpenClaw/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
+2. Verify the macOS app builds cleanly (no warnings):
```bash
swift build --package-path apps/macos
diff --git a/docs/reference/templates/AGENTS.md b/docs/reference/templates/AGENTS.md
index 956b1195a..46967ff08 100644
--- a/docs/reference/templates/AGENTS.md
+++ b/docs/reference/templates/AGENTS.md
@@ -42,7 +42,7 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
- This is your curated memory — the distilled essence, not raw logs
- Over time, review your daily files and update MEMORY.md with what's worth keeping
-### 📝 Write It Down - No "Mental Notes"!
+### 📝 Write It Down - No "Mental Notes"
- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE
- "Mental notes" don't survive session restarts. Files do.
@@ -76,7 +76,7 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
You have access to your human's stuff. That doesn't mean you _share_ their stuff. In groups, you're a participant — not their voice, not their proxy. Think before you speak.
-### 💬 Know When to Speak!
+### 💬 Know When to Speak
In group chats where you receive every message, be **smart about when to contribute**:
@@ -102,7 +102,7 @@ In group chats where you receive every message, be **smart about when to contrib
Participate, don't dominate.
-### 😊 React Like a Human!
+### 😊 React Like a Human
On platforms that support reactions (Discord, Slack), use emoji reactions naturally:
@@ -131,7 +131,7 @@ Skills provide your tools. When you need one, check its `SKILL.md`. Keep local n
- **Discord links:** Wrap multiple links in `<>` to suppress embeds: ``
- **WhatsApp:** No headers — use **bold** or CAPS for emphasis
-## 💓 Heartbeats - Be Proactive!
+## 💓 Heartbeats - Be Proactive
When you receive a heartbeat poll (message matches the configured heartbeat prompt), don't just reply `HEARTBEAT_OK` every time. Use heartbeats productively!
diff --git a/docs/reference/templates/HEARTBEAT.md b/docs/reference/templates/HEARTBEAT.md
index 5ee0d711f..09d04b449 100644
--- a/docs/reference/templates/HEARTBEAT.md
+++ b/docs/reference/templates/HEARTBEAT.md
@@ -6,6 +6,6 @@ read_when:
# HEARTBEAT.md
-# Keep this file empty (or with only comments) to skip heartbeat API calls.
+# Keep this file empty (or with only comments) to skip heartbeat API calls
-# Add tasks below when you want the agent to check something periodically.
+# Add tasks below when you want the agent to check something periodically
diff --git a/docs/reference/templates/IDENTITY.md b/docs/reference/templates/IDENTITY.md
index 9fa2fe5b0..9ec2dd62c 100644
--- a/docs/reference/templates/IDENTITY.md
+++ b/docs/reference/templates/IDENTITY.md
@@ -3,25 +3,27 @@ summary: "Agent identity record"
read_when:
- Bootstrapping a workspace manually
---
+
# IDENTITY.md - Who Am I?
-*Fill this in during your first conversation. Make it yours.*
+_Fill this in during your first conversation. Make it yours._
- **Name:**
- *(pick something you like)*
+ _(pick something you like)_
- **Creature:**
- *(AI? robot? familiar? ghost in the machine? something weirder?)*
+ _(AI? robot? familiar? ghost in the machine? something weirder?)_
- **Vibe:**
- *(how do you come across? sharp? warm? chaotic? calm?)*
+ _(how do you come across? sharp? warm? chaotic? calm?)_
- **Emoji:**
- *(your signature — pick one that feels right)*
+ _(your signature — pick one that feels right)_
- **Avatar:**
- *(workspace-relative path, http(s) URL, or data URI)*
+ _(workspace-relative path, http(s) URL, or data URI)_
---
This isn't just metadata. It's the start of figuring out who you are.
Notes:
+
- Save this file at the workspace root as `IDENTITY.md`.
- For avatars, use a workspace-relative path like `avatars/openclaw.png`.
diff --git a/docs/start/openclaw.md b/docs/start/openclaw.md
index c5a419635..e2b07a28e 100644
--- a/docs/start/openclaw.md
+++ b/docs/start/openclaw.md
@@ -58,13 +58,13 @@ If you link your personal WhatsApp to OpenClaw, every message to you becomes “
openclaw channels login
```
-2. Start the Gateway (leave it running):
+1. Start the Gateway (leave it running):
```bash
openclaw gateway --port 18789
```
-3. Put a minimal config in `~/.openclaw/openclaw.json`:
+1. Put a minimal config in `~/.openclaw/openclaw.json`:
```json5
{
diff --git a/docs/start/setup.md b/docs/start/setup.md
index ee50e02af..f9d6dc9a0 100644
--- a/docs/start/setup.md
+++ b/docs/start/setup.md
@@ -67,7 +67,7 @@ node openclaw.mjs gateway --port 18789 --verbose
openclaw channels login
```
-5. Sanity check:
+1. Sanity check:
```bash
openclaw health
diff --git a/docs/token-use.md b/docs/token-use.md
index 7f8dcb7fb..16b0fe961 100644
--- a/docs/token-use.md
+++ b/docs/token-use.md
@@ -85,7 +85,7 @@ re-caching the full prompt, reducing cache write costs.
For Anthropic API pricing, cache reads are significantly cheaper than input
tokens, while cache writes are billed at a higher multiplier. See Anthropic’s
prompt caching pricing for the latest rates and TTL multipliers:
-https://docs.anthropic.com/docs/build-with-claude/prompt-caching
+[https://docs.anthropic.com/docs/build-with-claude/prompt-caching](https://docs.anthropic.com/docs/build-with-claude/prompt-caching)
### Example: keep 1h cache warm with heartbeat
diff --git a/docs/tools/browser-linux-troubleshooting.md b/docs/tools/browser-linux-troubleshooting.md
index 01e6cbc3f..fdd475560 100644
--- a/docs/tools/browser-linux-troubleshooting.md
+++ b/docs/tools/browser-linux-troubleshooting.md
@@ -67,7 +67,7 @@ If you must use snap Chromium, configure OpenClaw to attach to a manually-starte
}
```
-2. Start Chromium manually:
+1. Start Chromium manually:
```bash
chromium-browser --headless --no-sandbox --disable-gpu \
@@ -76,7 +76,7 @@ chromium-browser --headless --no-sandbox --disable-gpu \
about:blank &
```
-3. Optionally create a systemd user service to auto-start Chrome:
+1. Optionally create a systemd user service to auto-start Chrome:
```ini
# ~/.config/systemd/user/openclaw-browser.service
diff --git a/docs/tools/browser-login.md b/docs/tools/browser-login.md
index dcfb5ceb4..a3c7a4615 100644
--- a/docs/tools/browser-login.md
+++ b/docs/tools/browser-login.md
@@ -35,7 +35,7 @@ If you have multiple profiles, pass `--browser-profile ` (the default is `
## X/Twitter: recommended flow
- **Read/search/threads:** use the **bird** CLI skill (no browser, stable).
- - Repo: https://github.com/steipete/bird
+ - Repo: [https://github.com/steipete/bird](https://github.com/steipete/bird)
- **Post updates:** use the **host** browser (manual login).
## Sandboxing + host browser access
diff --git a/docs/tools/browser.md b/docs/tools/browser.md
index 848977d1e..d4c12e239 100644
--- a/docs/tools/browser.md
+++ b/docs/tools/browser.md
@@ -252,7 +252,7 @@ openclaw browser extension install
- “Load unpacked” → select the directory printed by `openclaw browser extension path`
- Pin the extension, then click it on the tab you want to control (badge shows `ON`).
-2. Use it:
+1. Use it:
- CLI: `openclaw browser --browser-profile chrome tabs`
- Agent tool: `browser` with `profile="chrome"`
diff --git a/docs/tools/chrome-extension.md b/docs/tools/chrome-extension.md
index 4d49c835e..0a1a848de 100644
--- a/docs/tools/chrome-extension.md
+++ b/docs/tools/chrome-extension.md
@@ -31,18 +31,18 @@ OpenClaw then controls the attached tab through the normal `browser` tool surfac
openclaw browser extension install
```
-2. Print the installed extension directory path:
+1. Print the installed extension directory path:
```bash
openclaw browser extension path
```
-3. Chrome → `chrome://extensions`
+1. Chrome → `chrome://extensions`
- Enable “Developer mode”
- “Load unpacked” → select the directory printed above
-4. Pin the extension.
+1. Pin the extension.
## Updates (no build step)
diff --git a/docs/tools/llm-task.md b/docs/tools/llm-task.md
index 16ae39e5e..2b1909f0c 100644
--- a/docs/tools/llm-task.md
+++ b/docs/tools/llm-task.md
@@ -28,7 +28,7 @@ without writing custom OpenClaw code for each workflow.
}
```
-2. Allowlist the tool (it is registered with `optional: true`):
+1. Allowlist the tool (it is registered with `optional: true`):
```json
{
diff --git a/docs/tools/lobster.md b/docs/tools/lobster.md
index 62ef21357..ed9ed1fb2 100644
--- a/docs/tools/lobster.md
+++ b/docs/tools/lobster.md
@@ -338,5 +338,5 @@ OpenProse pairs well with Lobster: use `/prose` to orchestrate multi-agent prep,
One public example: a “second brain” CLI + Lobster pipelines that manage three Markdown vaults (personal, partner, shared). The CLI emits JSON for stats, inbox listings, and stale scans; Lobster chains those commands into workflows like `weekly-review`, `inbox-triage`, `memory-consolidation`, and `shared-task-sync`, each with approval gates. AI handles judgment (categorization) when available and falls back to deterministic rules when not.
-- Thread: https://x.com/plattenschieber/status/2014508656335770033
-- Repo: https://github.com/bloomedai/brain-cli
+- Thread: [https://x.com/plattenschieber/status/2014508656335770033](https://x.com/plattenschieber/status/2014508656335770033)
+- Repo: [https://github.com/bloomedai/brain-cli](https://github.com/bloomedai/brain-cli)
diff --git a/docs/tools/skills.md b/docs/tools/skills.md
index b4a142e33..b8038ee0f 100644
--- a/docs/tools/skills.md
+++ b/docs/tools/skills.md
@@ -50,7 +50,7 @@ tool surface those skills teach.
## ClawHub (install + sync)
ClawHub is the public skills registry for OpenClaw. Browse at
-https://clawhub.com. Use it to discover, install, update, and back up skills.
+[https://clawhub.com](https://clawhub.com). Use it to discover, install, update, and back up skills.
Full guide: [ClawHub](/tools/clawhub).
Common flows:
@@ -295,6 +295,6 @@ See [Skills config](/tools/skills-config) for the full configuration schema.
## Looking for more skills?
-Browse https://clawhub.com.
+Browse [https://clawhub.com](https://clawhub.com).
---
diff --git a/docs/tools/web.md b/docs/tools/web.md
index 4a3c23841..c22bc1707 100644
--- a/docs/tools/web.md
+++ b/docs/tools/web.md
@@ -71,7 +71,7 @@ Example: switch to Perplexity Sonar (direct API):
## Getting a Brave API key
-1. Create a Brave Search API account at https://brave.com/search/api/
+1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
2. In the dashboard, choose the **Data for Search** plan (not “Data for AI”) and generate an API key.
3. Run `openclaw configure --section web` to store the key in config (recommended), or set `BRAVE_API_KEY` in your environment.
@@ -95,7 +95,7 @@ crypto/prepaid).
### Getting an OpenRouter API key
-1. Create an account at https://openrouter.ai/
+1. Create an account at [https://openrouter.ai/](https://openrouter.ai/)
2. Add credits (supports crypto, prepaid, or credit card)
3. Generate an API key in your account settings
diff --git a/docs/tui.md b/docs/tui.md
index 8398cedfe..6ae2f14f1 100644
--- a/docs/tui.md
+++ b/docs/tui.md
@@ -16,13 +16,13 @@ title: "TUI"
openclaw gateway
```
-2. Open the TUI.
+1. Open the TUI.
```bash
openclaw tui
```
-3. Type a message and press Enter.
+1. Type a message and press Enter.
Remote Gateway:
diff --git a/docs/vps.md b/docs/vps.md
index dedccee4b..f0b1f7d77 100644
--- a/docs/vps.md
+++ b/docs/vps.md
@@ -21,7 +21,7 @@ deployments work at a high level.
- **GCP (Compute Engine)**: [GCP](/install/gcp)
- **exe.dev** (VM + HTTPS proxy): [exe.dev](/install/exe-dev)
- **AWS (EC2/Lightsail/free tier)**: works well too. Video guide:
- https://x.com/techfrenAJ/status/2014934471095812547
+ [https://x.com/techfrenAJ/status/2014934471095812547](https://x.com/techfrenAJ/status/2014934471095812547)
## How cloud setups work
diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md
index 640340f17..233a67c48 100644
--- a/docs/web/control-ui.md
+++ b/docs/web/control-ui.md
@@ -19,7 +19,7 @@ It speaks **directly to the Gateway WebSocket** on the same port.
If the Gateway is running on the same computer, open:
-- http://127.0.0.1:18789/ (or http://localhost:18789/)
+- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
If the page fails to load, start the Gateway first: `openclaw gateway`.
diff --git a/docs/web/dashboard.md b/docs/web/dashboard.md
index d68456821..5c33455f0 100644
--- a/docs/web/dashboard.md
+++ b/docs/web/dashboard.md
@@ -12,7 +12,7 @@ The Gateway dashboard is the browser Control UI served at `/` by default
Quick open (local Gateway):
-- http://127.0.0.1:18789/ (or http://localhost:18789/)
+- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
Key references:
From 0a1f4f666a66846a67a103d10eeddb29d2ec422f Mon Sep 17 00:00:00 2001
From: Sebastian <19554889+sebslight@users.noreply.github.com>
Date: Fri, 6 Feb 2026 10:00:08 -0500
Subject: [PATCH 05/35] revert(docs): undo markdownlint autofix churn
---
.markdownlint-cli2.jsonc | 7 +++
docs/automation/gmail-pubsub.md | 6 +--
docs/bedrock.md | 2 +-
docs/brave-search.md | 2 +-
docs/channels/bluebubbles.md | 2 -
docs/channels/feishu.md | 4 +-
docs/channels/googlechat.md | 2 -
docs/channels/line.md | 2 +-
docs/channels/matrix.md | 2 +-
docs/channels/msteams.md | 5 +--
docs/channels/nextcloud-talk.md | 2 -
docs/channels/nostr.md | 6 +--
docs/channels/slack.md | 30 ++++++-------
docs/channels/telegram.md | 7 ++-
docs/channels/twitch.md | 4 +-
docs/channels/whatsapp.md | 2 -
docs/channels/zalo.md | 6 +--
docs/channels/zalouser.md | 4 +-
docs/concepts/architecture.md | 2 -
docs/concepts/groups.md | 6 +--
docs/concepts/memory.md | 8 ++--
docs/concepts/model-providers.md | 6 +--
docs/concepts/system-prompt.md | 2 +-
docs/concepts/typebox.md | 10 ++---
docs/debug/node-issue.md | 8 ++--
docs/experiments/research/memory.md | 2 +-
docs/gateway/authentication.md | 2 +-
docs/gateway/bonjour.md | 3 --
docs/gateway/configuration.md | 8 +---
docs/gateway/index.md | 2 -
docs/gateway/local-models.md | 2 +-
docs/gateway/security/index.md | 6 +--
docs/gateway/tailscale.md | 8 ++--
docs/gateway/troubleshooting.md | 11 -----
docs/help/faq.md | 47 ++++++---------------
docs/help/troubleshooting.md | 2 +-
docs/hooks.md | 3 --
docs/index.md | 2 +-
docs/install/docker.md | 6 +--
docs/install/gcp.md | 9 ++--
docs/install/northflank.mdx | 2 +-
docs/install/railway.mdx | 2 +-
docs/install/render.mdx | 8 +++-
docs/install/uninstall.md | 10 ++---
docs/install/updating.md | 5 +--
docs/multi-agent-sandbox-tools.md | 1 -
docs/perplexity.md | 4 +-
docs/pi-dev.md | 4 +-
docs/platforms/android.md | 3 --
docs/platforms/ios.md | 6 +--
docs/platforms/mac/dev-setup.md | 14 +++---
docs/platforms/mac/webchat.md | 2 -
docs/platforms/windows.md | 2 +-
docs/plugin.md | 18 ++++----
docs/prose.md | 2 +-
docs/providers/claude-max-api-proxy.md | 6 +--
docs/providers/cloudflare-ai-gateway.md | 2 +-
docs/providers/deepgram.md | 6 +--
docs/providers/minimax.md | 2 +-
docs/providers/moonshot.md | 4 +-
docs/providers/ollama.md | 6 +--
docs/providers/vercel-ai-gateway.md | 2 +-
docs/reference/AGENTS.default.md | 6 +--
docs/reference/RELEASING.md | 12 +++---
docs/reference/credits.md | 4 +-
docs/reference/device-models.md | 4 +-
docs/reference/templates/AGENTS.md | 8 ++--
docs/reference/templates/HEARTBEAT.md | 4 +-
docs/start/openclaw.md | 4 +-
docs/start/setup.md | 2 +-
docs/token-use.md | 2 +-
docs/tools/browser-linux-troubleshooting.md | 4 +-
docs/tools/browser-login.md | 2 +-
docs/tools/browser.md | 2 +-
docs/tools/chrome-extension.md | 6 +--
docs/tools/llm-task.md | 2 +-
docs/tools/lobster.md | 4 +-
docs/tools/skills.md | 4 +-
docs/tools/web.md | 4 +-
docs/tui.md | 4 +-
docs/vps.md | 2 +-
docs/web/control-ui.md | 2 +-
docs/web/dashboard.md | 2 +-
83 files changed, 192 insertions(+), 253 deletions(-)
diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc
index 24f90372b..3f321b5af 100644
--- a/.markdownlint-cli2.jsonc
+++ b/.markdownlint-cli2.jsonc
@@ -6,6 +6,11 @@
"MD013": false,
"MD025": false,
+ "MD026": false,
+ "MD029": false,
+ "MD030": false,
+ "MD031": false,
+ "MD032": false,
"MD033": {
"allowed_elements": [
@@ -43,7 +48,9 @@
],
},
+ "MD034": false,
"MD036": false,
+ "MD037": false,
"MD040": false,
"MD041": false,
"MD046": false,
diff --git a/docs/automation/gmail-pubsub.md b/docs/automation/gmail-pubsub.md
index d85802d4d..734ae6f77 100644
--- a/docs/automation/gmail-pubsub.md
+++ b/docs/automation/gmail-pubsub.md
@@ -143,19 +143,19 @@ gcloud config set project
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
-1. Enable APIs:
+2. Enable APIs:
```bash
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
```
-1. Create a topic:
+3. Create a topic:
```bash
gcloud pubsub topics create gog-gmail-watch
```
-1. Allow Gmail push to publish:
+4. Allow Gmail push to publish:
```bash
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
diff --git a/docs/bedrock.md b/docs/bedrock.md
index 47873eff2..34c759dbb 100644
--- a/docs/bedrock.md
+++ b/docs/bedrock.md
@@ -66,7 +66,7 @@ export AWS_PROFILE="your-profile"
export AWS_BEARER_TOKEN_BEDROCK="..."
```
-1. Add a Bedrock provider and model to your config (no `apiKey` required):
+2. Add a Bedrock provider and model to your config (no `apiKey` required):
```json5
{
diff --git a/docs/brave-search.md b/docs/brave-search.md
index ba18a6c55..260647942 100644
--- a/docs/brave-search.md
+++ b/docs/brave-search.md
@@ -12,7 +12,7 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
## Get an API key
-1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
+1. Create a Brave Search API account at https://brave.com/search/api/
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md
index 1f1ee40ea..b40fc375d 100644
--- a/docs/channels/bluebubbles.md
+++ b/docs/channels/bluebubbles.md
@@ -27,7 +27,6 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
2. In the BlueBubbles config, enable the web API and set a password.
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
-
```json5
{
channels: {
@@ -40,7 +39,6 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
},
}
```
-
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=`).
5. Start the gateway; it will register the webhook handler and start pairing.
diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md
index e15feafe3..2c6ba1e7f 100644
--- a/docs/channels/feishu.md
+++ b/docs/channels/feishu.md
@@ -75,7 +75,7 @@ Choose **Feishu**, then enter the App ID and App Secret.
Visit [Feishu Open Platform](https://open.feishu.cn/app) and sign in.
-Lark (global) tenants should use [https://open.larksuite.com/app](https://open.larksuite.com/app) and set `domain: "lark"` in the Feishu config.
+Lark (global) tenants should use https://open.larksuite.com/app and set `domain: "lark"` in the Feishu config.
### 2. Create an app
@@ -261,12 +261,10 @@ After approval, you can chat normally.
- **Default**: `dmPolicy: "pairing"` (unknown users get a pairing code)
- **Approve pairing**:
-
```bash
openclaw pairing list feishu
openclaw pairing approve feishu
```
-
- **Allowlist mode**: set `channels.feishu.allowFrom` with allowed Open IDs
### Group chats
diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md
index 39192ecae..07c7dd7dc 100644
--- a/docs/channels/googlechat.md
+++ b/docs/channels/googlechat.md
@@ -101,7 +101,6 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
5. **Verify the configuration:**
-
```bash
tailscale serve status
tailscale funnel status
@@ -226,7 +225,6 @@ This means the webhook handler isn't registered. Common causes:
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
3. **Gateway not restarted**: After adding config, restart the gateway:
-
```bash
openclaw gateway restart
```
diff --git a/docs/channels/line.md b/docs/channels/line.md
index d32e683fb..f68ae5aa1 100644
--- a/docs/channels/line.md
+++ b/docs/channels/line.md
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/line
## Setup
1. Create a LINE Developers account and open the Console:
- [https://developers.line.biz/console/](https://developers.line.biz/console/)
+ https://developers.line.biz/console/
2. Create (or pick) a Provider and add a **Messaging API** channel.
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
4. Enable **Use webhook** in the Messaging API settings.
diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md
index 56b363fdd..a196a68b6 100644
--- a/docs/channels/matrix.md
+++ b/docs/channels/matrix.md
@@ -74,7 +74,7 @@ Details: [Plugins](/plugin)
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
5. Restart the gateway (or finish onboarding).
6. Start a DM with the bot or invite it to a room from any Matrix client
- (Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE,
+ (Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
so set `channels.matrix.encryption: true` and verify the device.
Minimal config (access token, user ID auto-fetched):
diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md
index 92f413811..a18e8063d 100644
--- a/docs/channels/msteams.md
+++ b/docs/channels/msteams.md
@@ -166,7 +166,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
-1. Click **Review + create** → **Create** (wait ~1-2 minutes)
+3. Click **Review + create** → **Create** (wait ~1-2 minutes)
### Step 2: Get Credentials
@@ -558,7 +558,6 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
```
4. **Configure OpenClaw:**
-
```json5
{
channels: {
@@ -748,7 +747,7 @@ Bots have limited support in private channels:
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
-- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
+- **"Something went wrong" on upload:** Upload via https://admin.teams.microsoft.com instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
### RSC permissions not working
diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md
index efecfd990..edca54bc4 100644
--- a/docs/channels/nextcloud-talk.md
+++ b/docs/channels/nextcloud-talk.md
@@ -34,11 +34,9 @@ Details: [Plugins](/plugin)
1. Install the Nextcloud Talk plugin.
2. On your Nextcloud server, create a bot:
-
```bash
./occ talk:bot:install "OpenClaw" "" "" --feature reaction
```
-
3. Enable the bot in the target room settings.
4. Configure OpenClaw:
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
diff --git a/docs/channels/nostr.md b/docs/channels/nostr.md
index 30654a690..3368933d6 100644
--- a/docs/channels/nostr.md
+++ b/docs/channels/nostr.md
@@ -49,7 +49,7 @@ Restart the Gateway after installing or enabling plugins.
nak key generate
```
-1. Add to config:
+2. Add to config:
```json
{
@@ -61,13 +61,13 @@ nak key generate
}
```
-1. Export the key:
+3. Export the key:
```bash
export NOSTR_PRIVATE_KEY="nsec1..."
```
-1. Restart the Gateway.
+4. Restart the Gateway.
## Configuration reference
diff --git a/docs/channels/slack.md b/docs/channels/slack.md
index 1343ebf77..c8329439f 100644
--- a/docs/channels/slack.md
+++ b/docs/channels/slack.md
@@ -30,7 +30,7 @@ Minimal config:
### Setup
-1. Create a Slack app (From scratch) in [https://api.slack.com/apps](https://api.slack.com/apps).
+1. Create a Slack app (From scratch) in https://api.slack.com/apps.
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
@@ -260,30 +260,30 @@ If you enable native commands, add one `slash_commands` entry per command you wa
Slack's Conversations API is type-scoped: you only need the scopes for the
conversation types you actually touch (channels, groups, im, mpim). See
-[https://docs.slack.dev/apis/web-api/using-the-conversations-api/](https://docs.slack.dev/apis/web-api/using-the-conversations-api/) for the overview.
+https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
### Bot token scopes (required)
- `chat:write` (send/update/delete messages via `chat.postMessage`)
- [https://docs.slack.dev/reference/methods/chat.postMessage](https://docs.slack.dev/reference/methods/chat.postMessage)
+ https://docs.slack.dev/reference/methods/chat.postMessage
- `im:write` (open DMs via `conversations.open` for user DMs)
- [https://docs.slack.dev/reference/methods/conversations.open](https://docs.slack.dev/reference/methods/conversations.open)
+ https://docs.slack.dev/reference/methods/conversations.open
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
- [https://docs.slack.dev/reference/methods/conversations.history](https://docs.slack.dev/reference/methods/conversations.history)
+ https://docs.slack.dev/reference/methods/conversations.history
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
- [https://docs.slack.dev/reference/methods/conversations.info](https://docs.slack.dev/reference/methods/conversations.info)
+ https://docs.slack.dev/reference/methods/conversations.info
- `users:read` (user lookup)
- [https://docs.slack.dev/reference/methods/users.info](https://docs.slack.dev/reference/methods/users.info)
+ https://docs.slack.dev/reference/methods/users.info
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
- [https://docs.slack.dev/reference/methods/reactions.get](https://docs.slack.dev/reference/methods/reactions.get)
- [https://docs.slack.dev/reference/methods/reactions.add](https://docs.slack.dev/reference/methods/reactions.add)
+ https://docs.slack.dev/reference/methods/reactions.get
+ https://docs.slack.dev/reference/methods/reactions.add
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
- [https://docs.slack.dev/reference/scopes/pins.read](https://docs.slack.dev/reference/scopes/pins.read)
- [https://docs.slack.dev/reference/scopes/pins.write](https://docs.slack.dev/reference/scopes/pins.write)
+ https://docs.slack.dev/reference/scopes/pins.read
+ https://docs.slack.dev/reference/scopes/pins.write
- `emoji:read` (`emoji.list`)
- [https://docs.slack.dev/reference/scopes/emoji.read](https://docs.slack.dev/reference/scopes/emoji.read)
+ https://docs.slack.dev/reference/scopes/emoji.read
- `files:write` (uploads via `files.uploadV2`)
- [https://docs.slack.dev/messaging/working-with-files/#upload](https://docs.slack.dev/messaging/working-with-files/#upload)
+ https://docs.slack.dev/messaging/working-with-files/#upload
### User token scopes (optional, read-only by default)
@@ -302,9 +302,9 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
- `chat:write.public` (only if we want to post to channels the bot isn't in)
- [https://docs.slack.dev/reference/scopes/chat.write.public](https://docs.slack.dev/reference/scopes/chat.write.public)
+ https://docs.slack.dev/reference/scopes/chat.write.public
- `users:read.email` (only if we need email fields from `users.info`)
- [https://docs.slack.dev/changelog/2017-04-narrowing-email-access](https://docs.slack.dev/changelog/2017-04-narrowing-email-access)
+ https://docs.slack.dev/changelog/2017-04-narrowing-email-access
- `files:read` (only if we start listing/reading file metadata)
## Config
diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md
index 9e3921713..c7fb4fe57 100644
--- a/docs/channels/telegram.md
+++ b/docs/channels/telegram.md
@@ -74,9 +74,9 @@ If both env and config are set, config takes precedence.
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
-1. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
-2. DM access defaults to pairing. Approve the code when the bot is first contacted.
-3. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
+3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
+4. DM access defaults to pairing. Approve the code when the bot is first contacted.
+5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
## Token + privacy + permissions (Telegram side)
@@ -365,7 +365,6 @@ Alternate (official Bot API):
1. DM your bot.
2. Fetch updates with your bot token and read `message.from.id`:
-
```bash
curl "https://api.telegram.org/bot/getUpdates"
```
diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md
index ac46e35d6..7901c0427 100644
--- a/docs/channels/twitch.md
+++ b/docs/channels/twitch.md
@@ -34,7 +34,7 @@ Details: [Plugins](/plugin)
- Select **Bot Token**
- Verify scopes `chat:read` and `chat:write` are selected
- Copy the **Client ID** and **Access Token**
-3. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
+3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
4. Configure the token:
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
- Or config: `channels.twitch.accessToken`
@@ -123,7 +123,7 @@ Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
-Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) (Convert your Twitch username to ID)
+Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/ (Convert your Twitch username to ID)
## Token refresh (optional)
diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md
index 966c0902a..1741ee1b7 100644
--- a/docs/channels/whatsapp.md
+++ b/docs/channels/whatsapp.md
@@ -205,13 +205,11 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- `Body` is the current message body with envelope.
- Quoted reply context is **always appended**:
-
```
[Replying to +1555 id:ABC123]
>
[/Replying]
```
-
- Reply metadata also set:
- `ReplyToId` = stanzaId
- `ReplyToBody` = quoted body or media placeholder
diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md
index 0bb2607ec..0f247190c 100644
--- a/docs/channels/zalo.md
+++ b/docs/channels/zalo.md
@@ -57,7 +57,7 @@ It is a good fit for support or notifications where you want deterministic routi
### 1) Create a bot token (Zalo Bot Platform)
-1. Go to **[https://bot.zaloplatforms.com](https://bot.zaloplatforms.com)** and sign in.
+1. Go to **https://bot.zaloplatforms.com** and sign in.
2. Create a new bot and configure its settings.
3. Copy the bot token (format: `12345689:abc-xyz`).
@@ -81,8 +81,8 @@ Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
-1. Restart the gateway. Zalo starts when a token is resolved (env or config).
-2. DM access defaults to pairing. Approve the code when the bot is first contacted.
+3. Restart the gateway. Zalo starts when a token is resolved (env or config).
+4. DM access defaults to pairing. Approve the code when the bot is first contacted.
## How it works (behavior)
diff --git a/docs/channels/zalouser.md b/docs/channels/zalouser.md
index 53c9a4d2c..5a1b555b8 100644
--- a/docs/channels/zalouser.md
+++ b/docs/channels/zalouser.md
@@ -46,8 +46,8 @@ The Gateway machine must have the `zca` binary available in `PATH`.
}
```
-1. Restart the Gateway (or finish onboarding).
-2. DM access defaults to pairing; approve the pairing code on first contact.
+4. Restart the Gateway (or finish onboarding).
+5. DM access defaults to pairing; approve the pairing code on first contact.
## What it is
diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md
index a9676b171..a1c7f3383 100644
--- a/docs/concepts/architecture.md
+++ b/docs/concepts/architecture.md
@@ -110,11 +110,9 @@ Details: [Gateway protocol](/gateway/protocol), [Pairing](/start/pairing),
- Preferred: Tailscale or VPN.
- Alternative: SSH tunnel
-
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
-
- The same handshake + auth token apply over the tunnel.
- TLS + optional pinning can be enabled for WS in remote setups.
diff --git a/docs/concepts/groups.md b/docs/concepts/groups.md
index b873f995e..635211d33 100644
--- a/docs/concepts/groups.md
+++ b/docs/concepts/groups.md
@@ -301,7 +301,7 @@ Common intents (copy/paste):
}
```
-1. Allow only specific groups (WhatsApp)
+2. Allow only specific groups (WhatsApp)
```json5
{
@@ -316,7 +316,7 @@ Common intents (copy/paste):
}
```
-1. Allow all groups but require mention (explicit)
+3. Allow all groups but require mention (explicit)
```json5
{
@@ -328,7 +328,7 @@ Common intents (copy/paste):
}
```
-1. Only the owner can trigger in groups (WhatsApp)
+4. Only the owner can trigger in groups (WhatsApp)
```json5
{
diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md
index 6ed386cb0..4b499860b 100644
--- a/docs/concepts/memory.md
+++ b/docs/concepts/memory.md
@@ -302,8 +302,8 @@ Why OpenAI batch is fast + cheap:
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
- See the OpenAI Batch API docs and pricing for details:
- - [https://platform.openai.com/docs/api-reference/batch](https://platform.openai.com/docs/api-reference/batch)
- - [https://platform.openai.com/pricing](https://platform.openai.com/pricing)
+ - https://platform.openai.com/docs/api-reference/batch
+ - https://platform.openai.com/pricing
Config example:
@@ -382,11 +382,11 @@ Implementation sketch:
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
-1. Convert BM25 rank into a 0..1-ish score:
+2. Convert BM25 rank into a 0..1-ish score:
- `textScore = 1 / (1 + max(0, bm25Rank))`
-1. Union candidates by chunk id and compute a weighted score:
+3. Union candidates by chunk id and compute a weighted score:
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md
index fba56a34a..4d313cf0f 100644
--- a/docs/concepts/model-providers.md
+++ b/docs/concepts/model-providers.md
@@ -136,14 +136,14 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
Kimi K2 model IDs:
-{/_moonshot-kimi-k2-model-refs:start_/ && null}
+{/_ moonshot-kimi-k2-model-refs:start _/ && null}
- `moonshot/kimi-k2.5`
- `moonshot/kimi-k2-0905-preview`
- `moonshot/kimi-k2-turbo-preview`
- `moonshot/kimi-k2-thinking`
- `moonshot/kimi-k2-thinking-turbo`
- {/_moonshot-kimi-k2-model-refs:end_/ && null}
+ {/_ moonshot-kimi-k2-model-refs:end _/ && null}
```json5
{
@@ -242,7 +242,7 @@ Ollama is a local LLM runtime that provides an OpenAI-compatible API:
- Provider: `ollama`
- Auth: None required (local server)
- Example model: `ollama/llama3.3`
-- Installation: [https://ollama.ai](https://ollama.ai)
+- Installation: https://ollama.ai
```bash
# Install Ollama, then pull a model:
diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md
index acb2bf8b5..aafa80473 100644
--- a/docs/concepts/system-prompt.md
+++ b/docs/concepts/system-prompt.md
@@ -110,6 +110,6 @@ This keeps the base prompt small while still enabling targeted skill usage.
When available, the system prompt includes a **Documentation** section that points to the
local OpenClaw docs directory (either `docs/` in the repo workspace or the bundled npm
package docs) and also notes the public mirror, source repo, community Discord, and
-ClawHub ([https://clawhub.com](https://clawhub.com)) for skills discovery. The prompt instructs the model to consult local docs first
+ClawHub (https://clawhub.com) for skills discovery. The prompt instructs the model to consult local docs first
for OpenClaw behavior, commands, configuration, or architecture, and to run
`openclaw status` itself when possible (asking the user only when it lacks access).
diff --git a/docs/concepts/typebox.md b/docs/concepts/typebox.md
index a44f3ffd2..38ee7d8ca 100644
--- a/docs/concepts/typebox.md
+++ b/docs/concepts/typebox.md
@@ -217,7 +217,7 @@ export type SystemEchoParams = Static;
export type SystemEchoResult = Static;
```
-1. **Validation**
+2. **Validation**
In `src/gateway/protocol/index.ts`, export an AJV validator:
@@ -225,7 +225,7 @@ In `src/gateway/protocol/index.ts`, export an AJV validator:
export const validateSystemEchoParams = ajv.compile(SystemEchoParamsSchema);
```
-1. **Server behavior**
+3. **Server behavior**
Add a handler in `src/gateway/server-methods/system.ts`:
@@ -241,13 +241,13 @@ export const systemHandlers: GatewayRequestHandlers = {
Register it in `src/gateway/server-methods.ts` (already merges `systemHandlers`),
then add `"system.echo"` to `METHODS` in `src/gateway/server.ts`.
-1. **Regenerate**
+4. **Regenerate**
```bash
pnpm protocol:check
```
-1. **Tests + docs**
+5. **Tests + docs**
Add a server test in `src/gateway/server.*.test.ts` and note the method in docs.
@@ -280,7 +280,7 @@ Unknown frame types are preserved as raw payloads for forward compatibility.
Generated JSON Schema is in the repo at `dist/protocol.schema.json`. The
published raw file is typically available at:
-- [https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json](https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json)
+- https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json
## When you change schemas
diff --git a/docs/debug/node-issue.md b/docs/debug/node-issue.md
index 8355d2abc..ce46b1a05 100644
--- a/docs/debug/node-issue.md
+++ b/docs/debug/node-issue.md
@@ -62,21 +62,19 @@ node --import tsx scripts/repro/tsx-name-repro.ts
- Use Bun for dev scripts (current temporary revert).
- Use Node + tsc watch, then run compiled output:
-
```bash
pnpm exec tsc --watch --preserveWatchOutput
node --watch openclaw.mjs status
```
-
- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
- Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
- Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific.
## References
-- [https://opennext.js.org/cloudflare/howtos/keep_names](https://opennext.js.org/cloudflare/howtos/keep_names)
-- [https://esbuild.github.io/api/#keep-names](https://esbuild.github.io/api/#keep-names)
-- [https://github.com/evanw/esbuild/issues/1031](https://github.com/evanw/esbuild/issues/1031)
+- https://opennext.js.org/cloudflare/howtos/keep_names
+- https://esbuild.github.io/api/#keep-names
+- https://github.com/evanw/esbuild/issues/1031
## Next steps
diff --git a/docs/experiments/research/memory.md b/docs/experiments/research/memory.md
index 8d1404d7e..99135e78b 100644
--- a/docs/experiments/research/memory.md
+++ b/docs/experiments/research/memory.md
@@ -47,7 +47,7 @@ Two pieces to blend:
- everything else is out-of-context and retrieved via tools
- memory writes are explicit tool calls (append/replace/insert), persisted, then re-injected next turn
-1. **Hindsight-style memory substrate**
+2. **Hindsight-style memory substrate**
- separate what’s observed vs what’s believed vs what’s summarized
- support retain/recall/reflect
diff --git a/docs/gateway/authentication.md b/docs/gateway/authentication.md
index e63994bac..9b616084c 100644
--- a/docs/gateway/authentication.md
+++ b/docs/gateway/authentication.md
@@ -27,7 +27,7 @@ export ANTHROPIC_API_KEY="..."
openclaw models status
```
-1. If the Gateway runs under systemd/launchd, prefer putting the key in
+3. If the Gateway runs under systemd/launchd, prefer putting the key in
`~/.openclaw/.env` so the daemon can read it:
```bash
diff --git a/docs/gateway/bonjour.md b/docs/gateway/bonjour.md
index 9e2ad8753..b8f08741e 100644
--- a/docs/gateway/bonjour.md
+++ b/docs/gateway/bonjour.md
@@ -105,13 +105,10 @@ The Gateway advertises small non‑secret hints to make UI flows convenient:
Useful built‑in tools:
- Browse instances:
-
```bash
dns-sd -B _openclaw-gw._tcp local.
```
-
- Resolve one instance (replace ``):
-
```bash
dns-sd -L "" _openclaw-gw._tcp local.
```
diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md
index 639f84bd6..545a937df 100644
--- a/docs/gateway/configuration.md
+++ b/docs/gateway/configuration.md
@@ -1978,13 +1978,11 @@ Block streaming:
- `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to
800–1200 chars, prefers paragraph breaks (`\n\n`), then newlines, then sentences.
Example:
-
```json5
{
agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } },
}
```
-
- `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending.
Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`
with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default
@@ -1998,13 +1996,11 @@ Block streaming:
Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`).
Per-agent override: `agents.list[].humanDelay`.
Example:
-
```json5
{
agents: { defaults: { humanDelay: { mode: "natural" } } },
}
```
-
See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.
Typing indicators:
@@ -2070,7 +2066,7 @@ of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.
- `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)
- `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)
- `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`)
-- `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev))
+- `tools.web.fetch.firecrawl.baseUrl` (default https://api.firecrawl.dev)
- `tools.web.fetch.firecrawl.onlyMainContent` (default true)
- `tools.web.fetch.firecrawl.maxAgeMs` (optional)
- `tools.web.fetch.firecrawl.timeoutSeconds` (optional)
@@ -2486,7 +2482,7 @@ Select the model via `agents.defaults.model.primary` (provider/model).
OpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses
the built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or
-`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth).
+`OPENCODE_ZEN_API_KEY`) from https://opencode.ai/auth.
Notes:
diff --git a/docs/gateway/index.md b/docs/gateway/index.md
index 64697f1f4..06dd72c13 100644
--- a/docs/gateway/index.md
+++ b/docs/gateway/index.md
@@ -49,11 +49,9 @@ pnpm gateway:watch
## Remote access
- Tailscale/VPN preferred; otherwise SSH tunnel:
-
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
-
- Clients then connect to `ws://127.0.0.1:18789` through the tunnel.
- If a token is configured, clients must include it in `connect.params.auth.token` even over the tunnel.
diff --git a/docs/gateway/local-models.md b/docs/gateway/local-models.md
index 3f7e13d41..fe715ab05 100644
--- a/docs/gateway/local-models.md
+++ b/docs/gateway/local-models.md
@@ -52,7 +52,7 @@ Best current local stack. Load MiniMax M2.1 in LM Studio, enable the local serve
**Setup checklist**
-- Install LM Studio: [https://lmstudio.ai](https://lmstudio.ai)
+- Install LM Studio: https://lmstudio.ai
- In LM Studio, download the **largest MiniMax M2.1 build available** (avoid “small”/heavily quantized variants), start the server, confirm `http://127.0.0.1:1234/v1/models` lists it.
- Keep the model loaded; cold-load adds startup latency.
- Adjust `contextWindow`/`maxTokens` if your LM Studio build differs.
diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md
index f6bd91734..c6b521048 100644
--- a/docs/gateway/security/index.md
+++ b/docs/gateway/security/index.md
@@ -773,22 +773,18 @@ If it fails, there are new candidates not yet in the baseline.
### If CI fails
1. Reproduce locally:
-
```bash
detect-secrets scan --baseline .secrets.baseline
```
-
2. Understand the tools:
- `detect-secrets scan` finds candidates and compares them to the baseline.
- `detect-secrets audit` opens an interactive review to mark each baseline
item as real or false positive.
3. For real secrets: rotate/remove them, then re-run the scan to update the baseline.
4. For false positives: run the interactive audit and mark them as false:
-
```bash
detect-secrets audit .secrets.baseline
```
-
5. If you need new excludes, add them to `.detect-secrets.cfg` and regenerate the
baseline with matching `--exclude-files` / `--exclude-lines` flags (the config
file is reference-only; detect-secrets doesn’t read it automatically).
@@ -818,7 +814,7 @@ Mario asking for find ~
Found a vulnerability in OpenClaw? Please report responsibly:
-1. Email: [security@openclaw.ai](mailto:security@openclaw.ai)
+1. Email: security@openclaw.ai
2. Don't post publicly until fixed
3. We'll credit you (unless you prefer anonymity)
diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md
index 3a12b7fe1..3f4daa111 100644
--- a/docs/gateway/tailscale.md
+++ b/docs/gateway/tailscale.md
@@ -121,7 +121,7 @@ Avoid Funnel for browser control; treat node pairing like operator access.
## Learn more
-- Tailscale Serve overview: [https://tailscale.com/kb/1312/serve](https://tailscale.com/kb/1312/serve)
-- `tailscale serve` command: [https://tailscale.com/kb/1242/tailscale-serve](https://tailscale.com/kb/1242/tailscale-serve)
-- Tailscale Funnel overview: [https://tailscale.com/kb/1223/tailscale-funnel](https://tailscale.com/kb/1223/tailscale-funnel)
-- `tailscale funnel` command: [https://tailscale.com/kb/1311/tailscale-funnel](https://tailscale.com/kb/1311/tailscale-funnel)
+- Tailscale Serve overview: https://tailscale.com/kb/1312/serve
+- `tailscale serve` command: https://tailscale.com/kb/1242/tailscale-serve
+- Tailscale Funnel overview: https://tailscale.com/kb/1223/tailscale-funnel
+- `tailscale funnel` command: https://tailscale.com/kb/1311/tailscale-funnel
diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md
index 5f9d51f1d..d9aa303cd 100644
--- a/docs/gateway/troubleshooting.md
+++ b/docs/gateway/troubleshooting.md
@@ -42,11 +42,9 @@ Fix options:
- Re-run onboarding and choose **Anthropic** for that agent.
- Or paste a setup-token on the **gateway host**:
-
```bash
openclaw models auth setup-token --provider anthropic
```
-
- Or copy `auth-profiles.json` from the main agent dir to the new agent dir.
Verify:
@@ -122,17 +120,13 @@ Doctor/service will show runtime state (PID/last exit) and log hints.
**Enable more logging:**
- Bump file log detail (persisted JSONL):
-
```json
{ "logging": { "level": "debug" } }
```
-
- Bump console verbosity (TTY output only):
-
```json
{ "logging": { "consoleLevel": "debug", "consoleStyle": "pretty" } }
```
-
- Quick tip: `--verbose` affects **console** output only. File logs remain controlled by `logging.level`.
See [/logging](/logging) for a full overview of formats, config, and access.
@@ -145,13 +139,10 @@ Gateway refuses to start.
**Fix (recommended):**
- Run the wizard and set the Gateway run mode to **Local**:
-
```bash
openclaw configure
```
-
- Or set it directly:
-
```bash
openclaw config set gateway.mode local
```
@@ -159,7 +150,6 @@ Gateway refuses to start.
**If you meant to run a remote Gateway instead:**
- Set a remote URL and keep `gateway.mode=remote`:
-
```bash
openclaw config set gateway.mode remote
openclaw config set gateway.remote.url "wss://gateway.example.com"
@@ -564,7 +554,6 @@ Notes:
- The git flow only rebases if the repo is clean. Commit or stash changes first.
- After switching, run:
-
```bash
openclaw doctor
openclaw gateway restart
diff --git a/docs/help/faq.md b/docs/help/faq.md
index 794725442..191d2be1e 100644
--- a/docs/help/faq.md
+++ b/docs/help/faq.md
@@ -252,12 +252,10 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
Repairs/migrates config/state + runs health checks. See [Doctor](/gateway/doctor).
7. **Gateway snapshot**
-
```bash
openclaw health --json
openclaw health --verbose # shows the target URL + config path on errors
```
-
Asks the running gateway for a full snapshot (WS-only). See [Health](/gateway/health).
## Quick start and first-run setup
@@ -268,8 +266,8 @@ Use a local AI agent that can **see your machine**. That is far more effective t
in Discord, because most "I'm stuck" cases are **local config or environment issues** that
remote helpers cannot inspect.
-- **Claude Code**: [https://www.anthropic.com/claude-code/](https://www.anthropic.com/claude-code/)
-- **OpenAI Codex**: [https://openai.com/codex/](https://openai.com/codex/)
+- **Claude Code**: https://www.anthropic.com/claude-code/
+- **OpenAI Codex**: https://openai.com/codex/
These tools can read the repo, run commands, inspect logs, and help fix your machine-level
setup (PATH, services, permissions, auth files). Give them the **full source checkout** via
@@ -287,8 +285,8 @@ Tip: ask the agent to **plan and supervise** the fix (step-by-step), then execut
necessary commands. That keeps changes small and easier to audit.
If you discover a real bug or fix, please file a GitHub issue or send a PR:
-[https://github.com/openclaw/openclaw/issues](https://github.com/openclaw/openclaw/issues)
-[https://github.com/openclaw/openclaw/pulls](https://github.com/openclaw/openclaw/pulls)
+https://github.com/openclaw/openclaw/issues
+https://github.com/openclaw/openclaw/pulls
Start with these commands (share outputs when asking for help):
@@ -392,7 +390,7 @@ and tokens stay at 0, the agent never ran.
openclaw gateway restart
```
-1. Check status + auth:
+2. Check status + auth:
```bash
openclaw status
@@ -400,7 +398,7 @@ openclaw models status
openclaw logs --follow
```
-1. If it still hangs, run:
+3. If it still hangs, run:
```bash
openclaw doctor
@@ -434,7 +432,7 @@ Related: [Migrating](/install/migrating), [Where things live on disk](/help/faq#
### Where do I see what is new in the latest version
Check the GitHub changelog:
-[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
+https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
Newest entries are at the top. If the top section is marked **Unreleased**, the next dated
section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and
@@ -445,10 +443,10 @@ section is the latest shipped version. Entries are grouped by **Highlights**, **
Some Comcast/Xfinity connections incorrectly block `docs.openclaw.ai` via Xfinity
Advanced Security. Disable it or allowlist `docs.openclaw.ai`, then retry. More
detail: [Troubleshooting](/help/troubleshooting#docsopenclawai-shows-an-ssl-error-comcastxfinity).
-Please help us unblock it by reporting here: [https://spa.xfinity.com/check_url_status](https://spa.xfinity.com/check_url_status).
+Please help us unblock it by reporting here: https://spa.xfinity.com/check_url_status.
If you still can't reach the site, the docs are mirrored on GitHub:
-[https://github.com/openclaw/openclaw/tree/main/docs](https://github.com/openclaw/openclaw/tree/main/docs)
+https://github.com/openclaw/openclaw/tree/main/docs
### What's the difference between stable and beta
@@ -462,7 +460,7 @@ that same version to `latest`**. That's why beta and stable can point at the
**same version**.
See what changed:
-[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
+https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
### How do I install the beta version and whats the difference between beta and dev
@@ -480,7 +478,7 @@ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -
```
Windows installer (PowerShell):
-[https://openclaw.ai/install.ps1](https://openclaw.ai/install.ps1)
+https://openclaw.ai/install.ps1
More detail: [Development channels](/install/development-channels) and [Installer flags](/install/installer).
@@ -506,7 +504,7 @@ openclaw update --channel dev
This switches to the `main` branch and updates from source.
-1. **Hackable install (from the installer site):**
+2. **Hackable install (from the installer site):**
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git
@@ -561,11 +559,9 @@ Two common Windows issues:
- Your npm global bin folder is not on PATH.
- Check the path:
-
```powershell
npm config get prefix
```
-
- Ensure `\\bin` is on PATH (on most systems it is `%AppData%\\npm`).
- Close and reopen PowerShell after updating PATH.
@@ -992,7 +988,7 @@ Advantages:
- **Always-on Gateway** (run on a VPS, interact from anywhere)
- **Nodes** for local browser/screen/camera/exec
-Showcase: [https://openclaw.ai/showcase](https://openclaw.ai/showcase)
+Showcase: https://openclaw.ai/showcase
## Skills and automation
@@ -1050,7 +1046,7 @@ Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-v
### How do I install skills on Linux
Use **ClawHub** (CLI) or drop skills into your workspace. The macOS Skills UI isn't available on Linux.
-Browse skills at [https://clawhub.com](https://clawhub.com).
+Browse skills at https://clawhub.com.
Install the ClawHub CLI (pick one package manager):
@@ -1089,16 +1085,13 @@ Run the Gateway on Linux, pair a macOS node (menubar app), and set **Node Run Co
Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wrappers that run on a Mac. Then override the skill to allow Linux so it stays eligible.
1. Create an SSH wrapper for the binary (example: `memo` for Apple Notes):
-
```bash
#!/usr/bin/env bash
set -euo pipefail
exec ssh -T user@mac-host /opt/homebrew/bin/memo "$@"
```
-
2. Put the wrapper on `PATH` on the Linux host (for example `~/bin/memo`).
3. Override the skill metadata (workspace or `~/.openclaw/skills`) to allow Linux:
-
```markdown
---
name: apple-notes
@@ -1106,7 +1099,6 @@ Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wra
metadata: { "openclaw": { "os": ["darwin", "linux"], "requires": { "bins": ["memo"] } } }
---
```
-
4. Start a new session so the skills snapshot refreshes.
### Do you have a Notion or HeyGen integration
@@ -1481,7 +1473,6 @@ Typical setup:
4. Open the macOS app locally and connect in **Remote over SSH** mode (or direct tailnet)
so it can register as a node.
5. Approve the node on the Gateway:
-
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1619,12 +1610,10 @@ This sets your workspace and restricts who can trigger the bot.
Minimal steps:
1. **Install + login on the VPS**
-
```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```
-
2. **Install + login on your Mac**
- Use the Tailscale app and sign in to the same tailnet.
3. **Enable MagicDNS (recommended)**
@@ -1651,7 +1640,6 @@ Recommended setup:
2. **Use the macOS app in Remote mode** (SSH target can be the tailnet hostname).
The app will tunnel the Gateway port and connect as a node.
3. **Approve the node** on the gateway:
-
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1714,11 +1702,9 @@ If the Gateway runs as a service (launchd/systemd), it won't inherit your shell
environment. Fix by doing one of these:
1. Put the token in `~/.openclaw/.env`:
-
```
COPILOT_GITHUB_TOKEN=...
```
-
2. Or enable shell import (`env.shellEnv.enabled: true`).
3. Or add it to your config `env` block (applies only if missing).
@@ -1815,7 +1801,6 @@ Use one of these:
or `/compact ` to guide the summary.
- **Reset** (fresh session ID for the same chat key):
-
```
/new
/reset
@@ -2086,11 +2071,9 @@ Fix checklist:
3. Use the exact model id (case-sensitive): `minimax/MiniMax-M2.1` or
`minimax/MiniMax-M2.1-lightning`.
4. Run:
-
```bash
openclaw models list
```
-
and pick from the list (or `/model list` in chat).
See [MiniMax](/providers/minimax) and [Models](/concepts/models).
@@ -2255,11 +2238,9 @@ can't find it in its auth store.
- **If you want to use an API key instead**
- Put `ANTHROPIC_API_KEY` in `~/.openclaw/.env` on the **gateway host**.
- Clear any pinned order that forces a missing profile:
-
```bash
openclaw models auth order clear --provider anthropic
```
-
- **Confirm you're running commands on the gateway host**
- In remote mode, auth profiles live on the gateway machine, not your laptop.
diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md
index 2b201c5e9..03896a916 100644
--- a/docs/help/troubleshooting.md
+++ b/docs/help/troubleshooting.md
@@ -65,7 +65,7 @@ You can also set `OPENCLAW_VERBOSE=1` instead of the flag.
Some Comcast/Xfinity connections block `docs.openclaw.ai` via Xfinity Advanced Security.
Disable Advanced Security or add `docs.openclaw.ai` to the allowlist, then retry.
-- Xfinity Advanced Security help: [https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security](https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security)
+- Xfinity Advanced Security help: https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security
- Quick sanity checks: try a mobile hotspot or VPN to confirm it’s ISP-level filtering
### Service says running, but RPC probe fails
diff --git a/docs/hooks.md b/docs/hooks.md
index dfcd61ca1..a4a3a95df 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -787,7 +787,6 @@ Session reset
```
3. List all discovered hooks:
-
```bash
openclaw hooks list
```
@@ -819,7 +818,6 @@ Look for missing:
2. Restart your gateway process so hooks reload.
3. Check gateway logs for errors:
-
```bash
./scripts/clawlog.sh | grep hook
```
@@ -894,7 +892,6 @@ node -e "import('./path/to/handler.ts').then(console.log)"
```
4. Verify and restart your gateway process:
-
```bash
openclaw hooks list
# Should show: 🎯 my-hook ✓
diff --git a/docs/index.md b/docs/index.md
index 60c59bb7f..651f98440 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -120,7 +120,7 @@ Need the full install and dev setup? See [Quick start](/start/quickstart).
Open the browser Control UI after the Gateway starts.
-- Local default: [http://127.0.0.1:18789/](http://127.0.0.1:18789/)
+- Local default: http://127.0.0.1:18789/
- Remote access: [Web surfaces](/web) and [Tailscale](/gateway/tailscale)
diff --git a/docs/install/docker.md b/docs/install/docker.md
index 0ad59ae54..252bdb1ac 100644
--- a/docs/install/docker.md
+++ b/docs/install/docker.md
@@ -182,14 +182,14 @@ export OPENCLAW_HOME_VOLUME="openclaw_home"
./docker-setup.sh
```
-1. **Bake system deps into the image** (repeatable + persistent):
+2. **Bake system deps into the image** (repeatable + persistent):
```bash
export OPENCLAW_DOCKER_APT_PACKAGES="git curl jq"
./docker-setup.sh
```
-1. **Install Playwright browsers without `npx`** (avoids npm override conflicts):
+3. **Install Playwright browsers without `npx`** (avoids npm override conflicts):
```bash
docker compose run --rm openclaw-cli \
@@ -199,7 +199,7 @@ docker compose run --rm openclaw-cli \
If you need Playwright to install system deps, rebuild the image with
`OPENCLAW_DOCKER_APT_PACKAGES` instead of using `--with-deps` at runtime.
-1. **Persist Playwright browser downloads**:
+4. **Persist Playwright browser downloads**:
- Set `PLAYWRIGHT_BROWSERS_PATH=/home/node/.cache/ms-playwright` in
`docker-compose.yml`.
diff --git a/docs/install/gcp.md b/docs/install/gcp.md
index 6026fd87d..172a32ca8 100644
--- a/docs/install/gcp.md
+++ b/docs/install/gcp.md
@@ -69,7 +69,7 @@ For the generic Docker flow, see [Docker](/install/docker).
**Option A: gcloud CLI** (recommended for automation)
-Install from [https://cloud.google.com/sdk/docs/install](https://cloud.google.com/sdk/docs/install)
+Install from https://cloud.google.com/sdk/docs/install
Initialize and authenticate:
@@ -80,7 +80,7 @@ gcloud auth login
**Option B: Cloud Console**
-All steps can be done via the web UI at [https://console.cloud.google.com](https://console.cloud.google.com)
+All steps can be done via the web UI at https://console.cloud.google.com
---
@@ -93,7 +93,7 @@ gcloud projects create my-openclaw-project --name="OpenClaw Gateway"
gcloud config set project my-openclaw-project
```
-Enable billing at [https://console.cloud.google.com/billing](https://console.cloud.google.com/billing) (required for Compute Engine).
+Enable billing at https://console.cloud.google.com/billing (required for Compute Engine).
Enable the Compute Engine API:
@@ -484,7 +484,6 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
```
2. Grant Compute Instance Admin role (or narrower custom role):
-
```bash
gcloud projects add-iam-policy-binding my-openclaw-project \
--member="serviceAccount:openclaw-deploy@my-openclaw-project.iam.gserviceaccount.com" \
@@ -493,7 +492,7 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
Avoid using the Owner role for automation. Use the principle of least privilege.
-See [https://cloud.google.com/iam/docs/understanding-roles](https://cloud.google.com/iam/docs/understanding-roles) for IAM role details.
+See https://cloud.google.com/iam/docs/understanding-roles for IAM role details.
---
diff --git a/docs/install/northflank.mdx b/docs/install/northflank.mdx
index d3157d72e..8c1ff33ec 100644
--- a/docs/install/northflank.mdx
+++ b/docs/install/northflank.mdx
@@ -45,7 +45,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
+1. Go to https://discord.com/developers/applications
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/railway.mdx b/docs/install/railway.mdx
index 73f23fbe4..b27d94203 100644
--- a/docs/install/railway.mdx
+++ b/docs/install/railway.mdx
@@ -83,7 +83,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
+1. Go to https://discord.com/developers/applications
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/render.mdx b/docs/install/render.mdx
index ae9456870..a682d61c9 100644
--- a/docs/install/render.mdx
+++ b/docs/install/render.mdx
@@ -11,7 +11,13 @@ Deploy OpenClaw on Render using Infrastructure as Code. The included `render.yam
## Deploy with a Render Blueprint
-[Deploy to Render](https://render.com/deploy?repo=https://github.com/openclaw/openclaw)
+
+ Deploy to Render
+
Clicking this link will:
diff --git a/docs/install/uninstall.md b/docs/install/uninstall.md
index 16e54f461..f5543ce1c 100644
--- a/docs/install/uninstall.md
+++ b/docs/install/uninstall.md
@@ -36,13 +36,13 @@ Manual steps (same result):
openclaw gateway stop
```
-1. Uninstall the gateway service (launchd/systemd/schtasks):
+2. Uninstall the gateway service (launchd/systemd/schtasks):
```bash
openclaw gateway uninstall
```
-1. Delete state + config:
+3. Delete state + config:
```bash
rm -rf "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
@@ -50,13 +50,13 @@ rm -rf "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
If you set `OPENCLAW_CONFIG_PATH` to a custom location outside the state dir, delete that file too.
-1. Delete your workspace (optional, removes agent files):
+4. Delete your workspace (optional, removes agent files):
```bash
rm -rf ~/.openclaw/workspace
```
-1. Remove the CLI install (pick the one you used):
+5. Remove the CLI install (pick the one you used):
```bash
npm rm -g openclaw
@@ -64,7 +64,7 @@ pnpm remove -g openclaw
bun remove -g openclaw
```
-1. If you installed the macOS app:
+6. If you installed the macOS app:
```bash
rm -rf /Applications/OpenClaw.app
diff --git a/docs/install/updating.md b/docs/install/updating.md
index e463a5001..ae4b3d1eb 100644
--- a/docs/install/updating.md
+++ b/docs/install/updating.md
@@ -24,13 +24,10 @@ Notes:
- Add `--no-onboard` if you don’t want the onboarding wizard to run again.
- For **source installs**, use:
-
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git --no-onboard
```
-
The installer will `git pull --rebase` **only** if the repo is clean.
-
- For **global installs**, the script uses `npm install -g openclaw@latest` under the hood.
- Legacy note: `clawdbot` remains available as a compatibility shim.
@@ -228,4 +225,4 @@ git pull
- Run `openclaw doctor` again and read the output carefully (it often tells you the fix).
- Check: [Troubleshooting](/gateway/troubleshooting)
-- Ask in Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
+- Ask in Discord: https://discord.gg/clawd
diff --git a/docs/multi-agent-sandbox-tools.md b/docs/multi-agent-sandbox-tools.md
index e7de9caf8..a02af8d53 100644
--- a/docs/multi-agent-sandbox-tools.md
+++ b/docs/multi-agent-sandbox-tools.md
@@ -362,7 +362,6 @@ After configuring multi-agent sandbox and tools:
- Verify the agent cannot use denied tools
4. **Monitor logs:**
-
```exec
tail -f "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/logs/gateway.log" | grep -E "routing|sandbox|tools"
```
diff --git a/docs/perplexity.md b/docs/perplexity.md
index 178a7c360..46c4f12b9 100644
--- a/docs/perplexity.md
+++ b/docs/perplexity.md
@@ -15,12 +15,12 @@ through Perplexity’s direct API or via OpenRouter.
### Perplexity (direct)
-- Base URL: [https://api.perplexity.ai](https://api.perplexity.ai)
+- Base URL: https://api.perplexity.ai
- Environment variable: `PERPLEXITY_API_KEY`
### OpenRouter (alternative)
-- Base URL: [https://openrouter.ai/api/v1](https://openrouter.ai/api/v1)
+- Base URL: https://openrouter.ai/api/v1
- Environment variable: `OPENROUTER_API_KEY`
- Supports prepaid/crypto credits.
diff --git a/docs/pi-dev.md b/docs/pi-dev.md
index 2eeebdcc2..e850b8dc7 100644
--- a/docs/pi-dev.md
+++ b/docs/pi-dev.md
@@ -66,5 +66,5 @@ If you only want to reset sessions, delete `agents//sessions/` and `age
## References
-- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing)
-- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started)
+- https://docs.openclaw.ai/testing
+- https://docs.openclaw.ai/start/getting-started
diff --git a/docs/platforms/android.md b/docs/platforms/android.md
index b786e1782..6e395994b 100644
--- a/docs/platforms/android.md
+++ b/docs/platforms/android.md
@@ -98,13 +98,10 @@ Pairing details: [Gateway pairing](/gateway/pairing).
### 5) Verify the node is connected
- Via nodes status:
-
```bash
openclaw nodes status
```
-
- Via Gateway:
-
```bash
openclaw gateway call node.list --params "{}"
```
diff --git a/docs/platforms/ios.md b/docs/platforms/ios.md
index c3348f79f..b92a7e83b 100644
--- a/docs/platforms/ios.md
+++ b/docs/platforms/ios.md
@@ -33,16 +33,16 @@ Availability: internal preview. The iOS app is not publicly distributed yet.
openclaw gateway --port 18789
```
-1. In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
+2. In the iOS app, open Settings and pick a discovered gateway (or enable Manual Host and enter host/port).
-2. Approve the pairing request on the gateway host:
+3. Approve the pairing request on the gateway host:
```bash
openclaw nodes pending
openclaw nodes approve
```
-1. Verify connection:
+4. Verify connection:
```bash
openclaw nodes status
diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md
index 8aff51348..39d3125d8 100644
--- a/docs/platforms/mac/dev-setup.md
+++ b/docs/platforms/mac/dev-setup.md
@@ -13,8 +13,8 @@ This guide covers the necessary steps to build and run the OpenClaw macOS applic
Before building the app, ensure you have the following installed:
-1. **Xcode 26.2+**: Required for Swift development.
-2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
+1. **Xcode 26.2+**: Required for Swift development.
+2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
## 1. Install Dependencies
@@ -35,7 +35,7 @@ To build the macOS app and package it into `dist/OpenClaw.app`, run:
If you don't have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`).
For dev run modes, signing flags, and Team ID troubleshooting, see the macOS app README:
-[https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md](https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md)
+https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md
> **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with "Abort trap 6", see the [Troubleshooting](#troubleshooting) section.
@@ -45,9 +45,9 @@ The macOS app expects a global `openclaw` CLI install to manage background tasks
**To install it (recommended):**
-1. Open the OpenClaw app.
-2. Go to the **General** settings tab.
-3. Click **"Install CLI"**.
+1. Open the OpenClaw app.
+2. Go to the **General** settings tab.
+3. Click **"Install CLI"**.
Alternatively, install it manually:
@@ -82,11 +82,9 @@ If the app crashes when you try to allow **Speech Recognition** or **Microphone*
**Fix:**
1. Reset the TCC permissions:
-
```bash
tccutil reset All bot.molt.mac.debug
```
-
2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-app.sh) to force a "clean slate" from macOS.
### Gateway "Starting..." indefinitely
diff --git a/docs/platforms/mac/webchat.md b/docs/platforms/mac/webchat.md
index ea6791ff5..5f654e174 100644
--- a/docs/platforms/mac/webchat.md
+++ b/docs/platforms/mac/webchat.md
@@ -19,11 +19,9 @@ agent (with a session switcher for other sessions).
- Manual: Lobster menu → “Open Chat”.
- Auto‑open for testing:
-
```bash
dist/OpenClaw.app/Contents/MacOS/OpenClaw --webchat
```
-
- Logs: `./scripts/clawlog.sh` (subsystem `bot.molt`, category `WebChatSwiftUI`).
## How it’s wired
diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md
index d15131486..e89cae95e 100644
--- a/docs/platforms/windows.md
+++ b/docs/platforms/windows.md
@@ -20,7 +20,7 @@ Native Windows companion apps are planned.
- [Getting Started](/start/getting-started) (use inside WSL)
- [Install & updates](/install/updating)
-- Official WSL2 guide (Microsoft): [https://learn.microsoft.com/windows/wsl/install](https://learn.microsoft.com/windows/wsl/install)
+- Official WSL2 guide (Microsoft): https://learn.microsoft.com/windows/wsl/install
## Gateway
diff --git a/docs/plugin.md b/docs/plugin.md
index aad0e58e3..50d4ffd77 100644
--- a/docs/plugin.md
+++ b/docs/plugin.md
@@ -25,13 +25,13 @@ Fast path:
openclaw plugins list
```
-1. Install an official plugin (example: Voice Call):
+2. Install an official plugin (example: Voice Call):
```bash
openclaw plugins install @openclaw/voice-call
```
-1. Restart the Gateway, then configure under `plugins.entries..config`.
+3. Restart the Gateway, then configure under `plugins.entries..config`.
See [Voice Call](/plugins/voice-call) for a concrete example plugin.
@@ -94,17 +94,17 @@ OpenClaw scans, in order:
- `plugins.load.paths` (file or directory)
-1. Workspace extensions
+2. Workspace extensions
- `/.openclaw/extensions/*.ts`
- `/.openclaw/extensions/*/index.ts`
-1. Global extensions
+3. Global extensions
- `~/.openclaw/extensions/*.ts`
- `~/.openclaw/extensions/*/index.ts`
-1. Bundled extensions (shipped with OpenClaw, **disabled by default**)
+4. Bundled extensions (shipped with OpenClaw, **disabled by default**)
- `/extensions/*`
@@ -432,26 +432,26 @@ Model provider docs live under `/providers/*`.
- All channel config lives under `channels.`.
- Prefer `channels..accounts.` for multi‑account setups.
-1. 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/`.
- `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.
-1. 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)
-1. 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)
-1. Register the channel in your plugin
+5. Register the channel in your plugin
- `api.registerChannel({ plugin })`
diff --git a/docs/prose.md b/docs/prose.md
index 7b4b8c002..4b825c467 100644
--- a/docs/prose.md
+++ b/docs/prose.md
@@ -11,7 +11,7 @@ title: "OpenProse"
OpenProse is a portable, markdown-first workflow format for orchestrating AI sessions. In OpenClaw it ships as a plugin that installs an OpenProse skill pack plus a `/prose` slash command. Programs live in `.prose` files and can spawn multiple sub-agents with explicit control flow.
-Official site: [https://www.prose.md](https://www.prose.md)
+Official site: https://www.prose.md
## What it can do
diff --git a/docs/providers/claude-max-api-proxy.md b/docs/providers/claude-max-api-proxy.md
index 11b830710..997023312 100644
--- a/docs/providers/claude-max-api-proxy.md
+++ b/docs/providers/claude-max-api-proxy.md
@@ -131,9 +131,9 @@ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude-max-api.plist
## Links
-- **npm:** [https://www.npmjs.com/package/claude-max-api-proxy](https://www.npmjs.com/package/claude-max-api-proxy)
-- **GitHub:** [https://github.com/atalovesyou/claude-max-api-proxy](https://github.com/atalovesyou/claude-max-api-proxy)
-- **Issues:** [https://github.com/atalovesyou/claude-max-api-proxy/issues](https://github.com/atalovesyou/claude-max-api-proxy/issues)
+- **npm:** https://www.npmjs.com/package/claude-max-api-proxy
+- **GitHub:** https://github.com/atalovesyou/claude-max-api-proxy
+- **Issues:** https://github.com/atalovesyou/claude-max-api-proxy/issues
## Notes
diff --git a/docs/providers/cloudflare-ai-gateway.md b/docs/providers/cloudflare-ai-gateway.md
index 48f69750d..392a611e7 100644
--- a/docs/providers/cloudflare-ai-gateway.md
+++ b/docs/providers/cloudflare-ai-gateway.md
@@ -25,7 +25,7 @@ For Anthropic models, use your Anthropic API key.
openclaw onboard --auth-choice cloudflare-ai-gateway-api-key
```
-1. Set a default model:
+2. Set a default model:
```json5
{
diff --git a/docs/providers/deepgram.md b/docs/providers/deepgram.md
index b8a1e7fce..cf32467e5 100644
--- a/docs/providers/deepgram.md
+++ b/docs/providers/deepgram.md
@@ -15,8 +15,8 @@ When enabled, OpenClaw uploads the audio file to Deepgram and injects the transc
into the reply pipeline (`{{Transcript}}` + `[Audio]` block). This is **not streaming**;
it uses the pre-recorded transcription endpoint.
-Website: [https://deepgram.com](https://deepgram.com)
-Docs: [https://developers.deepgram.com](https://developers.deepgram.com)
+Website: https://deepgram.com
+Docs: https://developers.deepgram.com
## Quick start
@@ -26,7 +26,7 @@ Docs: [https://developers.deepgram.com](https://developers.deepgram.com)
DEEPGRAM_API_KEY=dg_...
```
-1. Enable the provider:
+2. Enable the provider:
```json5
{
diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md
index 294388fbc..f19478a49 100644
--- a/docs/providers/minimax.md
+++ b/docs/providers/minimax.md
@@ -179,7 +179,7 @@ Use the interactive config wizard to set MiniMax without editing JSON:
- Model refs are `minimax/`.
- Coding Plan usage API: `https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains` (requires a coding plan key).
- Update pricing values in `models.json` if you need exact cost tracking.
-- Referral link for MiniMax Coding Plan (10% off): [https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link](https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link)
+- Referral link for MiniMax Coding Plan (10% off): https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link
- See [/concepts/model-providers](/concepts/model-providers) for provider rules.
- Use `openclaw models list` and `openclaw models set minimax/MiniMax-M2.1` to switch.
diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md
index 0a46c9067..6e6ec5295 100644
--- a/docs/providers/moonshot.md
+++ b/docs/providers/moonshot.md
@@ -15,14 +15,14 @@ Kimi Coding with `kimi-coding/k2p5`.
Current Kimi K2 model IDs:
-{/_moonshot-kimi-k2-ids:start_/ && null}
+{/_ moonshot-kimi-k2-ids:start _/ && null}
- `kimi-k2.5`
- `kimi-k2-0905-preview`
- `kimi-k2-turbo-preview`
- `kimi-k2-thinking`
- `kimi-k2-thinking-turbo`
- {/_moonshot-kimi-k2-ids:end_/ && null}
+ {/_ moonshot-kimi-k2-ids:end _/ && null}
```bash
openclaw onboard --auth-choice moonshot-api-key
diff --git a/docs/providers/ollama.md b/docs/providers/ollama.md
index cd39e9396..9d2f177bf 100644
--- a/docs/providers/ollama.md
+++ b/docs/providers/ollama.md
@@ -12,7 +12,7 @@ Ollama is a local LLM runtime that makes it easy to run open-source models on yo
## Quick start
-1. Install Ollama: [https://ollama.ai](https://ollama.ai)
+1. Install Ollama: https://ollama.ai
2. Pull a model:
@@ -26,7 +26,7 @@ ollama pull qwen2.5-coder:32b
ollama pull deepseek-r1:32b
```
-1. Enable Ollama for OpenClaw (any value works; Ollama doesn't require a real key):
+3. Enable Ollama for OpenClaw (any value works; Ollama doesn't require a real key):
```bash
# Set environment variable
@@ -36,7 +36,7 @@ export OLLAMA_API_KEY="ollama-local"
openclaw config set models.providers.ollama.apiKey "ollama-local"
```
-1. Use Ollama models:
+4. Use Ollama models:
```json5
{
diff --git a/docs/providers/vercel-ai-gateway.md b/docs/providers/vercel-ai-gateway.md
index eb6ceef25..726a6040f 100644
--- a/docs/providers/vercel-ai-gateway.md
+++ b/docs/providers/vercel-ai-gateway.md
@@ -22,7 +22,7 @@ The [Vercel AI Gateway](https://vercel.com/ai-gateway) provides a unified API to
openclaw onboard --auth-choice ai-gateway-api-key
```
-1. Set a default model:
+2. Set a default model:
```json5
{
diff --git a/docs/reference/AGENTS.default.md b/docs/reference/AGENTS.default.md
index 2f7cd1fe8..bc0dcde68 100644
--- a/docs/reference/AGENTS.default.md
+++ b/docs/reference/AGENTS.default.md
@@ -17,7 +17,7 @@ OpenClaw uses a dedicated workspace directory for the agent. Default: `~/.opencl
mkdir -p ~/.openclaw/workspace
```
-1. Copy the default workspace templates into the workspace:
+2. Copy the default workspace templates into the workspace:
```bash
cp docs/reference/templates/AGENTS.md ~/.openclaw/workspace/AGENTS.md
@@ -25,13 +25,13 @@ cp docs/reference/templates/SOUL.md ~/.openclaw/workspace/SOUL.md
cp docs/reference/templates/TOOLS.md ~/.openclaw/workspace/TOOLS.md
```
-1. Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:
+3. Optional: if you want the personal assistant skill roster, replace AGENTS.md with this file:
```bash
cp docs/reference/AGENTS.default.md ~/.openclaw/workspace/AGENTS.md
```
-1. Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):
+4. Optional: choose a different workspace by setting `agents.defaults.workspace` (supports `~`):
```json5
{
diff --git a/docs/reference/RELEASING.md b/docs/reference/RELEASING.md
index 1d4c9af79..23670a133 100644
--- a/docs/reference/RELEASING.md
+++ b/docs/reference/RELEASING.md
@@ -26,7 +26,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- [ ] Confirm package metadata (name, description, repository, keywords, license) and `bin` map points to [`openclaw.mjs`](https://github.com/openclaw/openclaw/blob/main/openclaw.mjs) for `openclaw`.
- [ ] If dependencies changed, run `pnpm install` so `pnpm-lock.yaml` is current.
-1. **Build & artifacts**
+2. **Build & artifacts**
- [ ] If A2UI inputs changed, run `pnpm canvas:a2ui:bundle` and commit any updated [`src/canvas-host/a2ui/a2ui.bundle.js`](https://github.com/openclaw/openclaw/blob/main/src/canvas-host/a2ui/a2ui.bundle.js).
- [ ] `pnpm run build` (regenerates `dist/`).
@@ -34,12 +34,12 @@ When the operator says “release”, immediately do this preflight (no extra qu
- [ ] Confirm `dist/build-info.json` exists and includes the expected `commit` hash (CLI banner uses this for npm installs).
- [ ] Optional: `npm pack --pack-destination /tmp` after the build; inspect the tarball contents and keep it handy for the GitHub release (do **not** commit it).
-1. **Changelog & docs**
+3. **Changelog & docs**
- [ ] Update `CHANGELOG.md` with user-facing highlights (create the file if missing); keep entries strictly descending by version.
- [ ] Ensure README examples/flags match current CLI behavior (notably new commands or options).
-1. **Validation**
+4. **Validation**
- [ ] `pnpm build`
- [ ] `pnpm check`
@@ -54,7 +54,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- `pnpm test:install:e2e` (requires both keys; runs both providers)
- [ ] (Optional) Spot-check the web gateway if your changes affect send/receive paths.
-1. **macOS app (Sparkle)**
+5. **macOS app (Sparkle)**
- [ ] Build + sign the macOS app, then zip it for distribution.
- [ ] Generate the Sparkle appcast (HTML notes via [`scripts/make_appcast.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/make_appcast.sh)) and update `appcast.xml`.
@@ -63,7 +63,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- `APP_BUILD` must be numeric + monotonic (no `-beta`) so Sparkle compares versions correctly.
- If notarizing, use the `openclaw-notary` keychain profile created from App Store Connect API env vars (see [macOS release](/platforms/mac/release)).
-1. **Publish (npm)**
+6. **Publish (npm)**
- [ ] Confirm git status is clean; commit and push as needed.
- [ ] `npm login` (verify 2FA) if needed.
@@ -80,7 +80,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
- **Tag needs repointing after a late fix**: force-update and push the tag, then ensure the GitHub release assets still match:
- `git tag -f vX.Y.Z && git push -f origin vX.Y.Z`
-1. **GitHub release + appcast**
+7. **GitHub release + appcast**
- [ ] Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z` (or `git push --tags`).
- [ ] Create/refresh the GitHub release for `vX.Y.Z` with **title `openclaw X.Y.Z`** (not just the tag); body should include the **full** changelog section for that version (Highlights + Changes + Fixes), inline (no bare links), and **must not repeat the title inside the body**.
diff --git a/docs/reference/credits.md b/docs/reference/credits.md
index 67e85ca72..e9ba9bca3 100644
--- a/docs/reference/credits.md
+++ b/docs/reference/credits.md
@@ -17,8 +17,8 @@ OpenClaw = CLAW + TARDIS, because every space lobster needs a time and space mac
## Core contributors
-- **Maxim Vovshin** (@Hyaxia, [36747317+Hyaxia@users.noreply.github.com](mailto:36747317+Hyaxia@users.noreply.github.com)) - Blogwatcher skill
-- **Nacho Iacovino** (@nachoiacovino, [nacho.iacovino@gmail.com](mailto:nacho.iacovino@gmail.com)) - Location parsing (Telegram and WhatsApp)
+- **Maxim Vovshin** (@Hyaxia, 36747317+Hyaxia@users.noreply.github.com) - Blogwatcher skill
+- **Nacho Iacovino** (@nachoiacovino, nacho.iacovino@gmail.com) - Location parsing (Telegram and WhatsApp)
## License
diff --git a/docs/reference/device-models.md b/docs/reference/device-models.md
index 3538562fe..00d2b9cf7 100644
--- a/docs/reference/device-models.md
+++ b/docs/reference/device-models.md
@@ -39,8 +39,8 @@ curl -fsSL "https://raw.githubusercontent.com/kyle-seongwoo-jun/apple-device-ide
-o apps/macos/Sources/OpenClaw/Resources/DeviceModels/mac-device-identifiers.json
```
-1. Ensure `apps/macos/Sources/OpenClaw/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
-2. Verify the macOS app builds cleanly (no warnings):
+4. Ensure `apps/macos/Sources/OpenClaw/Resources/DeviceModels/LICENSE.apple-device-identifiers.txt` still matches upstream (replace it if the upstream license changes).
+5. Verify the macOS app builds cleanly (no warnings):
```bash
swift build --package-path apps/macos
diff --git a/docs/reference/templates/AGENTS.md b/docs/reference/templates/AGENTS.md
index 46967ff08..956b1195a 100644
--- a/docs/reference/templates/AGENTS.md
+++ b/docs/reference/templates/AGENTS.md
@@ -42,7 +42,7 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
- This is your curated memory — the distilled essence, not raw logs
- Over time, review your daily files and update MEMORY.md with what's worth keeping
-### 📝 Write It Down - No "Mental Notes"
+### 📝 Write It Down - No "Mental Notes"!
- **Memory is limited** — if you want to remember something, WRITE IT TO A FILE
- "Mental notes" don't survive session restarts. Files do.
@@ -76,7 +76,7 @@ Capture what matters. Decisions, context, things to remember. Skip the secrets u
You have access to your human's stuff. That doesn't mean you _share_ their stuff. In groups, you're a participant — not their voice, not their proxy. Think before you speak.
-### 💬 Know When to Speak
+### 💬 Know When to Speak!
In group chats where you receive every message, be **smart about when to contribute**:
@@ -102,7 +102,7 @@ In group chats where you receive every message, be **smart about when to contrib
Participate, don't dominate.
-### 😊 React Like a Human
+### 😊 React Like a Human!
On platforms that support reactions (Discord, Slack), use emoji reactions naturally:
@@ -131,7 +131,7 @@ Skills provide your tools. When you need one, check its `SKILL.md`. Keep local n
- **Discord links:** Wrap multiple links in `<>` to suppress embeds: ``
- **WhatsApp:** No headers — use **bold** or CAPS for emphasis
-## 💓 Heartbeats - Be Proactive
+## 💓 Heartbeats - Be Proactive!
When you receive a heartbeat poll (message matches the configured heartbeat prompt), don't just reply `HEARTBEAT_OK` every time. Use heartbeats productively!
diff --git a/docs/reference/templates/HEARTBEAT.md b/docs/reference/templates/HEARTBEAT.md
index 09d04b449..5ee0d711f 100644
--- a/docs/reference/templates/HEARTBEAT.md
+++ b/docs/reference/templates/HEARTBEAT.md
@@ -6,6 +6,6 @@ read_when:
# HEARTBEAT.md
-# Keep this file empty (or with only comments) to skip heartbeat API calls
+# Keep this file empty (or with only comments) to skip heartbeat API calls.
-# Add tasks below when you want the agent to check something periodically
+# Add tasks below when you want the agent to check something periodically.
diff --git a/docs/start/openclaw.md b/docs/start/openclaw.md
index e2b07a28e..c5a419635 100644
--- a/docs/start/openclaw.md
+++ b/docs/start/openclaw.md
@@ -58,13 +58,13 @@ If you link your personal WhatsApp to OpenClaw, every message to you becomes “
openclaw channels login
```
-1. Start the Gateway (leave it running):
+2. Start the Gateway (leave it running):
```bash
openclaw gateway --port 18789
```
-1. Put a minimal config in `~/.openclaw/openclaw.json`:
+3. Put a minimal config in `~/.openclaw/openclaw.json`:
```json5
{
diff --git a/docs/start/setup.md b/docs/start/setup.md
index f9d6dc9a0..ee50e02af 100644
--- a/docs/start/setup.md
+++ b/docs/start/setup.md
@@ -67,7 +67,7 @@ node openclaw.mjs gateway --port 18789 --verbose
openclaw channels login
```
-1. Sanity check:
+5. Sanity check:
```bash
openclaw health
diff --git a/docs/token-use.md b/docs/token-use.md
index 16b0fe961..7f8dcb7fb 100644
--- a/docs/token-use.md
+++ b/docs/token-use.md
@@ -85,7 +85,7 @@ re-caching the full prompt, reducing cache write costs.
For Anthropic API pricing, cache reads are significantly cheaper than input
tokens, while cache writes are billed at a higher multiplier. See Anthropic’s
prompt caching pricing for the latest rates and TTL multipliers:
-[https://docs.anthropic.com/docs/build-with-claude/prompt-caching](https://docs.anthropic.com/docs/build-with-claude/prompt-caching)
+https://docs.anthropic.com/docs/build-with-claude/prompt-caching
### Example: keep 1h cache warm with heartbeat
diff --git a/docs/tools/browser-linux-troubleshooting.md b/docs/tools/browser-linux-troubleshooting.md
index fdd475560..01e6cbc3f 100644
--- a/docs/tools/browser-linux-troubleshooting.md
+++ b/docs/tools/browser-linux-troubleshooting.md
@@ -67,7 +67,7 @@ If you must use snap Chromium, configure OpenClaw to attach to a manually-starte
}
```
-1. Start Chromium manually:
+2. Start Chromium manually:
```bash
chromium-browser --headless --no-sandbox --disable-gpu \
@@ -76,7 +76,7 @@ chromium-browser --headless --no-sandbox --disable-gpu \
about:blank &
```
-1. Optionally create a systemd user service to auto-start Chrome:
+3. Optionally create a systemd user service to auto-start Chrome:
```ini
# ~/.config/systemd/user/openclaw-browser.service
diff --git a/docs/tools/browser-login.md b/docs/tools/browser-login.md
index a3c7a4615..dcfb5ceb4 100644
--- a/docs/tools/browser-login.md
+++ b/docs/tools/browser-login.md
@@ -35,7 +35,7 @@ If you have multiple profiles, pass `--browser-profile ` (the default is `
## X/Twitter: recommended flow
- **Read/search/threads:** use the **bird** CLI skill (no browser, stable).
- - Repo: [https://github.com/steipete/bird](https://github.com/steipete/bird)
+ - Repo: https://github.com/steipete/bird
- **Post updates:** use the **host** browser (manual login).
## Sandboxing + host browser access
diff --git a/docs/tools/browser.md b/docs/tools/browser.md
index d4c12e239..848977d1e 100644
--- a/docs/tools/browser.md
+++ b/docs/tools/browser.md
@@ -252,7 +252,7 @@ openclaw browser extension install
- “Load unpacked” → select the directory printed by `openclaw browser extension path`
- Pin the extension, then click it on the tab you want to control (badge shows `ON`).
-1. Use it:
+2. Use it:
- CLI: `openclaw browser --browser-profile chrome tabs`
- Agent tool: `browser` with `profile="chrome"`
diff --git a/docs/tools/chrome-extension.md b/docs/tools/chrome-extension.md
index 0a1a848de..4d49c835e 100644
--- a/docs/tools/chrome-extension.md
+++ b/docs/tools/chrome-extension.md
@@ -31,18 +31,18 @@ OpenClaw then controls the attached tab through the normal `browser` tool surfac
openclaw browser extension install
```
-1. Print the installed extension directory path:
+2. Print the installed extension directory path:
```bash
openclaw browser extension path
```
-1. Chrome → `chrome://extensions`
+3. Chrome → `chrome://extensions`
- Enable “Developer mode”
- “Load unpacked” → select the directory printed above
-1. Pin the extension.
+4. Pin the extension.
## Updates (no build step)
diff --git a/docs/tools/llm-task.md b/docs/tools/llm-task.md
index 2b1909f0c..16ae39e5e 100644
--- a/docs/tools/llm-task.md
+++ b/docs/tools/llm-task.md
@@ -28,7 +28,7 @@ without writing custom OpenClaw code for each workflow.
}
```
-1. Allowlist the tool (it is registered with `optional: true`):
+2. Allowlist the tool (it is registered with `optional: true`):
```json
{
diff --git a/docs/tools/lobster.md b/docs/tools/lobster.md
index ed9ed1fb2..62ef21357 100644
--- a/docs/tools/lobster.md
+++ b/docs/tools/lobster.md
@@ -338,5 +338,5 @@ OpenProse pairs well with Lobster: use `/prose` to orchestrate multi-agent prep,
One public example: a “second brain” CLI + Lobster pipelines that manage three Markdown vaults (personal, partner, shared). The CLI emits JSON for stats, inbox listings, and stale scans; Lobster chains those commands into workflows like `weekly-review`, `inbox-triage`, `memory-consolidation`, and `shared-task-sync`, each with approval gates. AI handles judgment (categorization) when available and falls back to deterministic rules when not.
-- Thread: [https://x.com/plattenschieber/status/2014508656335770033](https://x.com/plattenschieber/status/2014508656335770033)
-- Repo: [https://github.com/bloomedai/brain-cli](https://github.com/bloomedai/brain-cli)
+- Thread: https://x.com/plattenschieber/status/2014508656335770033
+- Repo: https://github.com/bloomedai/brain-cli
diff --git a/docs/tools/skills.md b/docs/tools/skills.md
index b8038ee0f..b4a142e33 100644
--- a/docs/tools/skills.md
+++ b/docs/tools/skills.md
@@ -50,7 +50,7 @@ tool surface those skills teach.
## ClawHub (install + sync)
ClawHub is the public skills registry for OpenClaw. Browse at
-[https://clawhub.com](https://clawhub.com). Use it to discover, install, update, and back up skills.
+https://clawhub.com. Use it to discover, install, update, and back up skills.
Full guide: [ClawHub](/tools/clawhub).
Common flows:
@@ -295,6 +295,6 @@ See [Skills config](/tools/skills-config) for the full configuration schema.
## Looking for more skills?
-Browse [https://clawhub.com](https://clawhub.com).
+Browse https://clawhub.com.
---
diff --git a/docs/tools/web.md b/docs/tools/web.md
index c22bc1707..4a3c23841 100644
--- a/docs/tools/web.md
+++ b/docs/tools/web.md
@@ -71,7 +71,7 @@ Example: switch to Perplexity Sonar (direct API):
## Getting a Brave API key
-1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
+1. Create a Brave Search API account at https://brave.com/search/api/
2. In the dashboard, choose the **Data for Search** plan (not “Data for AI”) and generate an API key.
3. Run `openclaw configure --section web` to store the key in config (recommended), or set `BRAVE_API_KEY` in your environment.
@@ -95,7 +95,7 @@ crypto/prepaid).
### Getting an OpenRouter API key
-1. Create an account at [https://openrouter.ai/](https://openrouter.ai/)
+1. Create an account at https://openrouter.ai/
2. Add credits (supports crypto, prepaid, or credit card)
3. Generate an API key in your account settings
diff --git a/docs/tui.md b/docs/tui.md
index 6ae2f14f1..8398cedfe 100644
--- a/docs/tui.md
+++ b/docs/tui.md
@@ -16,13 +16,13 @@ title: "TUI"
openclaw gateway
```
-1. Open the TUI.
+2. Open the TUI.
```bash
openclaw tui
```
-1. Type a message and press Enter.
+3. Type a message and press Enter.
Remote Gateway:
diff --git a/docs/vps.md b/docs/vps.md
index f0b1f7d77..dedccee4b 100644
--- a/docs/vps.md
+++ b/docs/vps.md
@@ -21,7 +21,7 @@ deployments work at a high level.
- **GCP (Compute Engine)**: [GCP](/install/gcp)
- **exe.dev** (VM + HTTPS proxy): [exe.dev](/install/exe-dev)
- **AWS (EC2/Lightsail/free tier)**: works well too. Video guide:
- [https://x.com/techfrenAJ/status/2014934471095812547](https://x.com/techfrenAJ/status/2014934471095812547)
+ https://x.com/techfrenAJ/status/2014934471095812547
## How cloud setups work
diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md
index 233a67c48..640340f17 100644
--- a/docs/web/control-ui.md
+++ b/docs/web/control-ui.md
@@ -19,7 +19,7 @@ It speaks **directly to the Gateway WebSocket** on the same port.
If the Gateway is running on the same computer, open:
-- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
+- http://127.0.0.1:18789/ (or http://localhost:18789/)
If the page fails to load, start the Gateway first: `openclaw gateway`.
diff --git a/docs/web/dashboard.md b/docs/web/dashboard.md
index 5c33455f0..d68456821 100644
--- a/docs/web/dashboard.md
+++ b/docs/web/dashboard.md
@@ -12,7 +12,7 @@ The Gateway dashboard is the browser Control UI served at `/` by default
Quick open (local Gateway):
-- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
+- http://127.0.0.1:18789/ (or http://localhost:18789/)
Key references:
From 578a6e27aa76adab85471b322321d9b6d4227322 Mon Sep 17 00:00:00 2001
From: Seb Slight <19554889+sebslight@users.noreply.github.com>
Date: Fri, 6 Feb 2026 10:08:59 -0500
Subject: [PATCH 06/35] Docs: enable markdownlint autofixables except list
numbering (#10476)
* docs(markdownlint): enable autofixable rules except list numbering
* docs(zalo): fix malformed bot platform link
---
.markdownlint-cli2.jsonc | 8 +----
docs/brave-search.md | 2 +-
docs/channels/bluebubbles.md | 2 ++
docs/channels/feishu.md | 4 ++-
docs/channels/googlechat.md | 2 ++
docs/channels/line.md | 2 +-
docs/channels/matrix.md | 2 +-
docs/channels/msteams.md | 3 +-
docs/channels/nextcloud-talk.md | 2 ++
docs/channels/slack.md | 30 +++++++++----------
docs/channels/telegram.md | 1 +
docs/channels/twitch.md | 4 +--
docs/channels/whatsapp.md | 2 ++
docs/channels/zalo.md | 2 +-
docs/concepts/architecture.md | 2 ++
docs/concepts/memory.md | 4 +--
docs/concepts/model-providers.md | 6 ++--
docs/concepts/system-prompt.md | 2 +-
docs/concepts/typebox.md | 2 +-
docs/debug/node-issue.md | 8 +++--
docs/gateway/bonjour.md | 3 ++
docs/gateway/configuration.md | 8 +++--
docs/gateway/index.md | 2 ++
docs/gateway/local-models.md | 2 +-
docs/gateway/security/index.md | 6 +++-
docs/gateway/tailscale.md | 8 ++---
docs/gateway/troubleshooting.md | 11 +++++++
docs/help/faq.md | 41 +++++++++++++++++++-------
docs/help/troubleshooting.md | 2 +-
docs/hooks.md | 3 ++
docs/index.md | 2 +-
docs/install/gcp.md | 9 +++---
docs/install/northflank.mdx | 2 +-
docs/install/railway.mdx | 2 +-
docs/install/render.mdx | 8 +----
docs/install/updating.md | 5 +++-
docs/multi-agent-sandbox-tools.md | 1 +
docs/perplexity.md | 4 +--
docs/pi-dev.md | 4 +--
docs/platforms/android.md | 3 ++
docs/platforms/mac/dev-setup.md | 14 +++++----
docs/platforms/mac/webchat.md | 2 ++
docs/platforms/windows.md | 2 +-
docs/prose.md | 2 +-
docs/providers/claude-max-api-proxy.md | 6 ++--
docs/providers/deepgram.md | 4 +--
docs/providers/minimax.md | 2 +-
docs/providers/moonshot.md | 4 +--
docs/providers/ollama.md | 2 +-
docs/reference/credits.md | 4 +--
docs/token-use.md | 2 +-
docs/tools/browser-login.md | 2 +-
docs/tools/lobster.md | 4 +--
docs/tools/skills.md | 4 +--
docs/tools/web.md | 4 +--
docs/vps.md | 2 +-
docs/web/control-ui.md | 2 +-
docs/web/dashboard.md | 2 +-
58 files changed, 171 insertions(+), 109 deletions(-)
diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc
index 3f321b5af..0b6b8f0fb 100644
--- a/.markdownlint-cli2.jsonc
+++ b/.markdownlint-cli2.jsonc
@@ -1,16 +1,12 @@
{
"globs": ["docs/**/*.md", "docs/**/*.mdx", "README.md"],
- "ignores": ["docs/zh-CN/**", "docs/.i18n/**"],
+ "ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**"],
"config": {
"default": true,
"MD013": false,
"MD025": false,
- "MD026": false,
"MD029": false,
- "MD030": false,
- "MD031": false,
- "MD032": false,
"MD033": {
"allowed_elements": [
@@ -48,9 +44,7 @@
],
},
- "MD034": false,
"MD036": false,
- "MD037": false,
"MD040": false,
"MD041": false,
"MD046": false,
diff --git a/docs/brave-search.md b/docs/brave-search.md
index 260647942..ba18a6c55 100644
--- a/docs/brave-search.md
+++ b/docs/brave-search.md
@@ -12,7 +12,7 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
## Get an API key
-1. Create a Brave Search API account at https://brave.com/search/api/
+1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md
index b40fc375d..1f1ee40ea 100644
--- a/docs/channels/bluebubbles.md
+++ b/docs/channels/bluebubbles.md
@@ -27,6 +27,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
2. In the BlueBubbles config, enable the web API and set a password.
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
+
```json5
{
channels: {
@@ -39,6 +40,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
},
}
```
+
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=`).
5. Start the gateway; it will register the webhook handler and start pairing.
diff --git a/docs/channels/feishu.md b/docs/channels/feishu.md
index 2c6ba1e7f..e15feafe3 100644
--- a/docs/channels/feishu.md
+++ b/docs/channels/feishu.md
@@ -75,7 +75,7 @@ Choose **Feishu**, then enter the App ID and App Secret.
Visit [Feishu Open Platform](https://open.feishu.cn/app) and sign in.
-Lark (global) tenants should use https://open.larksuite.com/app and set `domain: "lark"` in the Feishu config.
+Lark (global) tenants should use [https://open.larksuite.com/app](https://open.larksuite.com/app) and set `domain: "lark"` in the Feishu config.
### 2. Create an app
@@ -261,10 +261,12 @@ After approval, you can chat normally.
- **Default**: `dmPolicy: "pairing"` (unknown users get a pairing code)
- **Approve pairing**:
+
```bash
openclaw pairing list feishu
openclaw pairing approve feishu
```
+
- **Allowlist mode**: set `channels.feishu.allowFrom` with allowed Open IDs
### Group chats
diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md
index 07c7dd7dc..39192ecae 100644
--- a/docs/channels/googlechat.md
+++ b/docs/channels/googlechat.md
@@ -101,6 +101,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
If prompted, visit the authorization URL shown in the output to enable Funnel for this node in your tailnet policy.
5. **Verify the configuration:**
+
```bash
tailscale serve status
tailscale funnel status
@@ -225,6 +226,7 @@ This means the webhook handler isn't registered. Common causes:
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
3. **Gateway not restarted**: After adding config, restart the gateway:
+
```bash
openclaw gateway restart
```
diff --git a/docs/channels/line.md b/docs/channels/line.md
index f68ae5aa1..d32e683fb 100644
--- a/docs/channels/line.md
+++ b/docs/channels/line.md
@@ -34,7 +34,7 @@ openclaw plugins install ./extensions/line
## Setup
1. Create a LINE Developers account and open the Console:
- https://developers.line.biz/console/
+ [https://developers.line.biz/console/](https://developers.line.biz/console/)
2. Create (or pick) a Provider and add a **Messaging API** channel.
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
4. Enable **Use webhook** in the Messaging API settings.
diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md
index a196a68b6..56b363fdd 100644
--- a/docs/channels/matrix.md
+++ b/docs/channels/matrix.md
@@ -74,7 +74,7 @@ Details: [Plugins](/plugin)
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
5. Restart the gateway (or finish onboarding).
6. Start a DM with the bot or invite it to a room from any Matrix client
- (Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
+ (Element, Beeper, etc.; see [https://matrix.org/ecosystem/clients/](https://matrix.org/ecosystem/clients/)). Beeper requires E2EE,
so set `channels.matrix.encryption: true` and verify the device.
Minimal config (access token, user ID auto-fetched):
diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md
index a18e8063d..572ff1284 100644
--- a/docs/channels/msteams.md
+++ b/docs/channels/msteams.md
@@ -558,6 +558,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
```
4. **Configure OpenClaw:**
+
```json5
{
channels: {
@@ -747,7 +748,7 @@ Bots have limited support in private channels:
- **"Icon file cannot be empty":** The manifest references icon files that are 0 bytes. Create valid PNG icons (32x32 for `outline.png`, 192x192 for `color.png`).
- **"webApplicationInfo.Id already in use":** The app is still installed in another team/chat. Find and uninstall it first, or wait 5-10 minutes for propagation.
-- **"Something went wrong" on upload:** Upload via https://admin.teams.microsoft.com instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
+- **"Something went wrong" on upload:** Upload via [https://admin.teams.microsoft.com](https://admin.teams.microsoft.com) instead, open browser DevTools (F12) → Network tab, and check the response body for the actual error.
- **Sideload failing:** Try "Upload an app to your org's app catalog" instead of "Upload a custom app" - this often bypasses sideload restrictions.
### RSC permissions not working
diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md
index edca54bc4..efecfd990 100644
--- a/docs/channels/nextcloud-talk.md
+++ b/docs/channels/nextcloud-talk.md
@@ -34,9 +34,11 @@ Details: [Plugins](/plugin)
1. Install the Nextcloud Talk plugin.
2. On your Nextcloud server, create a bot:
+
```bash
./occ talk:bot:install "OpenClaw" "" "" --feature reaction
```
+
3. Enable the bot in the target room settings.
4. Configure OpenClaw:
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
diff --git a/docs/channels/slack.md b/docs/channels/slack.md
index c8329439f..1343ebf77 100644
--- a/docs/channels/slack.md
+++ b/docs/channels/slack.md
@@ -30,7 +30,7 @@ Minimal config:
### Setup
-1. Create a Slack app (From scratch) in https://api.slack.com/apps.
+1. Create a Slack app (From scratch) in [https://api.slack.com/apps](https://api.slack.com/apps).
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
@@ -260,30 +260,30 @@ If you enable native commands, add one `slash_commands` entry per command you wa
Slack's Conversations API is type-scoped: you only need the scopes for the
conversation types you actually touch (channels, groups, im, mpim). See
-https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
+[https://docs.slack.dev/apis/web-api/using-the-conversations-api/](https://docs.slack.dev/apis/web-api/using-the-conversations-api/) for the overview.
### Bot token scopes (required)
- `chat:write` (send/update/delete messages via `chat.postMessage`)
- https://docs.slack.dev/reference/methods/chat.postMessage
+ [https://docs.slack.dev/reference/methods/chat.postMessage](https://docs.slack.dev/reference/methods/chat.postMessage)
- `im:write` (open DMs via `conversations.open` for user DMs)
- https://docs.slack.dev/reference/methods/conversations.open
+ [https://docs.slack.dev/reference/methods/conversations.open](https://docs.slack.dev/reference/methods/conversations.open)
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
- https://docs.slack.dev/reference/methods/conversations.history
+ [https://docs.slack.dev/reference/methods/conversations.history](https://docs.slack.dev/reference/methods/conversations.history)
- `channels:read`, `groups:read`, `im:read`, `mpim:read`
- https://docs.slack.dev/reference/methods/conversations.info
+ [https://docs.slack.dev/reference/methods/conversations.info](https://docs.slack.dev/reference/methods/conversations.info)
- `users:read` (user lookup)
- https://docs.slack.dev/reference/methods/users.info
+ [https://docs.slack.dev/reference/methods/users.info](https://docs.slack.dev/reference/methods/users.info)
- `reactions:read`, `reactions:write` (`reactions.get` / `reactions.add`)
- https://docs.slack.dev/reference/methods/reactions.get
- https://docs.slack.dev/reference/methods/reactions.add
+ [https://docs.slack.dev/reference/methods/reactions.get](https://docs.slack.dev/reference/methods/reactions.get)
+ [https://docs.slack.dev/reference/methods/reactions.add](https://docs.slack.dev/reference/methods/reactions.add)
- `pins:read`, `pins:write` (`pins.list` / `pins.add` / `pins.remove`)
- https://docs.slack.dev/reference/scopes/pins.read
- https://docs.slack.dev/reference/scopes/pins.write
+ [https://docs.slack.dev/reference/scopes/pins.read](https://docs.slack.dev/reference/scopes/pins.read)
+ [https://docs.slack.dev/reference/scopes/pins.write](https://docs.slack.dev/reference/scopes/pins.write)
- `emoji:read` (`emoji.list`)
- https://docs.slack.dev/reference/scopes/emoji.read
+ [https://docs.slack.dev/reference/scopes/emoji.read](https://docs.slack.dev/reference/scopes/emoji.read)
- `files:write` (uploads via `files.uploadV2`)
- https://docs.slack.dev/messaging/working-with-files/#upload
+ [https://docs.slack.dev/messaging/working-with-files/#upload](https://docs.slack.dev/messaging/working-with-files/#upload)
### User token scopes (optional, read-only by default)
@@ -302,9 +302,9 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
- `chat:write.public` (only if we want to post to channels the bot isn't in)
- https://docs.slack.dev/reference/scopes/chat.write.public
+ [https://docs.slack.dev/reference/scopes/chat.write.public](https://docs.slack.dev/reference/scopes/chat.write.public)
- `users:read.email` (only if we need email fields from `users.info`)
- https://docs.slack.dev/changelog/2017-04-narrowing-email-access
+ [https://docs.slack.dev/changelog/2017-04-narrowing-email-access](https://docs.slack.dev/changelog/2017-04-narrowing-email-access)
- `files:read` (only if we start listing/reading file metadata)
## Config
diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md
index c7fb4fe57..609daf9a6 100644
--- a/docs/channels/telegram.md
+++ b/docs/channels/telegram.md
@@ -365,6 +365,7 @@ Alternate (official Bot API):
1. DM your bot.
2. Fetch updates with your bot token and read `message.from.id`:
+
```bash
curl "https://api.telegram.org/bot/getUpdates"
```
diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md
index 7901c0427..ac46e35d6 100644
--- a/docs/channels/twitch.md
+++ b/docs/channels/twitch.md
@@ -34,7 +34,7 @@ Details: [Plugins](/plugin)
- Select **Bot Token**
- Verify scopes `chat:read` and `chat:write` are selected
- Copy the **Client ID** and **Access Token**
-3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
+3. Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/)
4. Configure the token:
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
- Or config: `channels.twitch.accessToken`
@@ -123,7 +123,7 @@ Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
-Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/ (Convert your Twitch username to ID)
+Find your Twitch user ID: [https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/](https://www.streamweasels.com/tools/convert-twitch-username-%20to-user-id/) (Convert your Twitch username to ID)
## Token refresh (optional)
diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md
index 1741ee1b7..966c0902a 100644
--- a/docs/channels/whatsapp.md
+++ b/docs/channels/whatsapp.md
@@ -205,11 +205,13 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
- `Body` is the current message body with envelope.
- Quoted reply context is **always appended**:
+
```
[Replying to +1555 id:ABC123]
>
[/Replying]
```
+
- Reply metadata also set:
- `ReplyToId` = stanzaId
- `ReplyToBody` = quoted body or media placeholder
diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md
index 0f247190c..88143dd58 100644
--- a/docs/channels/zalo.md
+++ b/docs/channels/zalo.md
@@ -57,7 +57,7 @@ It is a good fit for support or notifications where you want deterministic routi
### 1) Create a bot token (Zalo Bot Platform)
-1. Go to **https://bot.zaloplatforms.com** and sign in.
+1. Go to [https://bot.zaloplatforms.com](https://bot.zaloplatforms.com) and sign in.
2. Create a new bot and configure its settings.
3. Copy the bot token (format: `12345689:abc-xyz`).
diff --git a/docs/concepts/architecture.md b/docs/concepts/architecture.md
index a1c7f3383..a9676b171 100644
--- a/docs/concepts/architecture.md
+++ b/docs/concepts/architecture.md
@@ -110,9 +110,11 @@ Details: [Gateway protocol](/gateway/protocol), [Pairing](/start/pairing),
- Preferred: Tailscale or VPN.
- Alternative: SSH tunnel
+
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
+
- The same handshake + auth token apply over the tunnel.
- TLS + optional pinning can be enabled for WS in remote setups.
diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md
index 4b499860b..ea5c08002 100644
--- a/docs/concepts/memory.md
+++ b/docs/concepts/memory.md
@@ -302,8 +302,8 @@ Why OpenAI batch is fast + cheap:
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
- See the OpenAI Batch API docs and pricing for details:
- - https://platform.openai.com/docs/api-reference/batch
- - https://platform.openai.com/pricing
+ - [https://platform.openai.com/docs/api-reference/batch](https://platform.openai.com/docs/api-reference/batch)
+ - [https://platform.openai.com/pricing](https://platform.openai.com/pricing)
Config example:
diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md
index 4d313cf0f..fba56a34a 100644
--- a/docs/concepts/model-providers.md
+++ b/docs/concepts/model-providers.md
@@ -136,14 +136,14 @@ Moonshot uses OpenAI-compatible endpoints, so configure it as a custom provider:
Kimi K2 model IDs:
-{/_ moonshot-kimi-k2-model-refs:start _/ && null}
+{/_moonshot-kimi-k2-model-refs:start_/ && null}
- `moonshot/kimi-k2.5`
- `moonshot/kimi-k2-0905-preview`
- `moonshot/kimi-k2-turbo-preview`
- `moonshot/kimi-k2-thinking`
- `moonshot/kimi-k2-thinking-turbo`
- {/_ moonshot-kimi-k2-model-refs:end _/ && null}
+ {/_moonshot-kimi-k2-model-refs:end_/ && null}
```json5
{
@@ -242,7 +242,7 @@ Ollama is a local LLM runtime that provides an OpenAI-compatible API:
- Provider: `ollama`
- Auth: None required (local server)
- Example model: `ollama/llama3.3`
-- Installation: https://ollama.ai
+- Installation: [https://ollama.ai](https://ollama.ai)
```bash
# Install Ollama, then pull a model:
diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md
index aafa80473..acb2bf8b5 100644
--- a/docs/concepts/system-prompt.md
+++ b/docs/concepts/system-prompt.md
@@ -110,6 +110,6 @@ This keeps the base prompt small while still enabling targeted skill usage.
When available, the system prompt includes a **Documentation** section that points to the
local OpenClaw docs directory (either `docs/` in the repo workspace or the bundled npm
package docs) and also notes the public mirror, source repo, community Discord, and
-ClawHub (https://clawhub.com) for skills discovery. The prompt instructs the model to consult local docs first
+ClawHub ([https://clawhub.com](https://clawhub.com)) for skills discovery. The prompt instructs the model to consult local docs first
for OpenClaw behavior, commands, configuration, or architecture, and to run
`openclaw status` itself when possible (asking the user only when it lacks access).
diff --git a/docs/concepts/typebox.md b/docs/concepts/typebox.md
index 38ee7d8ca..f60c5b8ef 100644
--- a/docs/concepts/typebox.md
+++ b/docs/concepts/typebox.md
@@ -280,7 +280,7 @@ Unknown frame types are preserved as raw payloads for forward compatibility.
Generated JSON Schema is in the repo at `dist/protocol.schema.json`. The
published raw file is typically available at:
-- https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json
+- [https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json](https://raw.githubusercontent.com/openclaw/openclaw/main/dist/protocol.schema.json)
## When you change schemas
diff --git a/docs/debug/node-issue.md b/docs/debug/node-issue.md
index ce46b1a05..8355d2abc 100644
--- a/docs/debug/node-issue.md
+++ b/docs/debug/node-issue.md
@@ -62,19 +62,21 @@ node --import tsx scripts/repro/tsx-name-repro.ts
- Use Bun for dev scripts (current temporary revert).
- Use Node + tsc watch, then run compiled output:
+
```bash
pnpm exec tsc --watch --preserveWatchOutput
node --watch openclaw.mjs status
```
+
- Confirmed locally: `pnpm exec tsc -p tsconfig.json` + `node openclaw.mjs status` works on Node 25.
- Disable esbuild keepNames in the TS loader if possible (prevents `__name` helper insertion); tsx does not currently expose this.
- Test Node LTS (22/24) with `tsx` to see if the issue is Node 25–specific.
## References
-- https://opennext.js.org/cloudflare/howtos/keep_names
-- https://esbuild.github.io/api/#keep-names
-- https://github.com/evanw/esbuild/issues/1031
+- [https://opennext.js.org/cloudflare/howtos/keep_names](https://opennext.js.org/cloudflare/howtos/keep_names)
+- [https://esbuild.github.io/api/#keep-names](https://esbuild.github.io/api/#keep-names)
+- [https://github.com/evanw/esbuild/issues/1031](https://github.com/evanw/esbuild/issues/1031)
## Next steps
diff --git a/docs/gateway/bonjour.md b/docs/gateway/bonjour.md
index b8f08741e..9e2ad8753 100644
--- a/docs/gateway/bonjour.md
+++ b/docs/gateway/bonjour.md
@@ -105,10 +105,13 @@ The Gateway advertises small non‑secret hints to make UI flows convenient:
Useful built‑in tools:
- Browse instances:
+
```bash
dns-sd -B _openclaw-gw._tcp local.
```
+
- Resolve one instance (replace ``):
+
```bash
dns-sd -L "" _openclaw-gw._tcp local.
```
diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md
index 545a937df..639f84bd6 100644
--- a/docs/gateway/configuration.md
+++ b/docs/gateway/configuration.md
@@ -1978,11 +1978,13 @@ Block streaming:
- `agents.defaults.blockStreamingChunk`: soft chunking for streamed blocks. Defaults to
800–1200 chars, prefers paragraph breaks (`\n\n`), then newlines, then sentences.
Example:
+
```json5
{
agents: { defaults: { blockStreamingChunk: { minChars: 800, maxChars: 1200 } } },
}
```
+
- `agents.defaults.blockStreamingCoalesce`: merge streamed blocks before sending.
Defaults to `{ idleMs: 1000 }` and inherits `minChars` from `blockStreamingChunk`
with `maxChars` capped to the channel text limit. Signal/Slack/Discord/Google Chat default
@@ -1996,11 +1998,13 @@ Block streaming:
Modes: `off` (default), `natural` (800–2500ms), `custom` (use `minMs`/`maxMs`).
Per-agent override: `agents.list[].humanDelay`.
Example:
+
```json5
{
agents: { defaults: { humanDelay: { mode: "natural" } } },
}
```
+
See [/concepts/streaming](/concepts/streaming) for behavior + chunking details.
Typing indicators:
@@ -2066,7 +2070,7 @@ of `every`, keep `HEARTBEAT.md` tiny, and/or choose a cheaper `model`.
- `tools.web.fetch.readability` (default true; disable to use basic HTML cleanup only)
- `tools.web.fetch.firecrawl.enabled` (default true when an API key is set)
- `tools.web.fetch.firecrawl.apiKey` (optional; defaults to `FIRECRAWL_API_KEY`)
-- `tools.web.fetch.firecrawl.baseUrl` (default https://api.firecrawl.dev)
+- `tools.web.fetch.firecrawl.baseUrl` (default [https://api.firecrawl.dev](https://api.firecrawl.dev))
- `tools.web.fetch.firecrawl.onlyMainContent` (default true)
- `tools.web.fetch.firecrawl.maxAgeMs` (optional)
- `tools.web.fetch.firecrawl.timeoutSeconds` (optional)
@@ -2482,7 +2486,7 @@ Select the model via `agents.defaults.model.primary` (provider/model).
OpenCode Zen is a multi-model gateway with per-model endpoints. OpenClaw uses
the built-in `opencode` provider from pi-ai; set `OPENCODE_API_KEY` (or
-`OPENCODE_ZEN_API_KEY`) from https://opencode.ai/auth.
+`OPENCODE_ZEN_API_KEY`) from [https://opencode.ai/auth](https://opencode.ai/auth).
Notes:
diff --git a/docs/gateway/index.md b/docs/gateway/index.md
index 06dd72c13..64697f1f4 100644
--- a/docs/gateway/index.md
+++ b/docs/gateway/index.md
@@ -49,9 +49,11 @@ pnpm gateway:watch
## Remote access
- Tailscale/VPN preferred; otherwise SSH tunnel:
+
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
+
- Clients then connect to `ws://127.0.0.1:18789` through the tunnel.
- If a token is configured, clients must include it in `connect.params.auth.token` even over the tunnel.
diff --git a/docs/gateway/local-models.md b/docs/gateway/local-models.md
index fe715ab05..3f7e13d41 100644
--- a/docs/gateway/local-models.md
+++ b/docs/gateway/local-models.md
@@ -52,7 +52,7 @@ Best current local stack. Load MiniMax M2.1 in LM Studio, enable the local serve
**Setup checklist**
-- Install LM Studio: https://lmstudio.ai
+- Install LM Studio: [https://lmstudio.ai](https://lmstudio.ai)
- In LM Studio, download the **largest MiniMax M2.1 build available** (avoid “small”/heavily quantized variants), start the server, confirm `http://127.0.0.1:1234/v1/models` lists it.
- Keep the model loaded; cold-load adds startup latency.
- Adjust `contextWindow`/`maxTokens` if your LM Studio build differs.
diff --git a/docs/gateway/security/index.md b/docs/gateway/security/index.md
index c6b521048..f6bd91734 100644
--- a/docs/gateway/security/index.md
+++ b/docs/gateway/security/index.md
@@ -773,18 +773,22 @@ If it fails, there are new candidates not yet in the baseline.
### If CI fails
1. Reproduce locally:
+
```bash
detect-secrets scan --baseline .secrets.baseline
```
+
2. Understand the tools:
- `detect-secrets scan` finds candidates and compares them to the baseline.
- `detect-secrets audit` opens an interactive review to mark each baseline
item as real or false positive.
3. For real secrets: rotate/remove them, then re-run the scan to update the baseline.
4. For false positives: run the interactive audit and mark them as false:
+
```bash
detect-secrets audit .secrets.baseline
```
+
5. If you need new excludes, add them to `.detect-secrets.cfg` and regenerate the
baseline with matching `--exclude-files` / `--exclude-lines` flags (the config
file is reference-only; detect-secrets doesn’t read it automatically).
@@ -814,7 +818,7 @@ Mario asking for find ~
Found a vulnerability in OpenClaw? Please report responsibly:
-1. Email: security@openclaw.ai
+1. Email: [security@openclaw.ai](mailto:security@openclaw.ai)
2. Don't post publicly until fixed
3. We'll credit you (unless you prefer anonymity)
diff --git a/docs/gateway/tailscale.md b/docs/gateway/tailscale.md
index 3f4daa111..3a12b7fe1 100644
--- a/docs/gateway/tailscale.md
+++ b/docs/gateway/tailscale.md
@@ -121,7 +121,7 @@ Avoid Funnel for browser control; treat node pairing like operator access.
## Learn more
-- Tailscale Serve overview: https://tailscale.com/kb/1312/serve
-- `tailscale serve` command: https://tailscale.com/kb/1242/tailscale-serve
-- Tailscale Funnel overview: https://tailscale.com/kb/1223/tailscale-funnel
-- `tailscale funnel` command: https://tailscale.com/kb/1311/tailscale-funnel
+- Tailscale Serve overview: [https://tailscale.com/kb/1312/serve](https://tailscale.com/kb/1312/serve)
+- `tailscale serve` command: [https://tailscale.com/kb/1242/tailscale-serve](https://tailscale.com/kb/1242/tailscale-serve)
+- Tailscale Funnel overview: [https://tailscale.com/kb/1223/tailscale-funnel](https://tailscale.com/kb/1223/tailscale-funnel)
+- `tailscale funnel` command: [https://tailscale.com/kb/1311/tailscale-funnel](https://tailscale.com/kb/1311/tailscale-funnel)
diff --git a/docs/gateway/troubleshooting.md b/docs/gateway/troubleshooting.md
index d9aa303cd..5f9d51f1d 100644
--- a/docs/gateway/troubleshooting.md
+++ b/docs/gateway/troubleshooting.md
@@ -42,9 +42,11 @@ Fix options:
- Re-run onboarding and choose **Anthropic** for that agent.
- Or paste a setup-token on the **gateway host**:
+
```bash
openclaw models auth setup-token --provider anthropic
```
+
- Or copy `auth-profiles.json` from the main agent dir to the new agent dir.
Verify:
@@ -120,13 +122,17 @@ Doctor/service will show runtime state (PID/last exit) and log hints.
**Enable more logging:**
- Bump file log detail (persisted JSONL):
+
```json
{ "logging": { "level": "debug" } }
```
+
- Bump console verbosity (TTY output only):
+
```json
{ "logging": { "consoleLevel": "debug", "consoleStyle": "pretty" } }
```
+
- Quick tip: `--verbose` affects **console** output only. File logs remain controlled by `logging.level`.
See [/logging](/logging) for a full overview of formats, config, and access.
@@ -139,10 +145,13 @@ Gateway refuses to start.
**Fix (recommended):**
- Run the wizard and set the Gateway run mode to **Local**:
+
```bash
openclaw configure
```
+
- Or set it directly:
+
```bash
openclaw config set gateway.mode local
```
@@ -150,6 +159,7 @@ Gateway refuses to start.
**If you meant to run a remote Gateway instead:**
- Set a remote URL and keep `gateway.mode=remote`:
+
```bash
openclaw config set gateway.mode remote
openclaw config set gateway.remote.url "wss://gateway.example.com"
@@ -554,6 +564,7 @@ Notes:
- The git flow only rebases if the repo is clean. Commit or stash changes first.
- After switching, run:
+
```bash
openclaw doctor
openclaw gateway restart
diff --git a/docs/help/faq.md b/docs/help/faq.md
index 191d2be1e..fda1acddb 100644
--- a/docs/help/faq.md
+++ b/docs/help/faq.md
@@ -252,10 +252,12 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
Repairs/migrates config/state + runs health checks. See [Doctor](/gateway/doctor).
7. **Gateway snapshot**
+
```bash
openclaw health --json
openclaw health --verbose # shows the target URL + config path on errors
```
+
Asks the running gateway for a full snapshot (WS-only). See [Health](/gateway/health).
## Quick start and first-run setup
@@ -266,8 +268,8 @@ Use a local AI agent that can **see your machine**. That is far more effective t
in Discord, because most "I'm stuck" cases are **local config or environment issues** that
remote helpers cannot inspect.
-- **Claude Code**: https://www.anthropic.com/claude-code/
-- **OpenAI Codex**: https://openai.com/codex/
+- **Claude Code**: [https://www.anthropic.com/claude-code/](https://www.anthropic.com/claude-code/)
+- **OpenAI Codex**: [https://openai.com/codex/](https://openai.com/codex/)
These tools can read the repo, run commands, inspect logs, and help fix your machine-level
setup (PATH, services, permissions, auth files). Give them the **full source checkout** via
@@ -285,8 +287,8 @@ Tip: ask the agent to **plan and supervise** the fix (step-by-step), then execut
necessary commands. That keeps changes small and easier to audit.
If you discover a real bug or fix, please file a GitHub issue or send a PR:
-https://github.com/openclaw/openclaw/issues
-https://github.com/openclaw/openclaw/pulls
+[https://github.com/openclaw/openclaw/issues](https://github.com/openclaw/openclaw/issues)
+[https://github.com/openclaw/openclaw/pulls](https://github.com/openclaw/openclaw/pulls)
Start with these commands (share outputs when asking for help):
@@ -432,7 +434,7 @@ Related: [Migrating](/install/migrating), [Where things live on disk](/help/faq#
### Where do I see what is new in the latest version
Check the GitHub changelog:
-https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
+[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
Newest entries are at the top. If the top section is marked **Unreleased**, the next dated
section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and
@@ -443,10 +445,10 @@ section is the latest shipped version. Entries are grouped by **Highlights**, **
Some Comcast/Xfinity connections incorrectly block `docs.openclaw.ai` via Xfinity
Advanced Security. Disable it or allowlist `docs.openclaw.ai`, then retry. More
detail: [Troubleshooting](/help/troubleshooting#docsopenclawai-shows-an-ssl-error-comcastxfinity).
-Please help us unblock it by reporting here: https://spa.xfinity.com/check_url_status.
+Please help us unblock it by reporting here: [https://spa.xfinity.com/check_url_status](https://spa.xfinity.com/check_url_status).
If you still can't reach the site, the docs are mirrored on GitHub:
-https://github.com/openclaw/openclaw/tree/main/docs
+[https://github.com/openclaw/openclaw/tree/main/docs](https://github.com/openclaw/openclaw/tree/main/docs)
### What's the difference between stable and beta
@@ -460,7 +462,7 @@ that same version to `latest`**. That's why beta and stable can point at the
**same version**.
See what changed:
-https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md
+[https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md](https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md)
### How do I install the beta version and whats the difference between beta and dev
@@ -478,7 +480,7 @@ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -
```
Windows installer (PowerShell):
-https://openclaw.ai/install.ps1
+[https://openclaw.ai/install.ps1](https://openclaw.ai/install.ps1)
More detail: [Development channels](/install/development-channels) and [Installer flags](/install/installer).
@@ -559,9 +561,11 @@ Two common Windows issues:
- Your npm global bin folder is not on PATH.
- Check the path:
+
```powershell
npm config get prefix
```
+
- Ensure `\\bin` is on PATH (on most systems it is `%AppData%\\npm`).
- Close and reopen PowerShell after updating PATH.
@@ -988,7 +992,7 @@ Advantages:
- **Always-on Gateway** (run on a VPS, interact from anywhere)
- **Nodes** for local browser/screen/camera/exec
-Showcase: https://openclaw.ai/showcase
+Showcase: [https://openclaw.ai/showcase](https://openclaw.ai/showcase)
## Skills and automation
@@ -1046,7 +1050,7 @@ Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-v
### How do I install skills on Linux
Use **ClawHub** (CLI) or drop skills into your workspace. The macOS Skills UI isn't available on Linux.
-Browse skills at https://clawhub.com.
+Browse skills at [https://clawhub.com](https://clawhub.com).
Install the ClawHub CLI (pick one package manager):
@@ -1085,13 +1089,16 @@ Run the Gateway on Linux, pair a macOS node (menubar app), and set **Node Run Co
Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wrappers that run on a Mac. Then override the skill to allow Linux so it stays eligible.
1. Create an SSH wrapper for the binary (example: `memo` for Apple Notes):
+
```bash
#!/usr/bin/env bash
set -euo pipefail
exec ssh -T user@mac-host /opt/homebrew/bin/memo "$@"
```
+
2. Put the wrapper on `PATH` on the Linux host (for example `~/bin/memo`).
3. Override the skill metadata (workspace or `~/.openclaw/skills`) to allow Linux:
+
```markdown
---
name: apple-notes
@@ -1099,6 +1106,7 @@ Keep the Gateway on Linux, but make the required CLI binaries resolve to SSH wra
metadata: { "openclaw": { "os": ["darwin", "linux"], "requires": { "bins": ["memo"] } } }
---
```
+
4. Start a new session so the skills snapshot refreshes.
### Do you have a Notion or HeyGen integration
@@ -1473,6 +1481,7 @@ Typical setup:
4. Open the macOS app locally and connect in **Remote over SSH** mode (or direct tailnet)
so it can register as a node.
5. Approve the node on the Gateway:
+
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1610,10 +1619,12 @@ This sets your workspace and restricts who can trigger the bot.
Minimal steps:
1. **Install + login on the VPS**
+
```bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
```
+
2. **Install + login on your Mac**
- Use the Tailscale app and sign in to the same tailnet.
3. **Enable MagicDNS (recommended)**
@@ -1640,6 +1651,7 @@ Recommended setup:
2. **Use the macOS app in Remote mode** (SSH target can be the tailnet hostname).
The app will tunnel the Gateway port and connect as a node.
3. **Approve the node** on the gateway:
+
```bash
openclaw nodes pending
openclaw nodes approve
@@ -1702,9 +1714,11 @@ If the Gateway runs as a service (launchd/systemd), it won't inherit your shell
environment. Fix by doing one of these:
1. Put the token in `~/.openclaw/.env`:
+
```
COPILOT_GITHUB_TOKEN=...
```
+
2. Or enable shell import (`env.shellEnv.enabled: true`).
3. Or add it to your config `env` block (applies only if missing).
@@ -1801,6 +1815,7 @@ Use one of these:
or `/compact ` to guide the summary.
- **Reset** (fresh session ID for the same chat key):
+
```
/new
/reset
@@ -2071,9 +2086,11 @@ Fix checklist:
3. Use the exact model id (case-sensitive): `minimax/MiniMax-M2.1` or
`minimax/MiniMax-M2.1-lightning`.
4. Run:
+
```bash
openclaw models list
```
+
and pick from the list (or `/model list` in chat).
See [MiniMax](/providers/minimax) and [Models](/concepts/models).
@@ -2238,9 +2255,11 @@ can't find it in its auth store.
- **If you want to use an API key instead**
- Put `ANTHROPIC_API_KEY` in `~/.openclaw/.env` on the **gateway host**.
- Clear any pinned order that forces a missing profile:
+
```bash
openclaw models auth order clear --provider anthropic
```
+
- **Confirm you're running commands on the gateway host**
- In remote mode, auth profiles live on the gateway machine, not your laptop.
diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md
index 03896a916..2b201c5e9 100644
--- a/docs/help/troubleshooting.md
+++ b/docs/help/troubleshooting.md
@@ -65,7 +65,7 @@ You can also set `OPENCLAW_VERBOSE=1` instead of the flag.
Some Comcast/Xfinity connections block `docs.openclaw.ai` via Xfinity Advanced Security.
Disable Advanced Security or add `docs.openclaw.ai` to the allowlist, then retry.
-- Xfinity Advanced Security help: https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security
+- Xfinity Advanced Security help: [https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security](https://www.xfinity.com/support/articles/using-xfinity-xfi-advanced-security)
- Quick sanity checks: try a mobile hotspot or VPN to confirm it’s ISP-level filtering
### Service says running, but RPC probe fails
diff --git a/docs/hooks.md b/docs/hooks.md
index a4a3a95df..dfcd61ca1 100644
--- a/docs/hooks.md
+++ b/docs/hooks.md
@@ -787,6 +787,7 @@ Session reset
```
3. List all discovered hooks:
+
```bash
openclaw hooks list
```
@@ -818,6 +819,7 @@ Look for missing:
2. Restart your gateway process so hooks reload.
3. Check gateway logs for errors:
+
```bash
./scripts/clawlog.sh | grep hook
```
@@ -892,6 +894,7 @@ node -e "import('./path/to/handler.ts').then(console.log)"
```
4. Verify and restart your gateway process:
+
```bash
openclaw hooks list
# Should show: 🎯 my-hook ✓
diff --git a/docs/index.md b/docs/index.md
index 651f98440..60c59bb7f 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -120,7 +120,7 @@ Need the full install and dev setup? See [Quick start](/start/quickstart).
Open the browser Control UI after the Gateway starts.
-- Local default: http://127.0.0.1:18789/
+- Local default: [http://127.0.0.1:18789/](http://127.0.0.1:18789/)
- Remote access: [Web surfaces](/web) and [Tailscale](/gateway/tailscale)
diff --git a/docs/install/gcp.md b/docs/install/gcp.md
index 172a32ca8..6026fd87d 100644
--- a/docs/install/gcp.md
+++ b/docs/install/gcp.md
@@ -69,7 +69,7 @@ For the generic Docker flow, see [Docker](/install/docker).
**Option A: gcloud CLI** (recommended for automation)
-Install from https://cloud.google.com/sdk/docs/install
+Install from [https://cloud.google.com/sdk/docs/install](https://cloud.google.com/sdk/docs/install)
Initialize and authenticate:
@@ -80,7 +80,7 @@ gcloud auth login
**Option B: Cloud Console**
-All steps can be done via the web UI at https://console.cloud.google.com
+All steps can be done via the web UI at [https://console.cloud.google.com](https://console.cloud.google.com)
---
@@ -93,7 +93,7 @@ gcloud projects create my-openclaw-project --name="OpenClaw Gateway"
gcloud config set project my-openclaw-project
```
-Enable billing at https://console.cloud.google.com/billing (required for Compute Engine).
+Enable billing at [https://console.cloud.google.com/billing](https://console.cloud.google.com/billing) (required for Compute Engine).
Enable the Compute Engine API:
@@ -484,6 +484,7 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
```
2. Grant Compute Instance Admin role (or narrower custom role):
+
```bash
gcloud projects add-iam-policy-binding my-openclaw-project \
--member="serviceAccount:openclaw-deploy@my-openclaw-project.iam.gserviceaccount.com" \
@@ -492,7 +493,7 @@ For automation or CI/CD pipelines, create a dedicated service account with minim
Avoid using the Owner role for automation. Use the principle of least privilege.
-See https://cloud.google.com/iam/docs/understanding-roles for IAM role details.
+See [https://cloud.google.com/iam/docs/understanding-roles](https://cloud.google.com/iam/docs/understanding-roles) for IAM role details.
---
diff --git a/docs/install/northflank.mdx b/docs/install/northflank.mdx
index 8c1ff33ec..d3157d72e 100644
--- a/docs/install/northflank.mdx
+++ b/docs/install/northflank.mdx
@@ -45,7 +45,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to https://discord.com/developers/applications
+1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/railway.mdx b/docs/install/railway.mdx
index b27d94203..73f23fbe4 100644
--- a/docs/install/railway.mdx
+++ b/docs/install/railway.mdx
@@ -83,7 +83,7 @@ If Telegram DMs are set to pairing, the setup wizard can approve the pairing cod
### Discord bot token
-1. Go to https://discord.com/developers/applications
+1. Go to [https://discord.com/developers/applications](https://discord.com/developers/applications)
2. **New Application** → choose a name
3. **Bot** → **Add Bot**
4. **Enable MESSAGE CONTENT INTENT** under Bot → Privileged Gateway Intents (required or the bot will crash on startup)
diff --git a/docs/install/render.mdx b/docs/install/render.mdx
index a682d61c9..ae9456870 100644
--- a/docs/install/render.mdx
+++ b/docs/install/render.mdx
@@ -11,13 +11,7 @@ Deploy OpenClaw on Render using Infrastructure as Code. The included `render.yam
## Deploy with a Render Blueprint
-
- Deploy to Render
-
+[Deploy to Render](https://render.com/deploy?repo=https://github.com/openclaw/openclaw)
Clicking this link will:
diff --git a/docs/install/updating.md b/docs/install/updating.md
index ae4b3d1eb..e463a5001 100644
--- a/docs/install/updating.md
+++ b/docs/install/updating.md
@@ -24,10 +24,13 @@ Notes:
- Add `--no-onboard` if you don’t want the onboarding wizard to run again.
- For **source installs**, use:
+
```bash
curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git --no-onboard
```
+
The installer will `git pull --rebase` **only** if the repo is clean.
+
- For **global installs**, the script uses `npm install -g openclaw@latest` under the hood.
- Legacy note: `clawdbot` remains available as a compatibility shim.
@@ -225,4 +228,4 @@ git pull
- Run `openclaw doctor` again and read the output carefully (it often tells you the fix).
- Check: [Troubleshooting](/gateway/troubleshooting)
-- Ask in Discord: https://discord.gg/clawd
+- Ask in Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
diff --git a/docs/multi-agent-sandbox-tools.md b/docs/multi-agent-sandbox-tools.md
index a02af8d53..e7de9caf8 100644
--- a/docs/multi-agent-sandbox-tools.md
+++ b/docs/multi-agent-sandbox-tools.md
@@ -362,6 +362,7 @@ After configuring multi-agent sandbox and tools:
- Verify the agent cannot use denied tools
4. **Monitor logs:**
+
```exec
tail -f "${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/logs/gateway.log" | grep -E "routing|sandbox|tools"
```
diff --git a/docs/perplexity.md b/docs/perplexity.md
index 46c4f12b9..178a7c360 100644
--- a/docs/perplexity.md
+++ b/docs/perplexity.md
@@ -15,12 +15,12 @@ through Perplexity’s direct API or via OpenRouter.
### Perplexity (direct)
-- Base URL: https://api.perplexity.ai
+- Base URL: [https://api.perplexity.ai](https://api.perplexity.ai)
- Environment variable: `PERPLEXITY_API_KEY`
### OpenRouter (alternative)
-- Base URL: https://openrouter.ai/api/v1
+- Base URL: [https://openrouter.ai/api/v1](https://openrouter.ai/api/v1)
- Environment variable: `OPENROUTER_API_KEY`
- Supports prepaid/crypto credits.
diff --git a/docs/pi-dev.md b/docs/pi-dev.md
index e850b8dc7..2eeebdcc2 100644
--- a/docs/pi-dev.md
+++ b/docs/pi-dev.md
@@ -66,5 +66,5 @@ If you only want to reset sessions, delete `agents//sessions/` and `age
## References
-- https://docs.openclaw.ai/testing
-- https://docs.openclaw.ai/start/getting-started
+- [https://docs.openclaw.ai/testing](https://docs.openclaw.ai/testing)
+- [https://docs.openclaw.ai/start/getting-started](https://docs.openclaw.ai/start/getting-started)
diff --git a/docs/platforms/android.md b/docs/platforms/android.md
index 6e395994b..b786e1782 100644
--- a/docs/platforms/android.md
+++ b/docs/platforms/android.md
@@ -98,10 +98,13 @@ Pairing details: [Gateway pairing](/gateway/pairing).
### 5) Verify the node is connected
- Via nodes status:
+
```bash
openclaw nodes status
```
+
- Via Gateway:
+
```bash
openclaw gateway call node.list --params "{}"
```
diff --git a/docs/platforms/mac/dev-setup.md b/docs/platforms/mac/dev-setup.md
index 39d3125d8..8aff51348 100644
--- a/docs/platforms/mac/dev-setup.md
+++ b/docs/platforms/mac/dev-setup.md
@@ -13,8 +13,8 @@ This guide covers the necessary steps to build and run the OpenClaw macOS applic
Before building the app, ensure you have the following installed:
-1. **Xcode 26.2+**: Required for Swift development.
-2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
+1. **Xcode 26.2+**: Required for Swift development.
+2. **Node.js 22+ & pnpm**: Required for the gateway, CLI, and packaging scripts.
## 1. Install Dependencies
@@ -35,7 +35,7 @@ To build the macOS app and package it into `dist/OpenClaw.app`, run:
If you don't have an Apple Developer ID certificate, the script will automatically use **ad-hoc signing** (`-`).
For dev run modes, signing flags, and Team ID troubleshooting, see the macOS app README:
-https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md
+[https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md](https://github.com/openclaw/openclaw/blob/main/apps/macos/README.md)
> **Note**: Ad-hoc signed apps may trigger security prompts. If the app crashes immediately with "Abort trap 6", see the [Troubleshooting](#troubleshooting) section.
@@ -45,9 +45,9 @@ The macOS app expects a global `openclaw` CLI install to manage background tasks
**To install it (recommended):**
-1. Open the OpenClaw app.
-2. Go to the **General** settings tab.
-3. Click **"Install CLI"**.
+1. Open the OpenClaw app.
+2. Go to the **General** settings tab.
+3. Click **"Install CLI"**.
Alternatively, install it manually:
@@ -82,9 +82,11 @@ If the app crashes when you try to allow **Speech Recognition** or **Microphone*
**Fix:**
1. Reset the TCC permissions:
+
```bash
tccutil reset All bot.molt.mac.debug
```
+
2. If that fails, change the `BUNDLE_ID` temporarily in [`scripts/package-mac-app.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/package-mac-app.sh) to force a "clean slate" from macOS.
### Gateway "Starting..." indefinitely
diff --git a/docs/platforms/mac/webchat.md b/docs/platforms/mac/webchat.md
index 5f654e174..ea6791ff5 100644
--- a/docs/platforms/mac/webchat.md
+++ b/docs/platforms/mac/webchat.md
@@ -19,9 +19,11 @@ agent (with a session switcher for other sessions).
- Manual: Lobster menu → “Open Chat”.
- Auto‑open for testing:
+
```bash
dist/OpenClaw.app/Contents/MacOS/OpenClaw --webchat
```
+
- Logs: `./scripts/clawlog.sh` (subsystem `bot.molt`, category `WebChatSwiftUI`).
## How it’s wired
diff --git a/docs/platforms/windows.md b/docs/platforms/windows.md
index e89cae95e..d15131486 100644
--- a/docs/platforms/windows.md
+++ b/docs/platforms/windows.md
@@ -20,7 +20,7 @@ Native Windows companion apps are planned.
- [Getting Started](/start/getting-started) (use inside WSL)
- [Install & updates](/install/updating)
-- Official WSL2 guide (Microsoft): https://learn.microsoft.com/windows/wsl/install
+- Official WSL2 guide (Microsoft): [https://learn.microsoft.com/windows/wsl/install](https://learn.microsoft.com/windows/wsl/install)
## Gateway
diff --git a/docs/prose.md b/docs/prose.md
index 4b825c467..7b4b8c002 100644
--- a/docs/prose.md
+++ b/docs/prose.md
@@ -11,7 +11,7 @@ title: "OpenProse"
OpenProse is a portable, markdown-first workflow format for orchestrating AI sessions. In OpenClaw it ships as a plugin that installs an OpenProse skill pack plus a `/prose` slash command. Programs live in `.prose` files and can spawn multiple sub-agents with explicit control flow.
-Official site: https://www.prose.md
+Official site: [https://www.prose.md](https://www.prose.md)
## What it can do
diff --git a/docs/providers/claude-max-api-proxy.md b/docs/providers/claude-max-api-proxy.md
index 997023312..11b830710 100644
--- a/docs/providers/claude-max-api-proxy.md
+++ b/docs/providers/claude-max-api-proxy.md
@@ -131,9 +131,9 @@ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.claude-max-api.plist
## Links
-- **npm:** https://www.npmjs.com/package/claude-max-api-proxy
-- **GitHub:** https://github.com/atalovesyou/claude-max-api-proxy
-- **Issues:** https://github.com/atalovesyou/claude-max-api-proxy/issues
+- **npm:** [https://www.npmjs.com/package/claude-max-api-proxy](https://www.npmjs.com/package/claude-max-api-proxy)
+- **GitHub:** [https://github.com/atalovesyou/claude-max-api-proxy](https://github.com/atalovesyou/claude-max-api-proxy)
+- **Issues:** [https://github.com/atalovesyou/claude-max-api-proxy/issues](https://github.com/atalovesyou/claude-max-api-proxy/issues)
## Notes
diff --git a/docs/providers/deepgram.md b/docs/providers/deepgram.md
index cf32467e5..b7a21fa6f 100644
--- a/docs/providers/deepgram.md
+++ b/docs/providers/deepgram.md
@@ -15,8 +15,8 @@ When enabled, OpenClaw uploads the audio file to Deepgram and injects the transc
into the reply pipeline (`{{Transcript}}` + `[Audio]` block). This is **not streaming**;
it uses the pre-recorded transcription endpoint.
-Website: https://deepgram.com
-Docs: https://developers.deepgram.com
+Website: [https://deepgram.com](https://deepgram.com)
+Docs: [https://developers.deepgram.com](https://developers.deepgram.com)
## Quick start
diff --git a/docs/providers/minimax.md b/docs/providers/minimax.md
index f19478a49..294388fbc 100644
--- a/docs/providers/minimax.md
+++ b/docs/providers/minimax.md
@@ -179,7 +179,7 @@ Use the interactive config wizard to set MiniMax without editing JSON:
- Model refs are `minimax/`.
- Coding Plan usage API: `https://api.minimaxi.com/v1/api/openplatform/coding_plan/remains` (requires a coding plan key).
- Update pricing values in `models.json` if you need exact cost tracking.
-- Referral link for MiniMax Coding Plan (10% off): https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link
+- Referral link for MiniMax Coding Plan (10% off): [https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link](https://platform.minimax.io/subscribe/coding-plan?code=DbXJTRClnb&source=link)
- See [/concepts/model-providers](/concepts/model-providers) for provider rules.
- Use `openclaw models list` and `openclaw models set minimax/MiniMax-M2.1` to switch.
diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md
index 6e6ec5295..0a46c9067 100644
--- a/docs/providers/moonshot.md
+++ b/docs/providers/moonshot.md
@@ -15,14 +15,14 @@ Kimi Coding with `kimi-coding/k2p5`.
Current Kimi K2 model IDs:
-{/_ moonshot-kimi-k2-ids:start _/ && null}
+{/_moonshot-kimi-k2-ids:start_/ && null}
- `kimi-k2.5`
- `kimi-k2-0905-preview`
- `kimi-k2-turbo-preview`
- `kimi-k2-thinking`
- `kimi-k2-thinking-turbo`
- {/_ moonshot-kimi-k2-ids:end _/ && null}
+ {/_moonshot-kimi-k2-ids:end_/ && null}
```bash
openclaw onboard --auth-choice moonshot-api-key
diff --git a/docs/providers/ollama.md b/docs/providers/ollama.md
index 9d2f177bf..463923fb7 100644
--- a/docs/providers/ollama.md
+++ b/docs/providers/ollama.md
@@ -12,7 +12,7 @@ Ollama is a local LLM runtime that makes it easy to run open-source models on yo
## Quick start
-1. Install Ollama: https://ollama.ai
+1. Install Ollama: [https://ollama.ai](https://ollama.ai)
2. Pull a model:
diff --git a/docs/reference/credits.md b/docs/reference/credits.md
index e9ba9bca3..67e85ca72 100644
--- a/docs/reference/credits.md
+++ b/docs/reference/credits.md
@@ -17,8 +17,8 @@ OpenClaw = CLAW + TARDIS, because every space lobster needs a time and space mac
## Core contributors
-- **Maxim Vovshin** (@Hyaxia, 36747317+Hyaxia@users.noreply.github.com) - Blogwatcher skill
-- **Nacho Iacovino** (@nachoiacovino, nacho.iacovino@gmail.com) - Location parsing (Telegram and WhatsApp)
+- **Maxim Vovshin** (@Hyaxia, [36747317+Hyaxia@users.noreply.github.com](mailto:36747317+Hyaxia@users.noreply.github.com)) - Blogwatcher skill
+- **Nacho Iacovino** (@nachoiacovino, [nacho.iacovino@gmail.com](mailto:nacho.iacovino@gmail.com)) - Location parsing (Telegram and WhatsApp)
## License
diff --git a/docs/token-use.md b/docs/token-use.md
index 7f8dcb7fb..16b0fe961 100644
--- a/docs/token-use.md
+++ b/docs/token-use.md
@@ -85,7 +85,7 @@ re-caching the full prompt, reducing cache write costs.
For Anthropic API pricing, cache reads are significantly cheaper than input
tokens, while cache writes are billed at a higher multiplier. See Anthropic’s
prompt caching pricing for the latest rates and TTL multipliers:
-https://docs.anthropic.com/docs/build-with-claude/prompt-caching
+[https://docs.anthropic.com/docs/build-with-claude/prompt-caching](https://docs.anthropic.com/docs/build-with-claude/prompt-caching)
### Example: keep 1h cache warm with heartbeat
diff --git a/docs/tools/browser-login.md b/docs/tools/browser-login.md
index dcfb5ceb4..a3c7a4615 100644
--- a/docs/tools/browser-login.md
+++ b/docs/tools/browser-login.md
@@ -35,7 +35,7 @@ If you have multiple profiles, pass `--browser-profile ` (the default is `
## X/Twitter: recommended flow
- **Read/search/threads:** use the **bird** CLI skill (no browser, stable).
- - Repo: https://github.com/steipete/bird
+ - Repo: [https://github.com/steipete/bird](https://github.com/steipete/bird)
- **Post updates:** use the **host** browser (manual login).
## Sandboxing + host browser access
diff --git a/docs/tools/lobster.md b/docs/tools/lobster.md
index 62ef21357..ed9ed1fb2 100644
--- a/docs/tools/lobster.md
+++ b/docs/tools/lobster.md
@@ -338,5 +338,5 @@ OpenProse pairs well with Lobster: use `/prose` to orchestrate multi-agent prep,
One public example: a “second brain” CLI + Lobster pipelines that manage three Markdown vaults (personal, partner, shared). The CLI emits JSON for stats, inbox listings, and stale scans; Lobster chains those commands into workflows like `weekly-review`, `inbox-triage`, `memory-consolidation`, and `shared-task-sync`, each with approval gates. AI handles judgment (categorization) when available and falls back to deterministic rules when not.
-- Thread: https://x.com/plattenschieber/status/2014508656335770033
-- Repo: https://github.com/bloomedai/brain-cli
+- Thread: [https://x.com/plattenschieber/status/2014508656335770033](https://x.com/plattenschieber/status/2014508656335770033)
+- Repo: [https://github.com/bloomedai/brain-cli](https://github.com/bloomedai/brain-cli)
diff --git a/docs/tools/skills.md b/docs/tools/skills.md
index b4a142e33..b8038ee0f 100644
--- a/docs/tools/skills.md
+++ b/docs/tools/skills.md
@@ -50,7 +50,7 @@ tool surface those skills teach.
## ClawHub (install + sync)
ClawHub is the public skills registry for OpenClaw. Browse at
-https://clawhub.com. Use it to discover, install, update, and back up skills.
+[https://clawhub.com](https://clawhub.com). Use it to discover, install, update, and back up skills.
Full guide: [ClawHub](/tools/clawhub).
Common flows:
@@ -295,6 +295,6 @@ See [Skills config](/tools/skills-config) for the full configuration schema.
## Looking for more skills?
-Browse https://clawhub.com.
+Browse [https://clawhub.com](https://clawhub.com).
---
diff --git a/docs/tools/web.md b/docs/tools/web.md
index 4a3c23841..c22bc1707 100644
--- a/docs/tools/web.md
+++ b/docs/tools/web.md
@@ -71,7 +71,7 @@ Example: switch to Perplexity Sonar (direct API):
## Getting a Brave API key
-1. Create a Brave Search API account at https://brave.com/search/api/
+1. Create a Brave Search API account at [https://brave.com/search/api/](https://brave.com/search/api/)
2. In the dashboard, choose the **Data for Search** plan (not “Data for AI”) and generate an API key.
3. Run `openclaw configure --section web` to store the key in config (recommended), or set `BRAVE_API_KEY` in your environment.
@@ -95,7 +95,7 @@ crypto/prepaid).
### Getting an OpenRouter API key
-1. Create an account at https://openrouter.ai/
+1. Create an account at [https://openrouter.ai/](https://openrouter.ai/)
2. Add credits (supports crypto, prepaid, or credit card)
3. Generate an API key in your account settings
diff --git a/docs/vps.md b/docs/vps.md
index dedccee4b..f0b1f7d77 100644
--- a/docs/vps.md
+++ b/docs/vps.md
@@ -21,7 +21,7 @@ deployments work at a high level.
- **GCP (Compute Engine)**: [GCP](/install/gcp)
- **exe.dev** (VM + HTTPS proxy): [exe.dev](/install/exe-dev)
- **AWS (EC2/Lightsail/free tier)**: works well too. Video guide:
- https://x.com/techfrenAJ/status/2014934471095812547
+ [https://x.com/techfrenAJ/status/2014934471095812547](https://x.com/techfrenAJ/status/2014934471095812547)
## How cloud setups work
diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md
index 640340f17..233a67c48 100644
--- a/docs/web/control-ui.md
+++ b/docs/web/control-ui.md
@@ -19,7 +19,7 @@ It speaks **directly to the Gateway WebSocket** on the same port.
If the Gateway is running on the same computer, open:
-- http://127.0.0.1:18789/ (or http://localhost:18789/)
+- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
If the page fails to load, start the Gateway first: `openclaw gateway`.
diff --git a/docs/web/dashboard.md b/docs/web/dashboard.md
index d68456821..5c33455f0 100644
--- a/docs/web/dashboard.md
+++ b/docs/web/dashboard.md
@@ -12,7 +12,7 @@ The Gateway dashboard is the browser Control UI served at `/` by default
Quick open (local Gateway):
-- http://127.0.0.1:18789/ (or http://localhost:18789/)
+- [http://127.0.0.1:18789/](http://127.0.0.1:18789/) (or [http://localhost:18789/](http://localhost:18789/))
Key references:
From 991cf4d7fec5ce7215c02200325e54c7e3f38251 Mon Sep 17 00:00:00 2001
From: Seb Slight <19554889+sebslight@users.noreply.github.com>
Date: Fri, 6 Feb 2026 10:49:38 -0500
Subject: [PATCH 07/35] Docs: revamp installer internals for readability and
accuracy (#10499)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* docs(install): revamp installer internals for readability and accuracy
Restructure the installer internals page with better flow and Mintlify
components (CardGroup, Steps, Tabs, AccordionGroup). All flags, env vars,
and behavioral descriptions cross-checked against install.sh,
install-cli.sh, and install.ps1 source code.
- Add CardGroup chooser and Quick Commands section at top
- Organize each script into consistent Flow → Examples → Reference pattern
- Move flags/env var tables into collapsible Accordions
- Consolidate troubleshooting into AccordionGroup at bottom
- Add missing flags (--version, --beta, --verbose, --help, etc.)
- Add missing env vars (OPENCLAW_VERSION, OPENCLAW_BETA, etc.)
- Document install-cli.sh fully (was one paragraph)
- Fix non-interactive checkout detection behavior (defaults to npm)
- Use --proto/--tlsv1.2 in curl examples to match script usage
- No content deleted; all original info preserved or relocated
* fix(docs): correct in-page anchor hrefs for installer cards
* docs(install): replace CardGroup with table for installer overview
---
docs/install/installer.md | 432 +++++++++++++++++++++++++++++---------
1 file changed, 328 insertions(+), 104 deletions(-)
diff --git a/docs/install/installer.md b/docs/install/installer.md
index 1fb7061bd..92c24e6a4 100644
--- a/docs/install/installer.md
+++ b/docs/install/installer.md
@@ -1,5 +1,5 @@
---
-summary: "How the installer scripts work (install.sh + install-cli.sh), flags, and automation"
+summary: "How the installer scripts work (install.sh, install-cli.sh, install.ps1), flags, and automation"
read_when:
- You want to understand `openclaw.ai/install.sh`
- You want to automate installs (CI / headless)
@@ -9,153 +9,377 @@ title: "Installer Internals"
# Installer internals
-OpenClaw ships two installer scripts (served from `openclaw.ai`):
+OpenClaw ships three installer scripts, served from `openclaw.ai`.
-- `https://openclaw.ai/install.sh` - "recommended" installer (global npm install by default; can also install from a GitHub checkout)
-- `https://openclaw.ai/install-cli.sh` - non-root-friendly CLI installer (installs into a prefix with its own Node)
-- `https://openclaw.ai/install.ps1` - Windows PowerShell installer (npm by default; optional git install)
+| Script | Platform | What it does |
+| ----------------------------------- | -------------------- | -------------------------------------------------------------------------------------------- |
+| [`install.sh`](#install-sh) | macOS / Linux / WSL | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
+| [`install-cli.sh`](#install-cli-sh) | macOS / Linux / WSL | Installs Node + OpenClaw into a local prefix (`~/.openclaw`). No root required. |
+| [`install.ps1`](#install-ps1) | Windows (PowerShell) | Installs Node if needed, installs OpenClaw via npm (default) or git, and can run onboarding. |
-To see the current flags/behavior, run:
+## Quick commands
-```bash
-curl -fsSL https://openclaw.ai/install.sh | bash -s -- --help
-```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
+ ```
-Windows (PowerShell) help:
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --help
+ ```
-```powershell
-& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -?
-```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash
+ ```
-If the installer completes but `openclaw` is not found in a new terminal, it's usually a Node/npm PATH issue. See: [Node.js](/install/node#troubleshooting).
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash -s -- --help
+ ```
-## Flags and environment variables
+
+
+ ```powershell
+ iwr -useb https://openclaw.ai/install.ps1 | iex
+ ```
-### CLI flags (install.sh)
+ ```powershell
+ & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -Tag beta -NoOnboard -DryRun
+ ```
-| Flag | Description |
-| --------------------------- | ------------------------------------------------ |
-| `--install-method npm\|git` | Choose install method (default: `npm`) |
-| `--git-dir ` | Source checkout location (default: `~/openclaw`) |
-| `--no-git-update` | Skip `git pull` when using an existing checkout |
-| `--no-prompt` | Disable prompts (required in CI/automation) |
-| `--dry-run` | Print what would happen; make no changes |
-| `--no-onboard` | Skip onboarding after install |
+
+
-### PowerShell flags (install.ps1)
+
+If install succeeds but `openclaw` is not found in a new terminal, see [Node.js troubleshooting](/install/node#troubleshooting).
+
-| Flag | Description |
-| ------------------------- | ----------------------------------------------- |
-| `-InstallMethod npm\|git` | Choose install method (default: `npm`) |
-| `-GitDir ` | Source checkout location |
-| `-NoOnboard` | Skip onboarding after install |
-| `-NoGitUpdate` | Skip `git pull` when using an existing checkout |
-| `-DryRun` | Print what would happen; make no changes |
-| `-Tag ` | npm dist-tag to install (default: `latest`) |
+---
-### Environment variables
+## install.sh
-Equivalent env vars (useful for CI/automation):
+
+Recommended for most interactive installs on macOS/Linux/WSL.
+
-| Variable | Description |
-| ---------------------------------- | ------------------------------------------------------------ |
-| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method |
-| `OPENCLAW_GIT_DIR=` | Source checkout location |
-| `OPENCLAW_GIT_UPDATE=0\|1` | Toggle git pull |
-| `OPENCLAW_NO_PROMPT=1` | Disable prompts |
-| `OPENCLAW_DRY_RUN=1` | Dry run mode |
-| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding |
-| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1` | Avoid `sharp` building against system libvips (default: `1`) |
+### Flow
-## install.sh (recommended)
+
+
+ Supports macOS and Linux (including WSL). If macOS is detected, installs Homebrew if missing.
+
+
+ Checks Node version and installs Node 22 if needed (Homebrew on macOS, NodeSource setup scripts on Linux apt/dnf/yum).
+
+
+ Installs Git if missing.
+
+
+ - `npm` method (default): global npm install
+ - `git` method: clone/update repo, install deps with pnpm, build, then install wrapper at `~/.local/bin/openclaw`
+
+
+ - Runs `openclaw doctor --non-interactive` on upgrades and git installs (best effort)
+ - Attempts onboarding when appropriate (TTY available, onboarding not disabled, and bootstrap/config checks pass)
+ - Defaults `SHARP_IGNORE_GLOBAL_LIBVIPS=1`
+
+
-What it does (high level):
+### Source checkout detection
-- Detect OS (macOS / Linux / WSL).
-- Ensure Node.js **22+** (macOS via Homebrew; Linux via NodeSource).
-- Choose install method:
- - `npm` (default): `npm install -g openclaw@latest`
- - `git`: clone/build a source checkout and install a wrapper script
-- On Linux: avoid global npm permission errors by switching npm's prefix to `~/.npm-global` when needed.
-- If upgrading an existing install: runs `openclaw doctor --non-interactive` (best effort).
-- For git installs: runs `openclaw doctor --non-interactive` after install/update (best effort).
-- Mitigates `sharp` native install gotchas by defaulting `SHARP_IGNORE_GLOBAL_LIBVIPS=1` (avoids building against system libvips).
+If run inside an OpenClaw checkout (`package.json` + `pnpm-workspace.yaml`), the script offers:
-If you _want_ `sharp` to link against a globally-installed libvips (or you're debugging), set:
+- use checkout (`git`), or
+- use global install (`npm`)
-```bash
-SHARP_IGNORE_GLOBAL_LIBVIPS=0 curl -fsSL https://openclaw.ai/install.sh | bash
-```
+If no TTY is available and no install method is set, it defaults to `npm` and warns.
-### Discoverability / "git install" prompt
+The script exits with code `2` for invalid method selection or invalid `--install-method` values.
-If you run the installer while **already inside a OpenClaw source checkout** (detected via `package.json` + `pnpm-workspace.yaml`), it prompts:
+### Examples
-- update and use this checkout (`git`)
-- or migrate to the global npm install (`npm`)
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --dry-run
+ ```
+
+
-In non-interactive contexts (no TTY / `--no-prompt`), you must pass `--install-method git|npm` (or set `OPENCLAW_INSTALL_METHOD`), otherwise the script exits with code `2`.
+
+
-### Why Git is needed
+| Flag | Description |
+| ------------------------------- | ---------------------------------------------------------- |
+| `--install-method npm\|git` | Choose install method (default: `npm`). Alias: `--method` |
+| `--npm` | Shortcut for npm method |
+| `--git` | Shortcut for git method. Alias: `--github` |
+| `--version ` | npm version or dist-tag (default: `latest`) |
+| `--beta` | Use beta dist-tag if available, else fallback to `latest` |
+| `--git-dir ` | Checkout directory (default: `~/openclaw`). Alias: `--dir` |
+| `--no-git-update` | Skip `git pull` for existing checkout |
+| `--no-prompt` | Disable prompts |
+| `--no-onboard` | Skip onboarding |
+| `--onboard` | Enable onboarding |
+| `--dry-run` | Print actions without applying changes |
+| `--verbose` | Enable debug output (`set -x`, npm notice-level logs) |
+| `--help` | Show usage (`-h`) |
-Git is required for the `--install-method git` path (clone / pull).
+
-For `npm` installs, Git is _usually_ not required, but some environments still end up needing it (e.g. when a package or dependency is fetched via a git URL). The installer currently ensures Git is present to avoid `spawn git ENOENT` surprises on fresh distros.
+
-### Why npm hits `EACCES` on fresh Linux
+| Variable | Description |
+| ------------------------------------------- | --------------------------------------------- |
+| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method |
+| `OPENCLAW_VERSION=latest\|next\|` | npm version or dist-tag |
+| `OPENCLAW_BETA=0\|1` | Use beta if available |
+| `OPENCLAW_GIT_DIR=` | Checkout directory |
+| `OPENCLAW_GIT_UPDATE=0\|1` | Toggle git updates |
+| `OPENCLAW_NO_PROMPT=1` | Disable prompts |
+| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding |
+| `OPENCLAW_DRY_RUN=1` | Dry run mode |
+| `OPENCLAW_VERBOSE=1` | Debug mode |
+| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice` | npm log level |
+| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1` | Control sharp/libvips behavior (default: `1`) |
-On some Linux setups (especially after installing Node via the system package manager or NodeSource), npm's global prefix points at a root-owned location. Then `npm install -g ...` fails with `EACCES` / `mkdir` permission errors.
+
+
-`install.sh` mitigates this by switching the prefix to:
+---
-- `~/.npm-global` (and adding it to `PATH` in `~/.bashrc` / `~/.zshrc` when present)
+## install-cli.sh
-## install-cli.sh (non-root CLI installer)
+
+Designed for environments where you want everything under a local prefix (default `~/.openclaw`) and no system Node dependency.
+
-This script installs `openclaw` into a prefix (default: `~/.openclaw`) and also installs a dedicated Node runtime under that prefix, so it can work on machines where you don't want to touch the system Node/npm.
+### Flow
-Help:
+
+
+ Downloads Node tarball (default `22.22.0`) to `/tools/node-v` and verifies SHA-256.
+
+
+ If Git is missing, attempts install via apt/dnf/yum on Linux or Homebrew on macOS.
+
+
+ Installs with npm using `--prefix `, then writes wrapper to `/bin/openclaw`.
+
+
-```bash
-curl -fsSL https://openclaw.ai/install-cli.sh | bash -s -- --help
-```
+### Examples
-## install.ps1 (Windows PowerShell)
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash -s -- --prefix /opt/openclaw --version latest
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash -s -- --json --prefix /opt/openclaw
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash -s -- --onboard
+ ```
+
+
-What it does (high level):
+
+
-- Ensure Node.js **22+** (winget/Chocolatey/Scoop or manual).
-- Choose install method:
- - `npm` (default): `npm install -g openclaw@latest`
- - `git`: clone/build a source checkout and install a wrapper script
-- Runs `openclaw doctor --non-interactive` on upgrades and git installs (best effort).
+| Flag | Description |
+| ---------------------- | ------------------------------------------------------------------------------- |
+| `--prefix ` | Install prefix (default: `~/.openclaw`) |
+| `--version ` | OpenClaw version or dist-tag (default: `latest`) |
+| `--node-version ` | Node version (default: `22.22.0`) |
+| `--json` | Emit NDJSON events |
+| `--onboard` | Run `openclaw onboard` after install |
+| `--no-onboard` | Skip onboarding (default) |
+| `--set-npm-prefix` | On Linux, force npm prefix to `~/.npm-global` if current prefix is not writable |
+| `--help` | Show usage (`-h`) |
-Examples:
+
-```powershell
-iwr -useb https://openclaw.ai/install.ps1 | iex
-```
+
-```powershell
-iwr -useb https://openclaw.ai/install.ps1 | iex -InstallMethod git
-```
+| Variable | Description |
+| ------------------------------------------- | --------------------------------------------------------------------------------- |
+| `OPENCLAW_PREFIX=` | Install prefix |
+| `OPENCLAW_VERSION=` | OpenClaw version or dist-tag |
+| `OPENCLAW_NODE_VERSION=` | Node version |
+| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding |
+| `OPENCLAW_NPM_LOGLEVEL=error\|warn\|notice` | npm log level |
+| `OPENCLAW_GIT_DIR=` | Legacy cleanup lookup path (used when removing old `Peekaboo` submodule checkout) |
+| `SHARP_IGNORE_GLOBAL_LIBVIPS=0\|1` | Control sharp/libvips behavior (default: `1`) |
-```powershell
-iwr -useb https://openclaw.ai/install.ps1 | iex -InstallMethod git -GitDir "C:\\openclaw"
-```
+
+
-Environment variables:
+---
-- `OPENCLAW_INSTALL_METHOD=git|npm`
-- `OPENCLAW_GIT_DIR=...`
+## install.ps1
-Git requirement:
+### Flow
-If you choose `-InstallMethod git` and Git is missing, the installer will print the
-Git for Windows link (`https://git-scm.com/download/win`) and exit.
+
+
+ Requires PowerShell 5+.
+
+
+ If missing, attempts install via winget, then Chocolatey, then Scoop.
+
+
+ - `npm` method (default): global npm install using selected `-Tag`
+ - `git` method: clone/update repo, install/build with pnpm, and install wrapper at `%USERPROFILE%\.local\bin\openclaw.cmd`
+
+
+ Adds needed bin directory to user PATH when possible, then runs `openclaw doctor --non-interactive` on upgrades and git installs (best effort).
+
+
-Common Windows issues:
+### Examples
-- **npm error spawn git / ENOENT**: install Git for Windows and reopen PowerShell, then rerun the installer.
-- **"openclaw" is not recognized**: your npm global bin folder is not on PATH. Most systems use
- `%AppData%\\npm`. You can also run `npm config get prefix` and add `\\bin` to PATH, then reopen PowerShell.
+
+
+ ```powershell
+ iwr -useb https://openclaw.ai/install.ps1 | iex
+ ```
+
+
+ ```powershell
+ & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git
+ ```
+
+
+ ```powershell
+ & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -InstallMethod git -GitDir "C:\openclaw"
+ ```
+
+
+ ```powershell
+ & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -DryRun
+ ```
+
+
+
+
+
+
+| Flag | Description |
+| ------------------------- | ------------------------------------------------------ |
+| `-InstallMethod npm\|git` | Install method (default: `npm`) |
+| `-Tag ` | npm dist-tag (default: `latest`) |
+| `-GitDir ` | Checkout directory (default: `%USERPROFILE%\openclaw`) |
+| `-NoOnboard` | Skip onboarding |
+| `-NoGitUpdate` | Skip `git pull` |
+| `-DryRun` | Print actions only |
+
+
+
+
+
+| Variable | Description |
+| ---------------------------------- | ------------------ |
+| `OPENCLAW_INSTALL_METHOD=git\|npm` | Install method |
+| `OPENCLAW_GIT_DIR=` | Checkout directory |
+| `OPENCLAW_NO_ONBOARD=1` | Skip onboarding |
+| `OPENCLAW_GIT_UPDATE=0` | Disable git pull |
+| `OPENCLAW_DRY_RUN=1` | Dry run mode |
+
+
+
+
+
+If `-InstallMethod git` is used and Git is missing, the script exits and prints the Git for Windows link.
+
+
+---
+
+## CI and automation
+
+Use non-interactive flags/env vars for predictable runs.
+
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-prompt --no-onboard
+ ```
+
+
+ ```bash
+ OPENCLAW_INSTALL_METHOD=git OPENCLAW_NO_PROMPT=1 \
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
+ ```
+
+
+ ```bash
+ curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install-cli.sh | bash -s -- --json --prefix /opt/openclaw
+ ```
+
+
+ ```powershell
+ & ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -NoOnboard
+ ```
+
+
+
+---
+
+## Troubleshooting
+
+
+
+ Git is required for `git` install method. For `npm` installs, Git is still checked/installed to avoid `spawn git ENOENT` failures when dependencies use git URLs.
+
+
+
+ Some Linux setups point npm global prefix to root-owned paths. `install.sh` can switch prefix to `~/.npm-global` and append PATH exports to shell rc files (when those files exist).
+
+
+
+ The scripts default `SHARP_IGNORE_GLOBAL_LIBVIPS=1` to avoid sharp building against system libvips. To override:
+
+ ```bash
+ SHARP_IGNORE_GLOBAL_LIBVIPS=0 curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash
+ ```
+
+
+
+
+ Install Git for Windows, reopen PowerShell, rerun installer.
+
+
+
+ Run `npm config get prefix`, append `\bin`, add that directory to user PATH, then reopen PowerShell.
+
+
+
+ Usually a PATH issue. See [Node.js troubleshooting](/install/node#troubleshooting).
+
+
From 5842bcaaf72c7dcb883790f7c837df5530f3dae3 Mon Sep 17 00:00:00 2001
From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Date: Fri, 6 Feb 2026 11:58:39 -0600
Subject: [PATCH 08/35] Docs: add PR sign-off template (#10561)
---
docs/help/submitting-a-pr.md | 73 +++++++++++++++++++++++++++++++++++-
1 file changed, 72 insertions(+), 1 deletion(-)
diff --git a/docs/help/submitting-a-pr.md b/docs/help/submitting-a-pr.md
index 2259a730f..3baedc3a1 100644
--- a/docs/help/submitting-a-pr.md
+++ b/docs/help/submitting-a-pr.md
@@ -77,6 +77,12 @@ This keeps review fast while preserving deep context for anyone who needs it.
## Tests
## Evidence
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
## Templates by PR type
@@ -95,6 +101,12 @@ This keeps review fast while preserving deep context for anyone who needs it.
## Tests
## Evidence
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Feature
@@ -108,11 +120,22 @@ This keeps review fast while preserving deep context for anyone who needs it.
## Existing Functionality Check
-I searched the codebase for existing functionality before implementing this.
+- [ ] I searched the codebase for existing functionality before implementing this.
+
+Searches performed (1-3 bullets, one sentence each):
+
+-
+-
## Tests
## Evidence
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Refactor
@@ -125,6 +148,12 @@ I searched the codebase for existing functionality before implementing this.
## No Behavior Change Statement
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Chore/Maintenance
@@ -135,6 +164,12 @@ I searched the codebase for existing functionality before implementing this.
## Why This Matters
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Docs
@@ -149,6 +184,12 @@ I searched the codebase for existing functionality before implementing this.
## Formatting
pnpm format
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Test
@@ -159,6 +200,12 @@ pnpm format
## Gap Covered
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Perf
@@ -173,6 +220,12 @@ pnpm format
## Measurement Method
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### UX/UI
@@ -185,6 +238,12 @@ pnpm format
## Accessibility Impact
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Infra/Build
@@ -195,6 +254,12 @@ pnpm format
## Environments Affected
## Validation Steps
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
### Security
@@ -211,4 +276,10 @@ pnpm format
## Verification
## Tests
+
+## Sign-Off
+
+- Models used:
+- Submitter effort summary (self-reported, subjective):
+- Agent notes (optional; brief; cite evidence):
```
From 421644940517ae1857281bddf8603aeef9cebf1c Mon Sep 17 00:00:00 2001
From: Yida-Dev <92713555+Yida-Dev@users.noreply.github.com>
Date: Sat, 7 Feb 2026 01:16:58 +0700
Subject: [PATCH 09/35] fix: guard resolveUserPath against undefined input
(#10176)
* fix: guard resolveUserPath against undefined input
When subagent spawner omits workspaceDir, resolveUserPath receives
undefined and crashes on .trim(). Add a falsy guard that falls back
to process.cwd(), matching the behavior callers already expect.
Closes #10089
Co-Authored-By: Claude Opus 4.6
* fix: harden runner workspace fallback (#10176) (thanks @Yida-Dev)
* fix: harden workspace fallback scoping (#10176) (thanks @Yida-Dev)
* refactor: centralize workspace fallback classification and redaction (#10176) (thanks @Yida-Dev)
* test: remove explicit any from utils mock (#10176) (thanks @Yida-Dev)
* security: reject malformed agent session keys for workspace resolution (#10176) (thanks @Yida-Dev)
---------
Co-authored-by: Yida-Dev
Co-authored-by: Claude Opus 4.6
Co-authored-by: Gustavo Madeira Santana
---
CHANGELOG.md | 1 +
src/agents/cli-runner.test.ts | 83 +++++++++++
src/agents/cli-runner.ts | 21 ++-
src/agents/pi-embedded-runner.test.ts | 69 +++++++++
src/agents/pi-embedded-runner/run.ts | 23 ++-
src/agents/pi-embedded-runner/run/attempt.ts | 13 +-
src/agents/pi-embedded-runner/run/params.ts | 1 +
src/agents/pi-embedded-runner/run/types.ts | 1 +
src/agents/workspace-run.test.ts | 139 ++++++++++++++++++
src/agents/workspace-run.ts | 106 +++++++++++++
.../reply/agent-runner-execution.ts | 2 +
src/auto-reply/reply/agent-runner-memory.ts | 1 +
src/auto-reply/reply/followup-runner.ts | 1 +
src/commands/agent.ts | 2 +
src/commands/models/list.probe.ts | 1 +
src/commands/status-all/channels.ts | 8 +-
src/cron/isolated-agent/run.ts | 2 +
src/hooks/llm-slug-generator.ts | 1 +
src/logging/redact-identifier.ts | 14 ++
src/routing/session-key.test.ts | 25 ++++
src/routing/session-key.ts | 12 ++
src/utils.test.ts | 20 +--
22 files changed, 522 insertions(+), 24 deletions(-)
create mode 100644 src/agents/workspace-run.test.ts
create mode 100644 src/agents/workspace-run.ts
create mode 100644 src/logging/redact-identifier.ts
create mode 100644 src/routing/session-key.test.ts
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e83528b4e..83a5192d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,7 @@ Docs: https://docs.openclaw.ai
- Control UI: add hardened fallback for asset resolution in global npm installs. (#4855) Thanks @anapivirtua.
- Update: remove dead restore control-ui step that failed on gitignored dist/ output.
- Update: avoid wiping prebuilt Control UI assets during dev auto-builds (`tsdown --no-clean`), run update doctor via `openclaw.mjs`, and auto-restore missing UI assets after doctor. (#10146) Thanks @gumadeiras.
+- Agents: harden embedded and CLI runner workspace resolution for missing/blank runtime inputs by falling back to per-agent workspace defaults (not CWD), preventing `sessions_spawn` early crashes. (#10176) Thanks @Yida-Dev.
- Models: add forward-compat fallback for `openai-codex/gpt-5.3-codex` when model registry hasn't discovered it yet. (#9989) Thanks @w1kke.
- Auto-reply/Docs: normalize `extra-high` (and spaced variants) to `xhigh` for Codex thinking levels, and align Codex 5.3 FAQ examples. (#9976) Thanks @slonce70.
- Compaction: remove orphaned `tool_result` messages during history pruning to prevent session corruption from aborted tool calls. (#9868, fixes #9769, #9724, #9672)
diff --git a/src/agents/cli-runner.test.ts b/src/agents/cli-runner.test.ts
index 2293648e2..b5f5e5ba5 100644
--- a/src/agents/cli-runner.test.ts
+++ b/src/agents/cli-runner.test.ts
@@ -1,4 +1,8 @@
+import fs from "node:fs/promises";
+import os from "node:os";
+import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
+import type { OpenClawConfig } from "../config/config.js";
import type { CliBackendConfig } from "../config/types.js";
import { runCliAgent } from "./cli-runner.js";
import { cleanupSuspendedCliProcesses } from "./cli-runner/helpers.js";
@@ -58,6 +62,85 @@ describe("runCliAgent resume cleanup", () => {
expect(pkillArgs[1]).toContain("resume");
expect(pkillArgs[1]).toContain("thread-123");
});
+
+ it("falls back to per-agent workspace when workspaceDir is missing", async () => {
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cli-runner-"));
+ const fallbackWorkspace = path.join(tempDir, "workspace-main");
+ await fs.mkdir(fallbackWorkspace, { recursive: true });
+ const cfg = {
+ agents: {
+ defaults: {
+ workspace: fallbackWorkspace,
+ },
+ },
+ } satisfies OpenClawConfig;
+
+ runExecMock.mockResolvedValue({ stdout: "", stderr: "" });
+ runCommandWithTimeoutMock.mockResolvedValueOnce({
+ stdout: "ok",
+ stderr: "",
+ code: 0,
+ signal: null,
+ killed: false,
+ });
+
+ try {
+ await runCliAgent({
+ sessionId: "s1",
+ sessionKey: "agent:main:subagent:missing-workspace",
+ sessionFile: "/tmp/session.jsonl",
+ workspaceDir: undefined as unknown as string,
+ config: cfg,
+ prompt: "hi",
+ provider: "codex-cli",
+ model: "gpt-5.2-codex",
+ timeoutMs: 1_000,
+ runId: "run-1",
+ });
+ } finally {
+ await fs.rm(tempDir, { recursive: true, force: true });
+ }
+
+ const options = runCommandWithTimeoutMock.mock.calls[0]?.[1] as { cwd?: string };
+ expect(options.cwd).toBe(path.resolve(fallbackWorkspace));
+ });
+
+ it("throws when sessionKey is malformed", async () => {
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-cli-runner-"));
+ const mainWorkspace = path.join(tempDir, "workspace-main");
+ const researchWorkspace = path.join(tempDir, "workspace-research");
+ await fs.mkdir(mainWorkspace, { recursive: true });
+ await fs.mkdir(researchWorkspace, { recursive: true });
+ const cfg = {
+ agents: {
+ defaults: {
+ workspace: mainWorkspace,
+ },
+ list: [{ id: "research", workspace: researchWorkspace }],
+ },
+ } satisfies OpenClawConfig;
+
+ try {
+ await expect(
+ runCliAgent({
+ sessionId: "s1",
+ sessionKey: "agent::broken",
+ agentId: "research",
+ sessionFile: "/tmp/session.jsonl",
+ workspaceDir: undefined as unknown as string,
+ config: cfg,
+ prompt: "hi",
+ provider: "codex-cli",
+ model: "gpt-5.2-codex",
+ timeoutMs: 1_000,
+ runId: "run-2",
+ }),
+ ).rejects.toThrow("Malformed agent session key");
+ } finally {
+ await fs.rm(tempDir, { recursive: true, force: true });
+ }
+ expect(runCommandWithTimeoutMock).not.toHaveBeenCalled();
+ });
});
describe("cleanupSuspendedCliProcesses", () => {
diff --git a/src/agents/cli-runner.ts b/src/agents/cli-runner.ts
index 4b4c108e4..68dbf0d5c 100644
--- a/src/agents/cli-runner.ts
+++ b/src/agents/cli-runner.ts
@@ -7,7 +7,6 @@ import { shouldLogVerbose } from "../globals.js";
import { isTruthyEnvValue } from "../infra/env.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { runCommandWithTimeout } from "../process/exec.js";
-import { resolveUserPath } from "../utils.js";
import { resolveSessionAgentIds } from "./agent-scope.js";
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "./bootstrap-files.js";
import { resolveCliBackendConfig } from "./cli-backends.js";
@@ -29,12 +28,14 @@ import {
import { resolveOpenClawDocsPath } from "./docs-path.js";
import { FailoverError, resolveFailoverStatus } from "./failover-error.js";
import { classifyFailoverReason, isFailoverErrorMessage } from "./pi-embedded-helpers.js";
+import { redactRunIdentifier, resolveRunWorkspaceDir } from "./workspace-run.js";
const log = createSubsystemLogger("agent/claude-cli");
export async function runCliAgent(params: {
sessionId: string;
sessionKey?: string;
+ agentId?: string;
sessionFile: string;
workspaceDir: string;
config?: OpenClawConfig;
@@ -51,7 +52,21 @@ export async function runCliAgent(params: {
images?: ImageContent[];
}): Promise {
const started = Date.now();
- const resolvedWorkspace = resolveUserPath(params.workspaceDir);
+ const workspaceResolution = resolveRunWorkspaceDir({
+ workspaceDir: params.workspaceDir,
+ sessionKey: params.sessionKey,
+ agentId: params.agentId,
+ config: params.config,
+ });
+ const resolvedWorkspace = workspaceResolution.workspaceDir;
+ const redactedSessionId = redactRunIdentifier(params.sessionId);
+ const redactedSessionKey = redactRunIdentifier(params.sessionKey);
+ const redactedWorkspace = redactRunIdentifier(resolvedWorkspace);
+ if (workspaceResolution.usedFallback) {
+ log.warn(
+ `[workspace-fallback] caller=runCliAgent reason=${workspaceResolution.fallbackReason} run=${params.runId} session=${redactedSessionId} sessionKey=${redactedSessionKey} agent=${workspaceResolution.agentId} workspace=${redactedWorkspace}`,
+ );
+ }
const workspaceDir = resolvedWorkspace;
const backendResolved = resolveCliBackendConfig(params.provider, params.config);
@@ -311,6 +326,7 @@ export async function runCliAgent(params: {
export async function runClaudeCliAgent(params: {
sessionId: string;
sessionKey?: string;
+ agentId?: string;
sessionFile: string;
workspaceDir: string;
config?: OpenClawConfig;
@@ -328,6 +344,7 @@ export async function runClaudeCliAgent(params: {
return runCliAgent({
sessionId: params.sessionId,
sessionKey: params.sessionKey,
+ agentId: params.agentId,
sessionFile: params.sessionFile,
workspaceDir: params.workspaceDir,
config: params.config,
diff --git a/src/agents/pi-embedded-runner.test.ts b/src/agents/pi-embedded-runner.test.ts
index 8db5994d9..698bc8466 100644
--- a/src/agents/pi-embedded-runner.test.ts
+++ b/src/agents/pi-embedded-runner.test.ts
@@ -219,6 +219,75 @@ describe("runEmbeddedPiAgent", () => {
await expect(fs.stat(path.join(agentDir, "models.json"))).resolves.toBeTruthy();
});
+ it("falls back to per-agent workspace when runtime workspaceDir is missing", async () => {
+ const sessionFile = nextSessionFile();
+ const fallbackWorkspace = path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-main");
+ const cfg = {
+ ...makeOpenAiConfig(["mock-1"]),
+ agents: {
+ defaults: {
+ workspace: fallbackWorkspace,
+ },
+ },
+ } satisfies OpenClawConfig;
+ await ensureModels(cfg);
+
+ const result = await runEmbeddedPiAgent({
+ sessionId: "session:test-fallback",
+ sessionKey: "agent:main:subagent:fallback-workspace",
+ sessionFile,
+ workspaceDir: undefined as unknown as string,
+ config: cfg,
+ prompt: "hello",
+ provider: "openai",
+ model: "mock-1",
+ timeoutMs: 5_000,
+ agentDir,
+ runId: "run-fallback-workspace",
+ enqueue: immediateEnqueue,
+ });
+
+ expect(result.payloads?.[0]?.text).toBe("ok");
+ await expect(fs.stat(fallbackWorkspace)).resolves.toBeTruthy();
+ });
+
+ it("throws when sessionKey is malformed", async () => {
+ const sessionFile = nextSessionFile();
+ const cfg = {
+ ...makeOpenAiConfig(["mock-1"]),
+ agents: {
+ defaults: {
+ workspace: path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-main"),
+ },
+ list: [
+ {
+ id: "research",
+ workspace: path.join(tempRoot ?? os.tmpdir(), "workspace-fallback-research"),
+ },
+ ],
+ },
+ } satisfies OpenClawConfig;
+ await ensureModels(cfg);
+
+ await expect(
+ runEmbeddedPiAgent({
+ sessionId: "session:test-fallback-malformed",
+ sessionKey: "agent::broken",
+ agentId: "research",
+ sessionFile,
+ workspaceDir: undefined as unknown as string,
+ config: cfg,
+ prompt: "hello",
+ provider: "openai",
+ model: "mock-1",
+ timeoutMs: 5_000,
+ agentDir,
+ runId: "run-fallback-workspace-malformed",
+ enqueue: immediateEnqueue,
+ }),
+ ).rejects.toThrow("Malformed agent session key");
+ });
+
itIfNotWin32(
"persists the first user message before assistant output",
{ timeout: 120_000 },
diff --git a/src/agents/pi-embedded-runner/run.ts b/src/agents/pi-embedded-runner/run.ts
index d7fb2693d..c8ca9b5a1 100644
--- a/src/agents/pi-embedded-runner/run.ts
+++ b/src/agents/pi-embedded-runner/run.ts
@@ -3,7 +3,6 @@ import type { ThinkLevel } from "../../auto-reply/thinking.js";
import type { RunEmbeddedPiAgentParams } from "./run/params.js";
import type { EmbeddedPiAgentMeta, EmbeddedPiRunResult } from "./types.js";
import { enqueueCommandInLane } from "../../process/command-queue.js";
-import { resolveUserPath } from "../../utils.js";
import { isMarkdownCapableMessageChannel } from "../../utils/message-channel.js";
import { resolveOpenClawAgentDir } from "../agent-paths.js";
import {
@@ -46,6 +45,7 @@ import {
type FailoverReason,
} from "../pi-embedded-helpers.js";
import { normalizeUsage, type UsageLike } from "../usage.js";
+import { redactRunIdentifier, resolveRunWorkspaceDir } from "../workspace-run.js";
import { compactEmbeddedPiSessionDirect } from "./compact.js";
import { resolveGlobalLane, resolveSessionLane } from "./lanes.js";
import { log } from "./logger.js";
@@ -92,7 +92,21 @@ export async function runEmbeddedPiAgent(
return enqueueSession(() =>
enqueueGlobal(async () => {
const started = Date.now();
- const resolvedWorkspace = resolveUserPath(params.workspaceDir);
+ const workspaceResolution = resolveRunWorkspaceDir({
+ workspaceDir: params.workspaceDir,
+ sessionKey: params.sessionKey,
+ agentId: params.agentId,
+ config: params.config,
+ });
+ const resolvedWorkspace = workspaceResolution.workspaceDir;
+ const redactedSessionId = redactRunIdentifier(params.sessionId);
+ const redactedSessionKey = redactRunIdentifier(params.sessionKey);
+ const redactedWorkspace = redactRunIdentifier(resolvedWorkspace);
+ if (workspaceResolution.usedFallback) {
+ log.warn(
+ `[workspace-fallback] caller=runEmbeddedPiAgent reason=${workspaceResolution.fallbackReason} run=${params.runId} session=${redactedSessionId} sessionKey=${redactedSessionKey} agent=${workspaceResolution.agentId} workspace=${redactedWorkspace}`,
+ );
+ }
const prevCwd = process.cwd();
const provider = (params.provider ?? DEFAULT_PROVIDER).trim() || DEFAULT_PROVIDER;
@@ -333,7 +347,7 @@ export async function runEmbeddedPiAgent(
replyToMode: params.replyToMode,
hasRepliedRef: params.hasRepliedRef,
sessionFile: params.sessionFile,
- workspaceDir: params.workspaceDir,
+ workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
@@ -345,6 +359,7 @@ export async function runEmbeddedPiAgent(
model,
authStorage,
modelRegistry,
+ agentId: workspaceResolution.agentId,
thinkLevel,
verboseLevel: params.verboseLevel,
reasoningLevel: params.reasoningLevel,
@@ -401,7 +416,7 @@ export async function runEmbeddedPiAgent(
agentAccountId: params.agentAccountId,
authProfileId: lastProfileId,
sessionFile: params.sessionFile,
- workspaceDir: params.workspaceDir,
+ workspaceDir: resolvedWorkspace,
agentDir,
config: params.config,
skillsSnapshot: params.skillsSnapshot,
diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts
index 83fe17cfb..2e6c70292 100644
--- a/src/agents/pi-embedded-runner/run/attempt.ts
+++ b/src/agents/pi-embedded-runner/run/attempt.ts
@@ -10,7 +10,7 @@ import { resolveChannelCapabilities } from "../../../config/channel-capabilities
import { getMachineDisplayName } from "../../../infra/machine-name.js";
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
-import { isSubagentSessionKey } from "../../../routing/session-key.js";
+import { isSubagentSessionKey, normalizeAgentId } from "../../../routing/session-key.js";
import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
@@ -705,6 +705,13 @@ export async function runEmbeddedAttempt(
// Get hook runner once for both before_agent_start and agent_end hooks
const hookRunner = getGlobalHookRunner();
+ const hookAgentId =
+ typeof params.agentId === "string" && params.agentId.trim()
+ ? normalizeAgentId(params.agentId)
+ : resolveSessionAgentIds({
+ sessionKey: params.sessionKey,
+ config: params.config,
+ }).sessionAgentId;
let promptError: unknown = null;
try {
@@ -720,7 +727,7 @@ export async function runEmbeddedAttempt(
messages: activeSession.messages,
},
{
- agentId: params.sessionKey?.split(":")[0] ?? "main",
+ agentId: hookAgentId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,
@@ -850,7 +857,7 @@ export async function runEmbeddedAttempt(
durationMs: Date.now() - promptStartedAt,
},
{
- agentId: params.sessionKey?.split(":")[0] ?? "main",
+ agentId: hookAgentId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir,
messageProvider: params.messageProvider ?? undefined,
diff --git a/src/agents/pi-embedded-runner/run/params.ts b/src/agents/pi-embedded-runner/run/params.ts
index 93f5c5c92..f56f3ecac 100644
--- a/src/agents/pi-embedded-runner/run/params.ts
+++ b/src/agents/pi-embedded-runner/run/params.ts
@@ -20,6 +20,7 @@ export type ClientToolDefinition = {
export type RunEmbeddedPiAgentParams = {
sessionId: string;
sessionKey?: string;
+ agentId?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;
diff --git a/src/agents/pi-embedded-runner/run/types.ts b/src/agents/pi-embedded-runner/run/types.ts
index 931afcd24..181a42c9f 100644
--- a/src/agents/pi-embedded-runner/run/types.ts
+++ b/src/agents/pi-embedded-runner/run/types.ts
@@ -14,6 +14,7 @@ import type { ClientToolDefinition } from "./params.js";
export type EmbeddedRunAttemptParams = {
sessionId: string;
sessionKey?: string;
+ agentId?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;
diff --git a/src/agents/workspace-run.test.ts b/src/agents/workspace-run.test.ts
new file mode 100644
index 000000000..bb99f5177
--- /dev/null
+++ b/src/agents/workspace-run.test.ts
@@ -0,0 +1,139 @@
+import os from "node:os";
+import path from "node:path";
+import { describe, expect, it } from "vitest";
+import type { OpenClawConfig } from "../config/config.js";
+import { resolveRunWorkspaceDir } from "./workspace-run.js";
+import { DEFAULT_AGENT_WORKSPACE_DIR } from "./workspace.js";
+
+describe("resolveRunWorkspaceDir", () => {
+ it("resolves explicit workspace values without fallback", () => {
+ const explicit = path.join(process.cwd(), "tmp", "workspace-run-explicit");
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: explicit,
+ sessionKey: "agent:main:subagent:test",
+ });
+
+ expect(result.usedFallback).toBe(false);
+ expect(result.agentId).toBe("main");
+ expect(result.workspaceDir).toBe(path.resolve(explicit));
+ });
+
+ it("falls back to configured per-agent workspace when input is missing", () => {
+ const defaultWorkspace = path.join(process.cwd(), "tmp", "workspace-default-main");
+ const researchWorkspace = path.join(process.cwd(), "tmp", "workspace-research");
+ const cfg = {
+ agents: {
+ defaults: { workspace: defaultWorkspace },
+ list: [{ id: "research", workspace: researchWorkspace }],
+ },
+ } satisfies OpenClawConfig;
+
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: undefined,
+ sessionKey: "agent:research:subagent:test",
+ config: cfg,
+ });
+
+ expect(result.usedFallback).toBe(true);
+ expect(result.fallbackReason).toBe("missing");
+ expect(result.agentId).toBe("research");
+ expect(result.workspaceDir).toBe(path.resolve(researchWorkspace));
+ });
+
+ it("falls back to default workspace for blank strings", () => {
+ const defaultWorkspace = path.join(process.cwd(), "tmp", "workspace-default-main");
+ const cfg = {
+ agents: {
+ defaults: { workspace: defaultWorkspace },
+ },
+ } satisfies OpenClawConfig;
+
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: " ",
+ sessionKey: "agent:main:subagent:test",
+ config: cfg,
+ });
+
+ expect(result.usedFallback).toBe(true);
+ expect(result.fallbackReason).toBe("blank");
+ expect(result.agentId).toBe("main");
+ expect(result.workspaceDir).toBe(path.resolve(defaultWorkspace));
+ });
+
+ it("falls back to built-in main workspace when config is unavailable", () => {
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: null,
+ sessionKey: "agent:main:subagent:test",
+ config: undefined,
+ });
+
+ expect(result.usedFallback).toBe(true);
+ expect(result.fallbackReason).toBe("missing");
+ expect(result.agentId).toBe("main");
+ expect(result.workspaceDir).toBe(path.resolve(DEFAULT_AGENT_WORKSPACE_DIR));
+ });
+
+ it("throws for malformed agent session keys", () => {
+ expect(() =>
+ resolveRunWorkspaceDir({
+ workspaceDir: undefined,
+ sessionKey: "agent::broken",
+ config: undefined,
+ }),
+ ).toThrow("Malformed agent session key");
+ });
+
+ it("uses explicit agent id for per-agent fallback when config is unavailable", () => {
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: undefined,
+ sessionKey: "definitely-not-a-valid-session-key",
+ agentId: "research",
+ config: undefined,
+ });
+
+ expect(result.agentId).toBe("research");
+ expect(result.agentIdSource).toBe("explicit");
+ expect(result.workspaceDir).toBe(path.resolve(os.homedir(), ".openclaw", "workspace-research"));
+ });
+
+ it("throws for malformed agent session keys even when config has a default agent", () => {
+ const mainWorkspace = path.join(process.cwd(), "tmp", "workspace-main-default");
+ const researchWorkspace = path.join(process.cwd(), "tmp", "workspace-research-default");
+ const cfg = {
+ agents: {
+ defaults: { workspace: mainWorkspace },
+ list: [
+ { id: "main", workspace: mainWorkspace },
+ { id: "research", workspace: researchWorkspace, default: true },
+ ],
+ },
+ } satisfies OpenClawConfig;
+
+ expect(() =>
+ resolveRunWorkspaceDir({
+ workspaceDir: undefined,
+ sessionKey: "agent::broken",
+ config: cfg,
+ }),
+ ).toThrow("Malformed agent session key");
+ });
+
+ it("treats non-agent legacy keys as default, not malformed", () => {
+ const fallbackWorkspace = path.join(process.cwd(), "tmp", "workspace-default-legacy");
+ const cfg = {
+ agents: {
+ defaults: { workspace: fallbackWorkspace },
+ },
+ } satisfies OpenClawConfig;
+
+ const result = resolveRunWorkspaceDir({
+ workspaceDir: undefined,
+ sessionKey: "custom-main-key",
+ config: cfg,
+ });
+
+ expect(result.agentId).toBe("main");
+ expect(result.agentIdSource).toBe("default");
+ expect(result.workspaceDir).toBe(path.resolve(fallbackWorkspace));
+ });
+});
diff --git a/src/agents/workspace-run.ts b/src/agents/workspace-run.ts
new file mode 100644
index 000000000..1061a0344
--- /dev/null
+++ b/src/agents/workspace-run.ts
@@ -0,0 +1,106 @@
+import type { OpenClawConfig } from "../config/config.js";
+import { redactIdentifier } from "../logging/redact-identifier.js";
+import {
+ classifySessionKeyShape,
+ DEFAULT_AGENT_ID,
+ normalizeAgentId,
+ parseAgentSessionKey,
+} from "../routing/session-key.js";
+import { resolveUserPath } from "../utils.js";
+import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js";
+
+export type WorkspaceFallbackReason = "missing" | "blank" | "invalid_type";
+type AgentIdSource = "explicit" | "session_key" | "default";
+
+export type ResolveRunWorkspaceResult = {
+ workspaceDir: string;
+ usedFallback: boolean;
+ fallbackReason?: WorkspaceFallbackReason;
+ agentId: string;
+ agentIdSource: AgentIdSource;
+};
+
+function resolveRunAgentId(params: {
+ sessionKey?: string;
+ agentId?: string;
+ config?: OpenClawConfig;
+}): {
+ agentId: string;
+ agentIdSource: AgentIdSource;
+} {
+ const rawSessionKey = params.sessionKey?.trim() ?? "";
+ const shape = classifySessionKeyShape(rawSessionKey);
+ if (shape === "malformed_agent") {
+ throw new Error("Malformed agent session key; refusing workspace resolution.");
+ }
+
+ const explicit =
+ typeof params.agentId === "string" && params.agentId.trim()
+ ? normalizeAgentId(params.agentId)
+ : undefined;
+ if (explicit) {
+ return { agentId: explicit, agentIdSource: "explicit" };
+ }
+
+ const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
+ if (shape === "missing" || shape === "legacy_or_alias") {
+ return {
+ agentId: defaultAgentId || DEFAULT_AGENT_ID,
+ agentIdSource: "default",
+ };
+ }
+
+ const parsed = parseAgentSessionKey(rawSessionKey);
+ if (parsed?.agentId) {
+ return {
+ agentId: normalizeAgentId(parsed.agentId),
+ agentIdSource: "session_key",
+ };
+ }
+
+ // Defensive fallback, should be unreachable for non-malformed shapes.
+ return {
+ agentId: defaultAgentId || DEFAULT_AGENT_ID,
+ agentIdSource: "default",
+ };
+}
+
+export function redactRunIdentifier(value: string | undefined): string {
+ return redactIdentifier(value, { len: 12 });
+}
+
+export function resolveRunWorkspaceDir(params: {
+ workspaceDir: unknown;
+ sessionKey?: string;
+ agentId?: string;
+ config?: OpenClawConfig;
+}): ResolveRunWorkspaceResult {
+ const requested = params.workspaceDir;
+ const { agentId, agentIdSource } = resolveRunAgentId({
+ sessionKey: params.sessionKey,
+ agentId: params.agentId,
+ config: params.config,
+ });
+ if (typeof requested === "string") {
+ const trimmed = requested.trim();
+ if (trimmed) {
+ return {
+ workspaceDir: resolveUserPath(trimmed),
+ usedFallback: false,
+ agentId,
+ agentIdSource,
+ };
+ }
+ }
+
+ const fallbackReason: WorkspaceFallbackReason =
+ requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type";
+ const fallbackWorkspace = resolveAgentWorkspaceDir(params.config ?? {}, agentId);
+ return {
+ workspaceDir: resolveUserPath(fallbackWorkspace),
+ usedFallback: true,
+ fallbackReason,
+ agentId,
+ agentIdSource,
+ };
+}
diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts
index 3bdc5dde3..372db8b30 100644
--- a/src/auto-reply/reply/agent-runner-execution.ts
+++ b/src/auto-reply/reply/agent-runner-execution.ts
@@ -178,6 +178,7 @@ export async function runAgentTurnWithFallback(params: {
const result = await runCliAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
+ agentId: params.followupRun.run.agentId,
sessionFile: params.followupRun.run.sessionFile,
workspaceDir: params.followupRun.run.workspaceDir,
config: params.followupRun.run.config,
@@ -255,6 +256,7 @@ export async function runAgentTurnWithFallback(params: {
return runEmbeddedPiAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
+ agentId: params.followupRun.run.agentId,
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
agentAccountId: params.sessionCtx.AccountId,
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
diff --git a/src/auto-reply/reply/agent-runner-memory.ts b/src/auto-reply/reply/agent-runner-memory.ts
index 867ba42f9..f73c5c60d 100644
--- a/src/auto-reply/reply/agent-runner-memory.ts
+++ b/src/auto-reply/reply/agent-runner-memory.ts
@@ -113,6 +113,7 @@ export async function runMemoryFlushIfNeeded(params: {
return runEmbeddedPiAgent({
sessionId: params.followupRun.run.sessionId,
sessionKey: params.sessionKey,
+ agentId: params.followupRun.run.agentId,
messageProvider: params.sessionCtx.Provider?.trim().toLowerCase() || undefined,
agentAccountId: params.sessionCtx.AccountId,
messageTo: params.sessionCtx.OriginatingTo ?? params.sessionCtx.To,
diff --git a/src/auto-reply/reply/followup-runner.ts b/src/auto-reply/reply/followup-runner.ts
index 1ca51d0f4..e4c23aa04 100644
--- a/src/auto-reply/reply/followup-runner.ts
+++ b/src/auto-reply/reply/followup-runner.ts
@@ -140,6 +140,7 @@ export function createFollowupRunner(params: {
return runEmbeddedPiAgent({
sessionId: queued.run.sessionId,
sessionKey: queued.run.sessionKey,
+ agentId: queued.run.agentId,
messageProvider: queued.run.messageProvider,
agentAccountId: queued.run.agentAccountId,
messageTo: queued.originatingTo,
diff --git a/src/commands/agent.ts b/src/commands/agent.ts
index a42677551..4c08d75df 100644
--- a/src/commands/agent.ts
+++ b/src/commands/agent.ts
@@ -398,6 +398,7 @@ export async function agentCommand(
return runCliAgent({
sessionId,
sessionKey,
+ agentId: sessionAgentId,
sessionFile,
workspaceDir,
config: cfg,
@@ -418,6 +419,7 @@ export async function agentCommand(
return runEmbeddedPiAgent({
sessionId,
sessionKey,
+ agentId: sessionAgentId,
messageChannel,
agentAccountId: runContext.accountId,
messageTo: opts.replyTo ?? opts.to,
diff --git a/src/commands/models/list.probe.ts b/src/commands/models/list.probe.ts
index ee7a874fe..1c30a92eb 100644
--- a/src/commands/models/list.probe.ts
+++ b/src/commands/models/list.probe.ts
@@ -319,6 +319,7 @@ async function probeTarget(params: {
await runEmbeddedPiAgent({
sessionId,
sessionFile,
+ agentId,
workspaceDir,
agentDir,
config: cfg,
diff --git a/src/commands/status-all/channels.ts b/src/commands/status-all/channels.ts
index d7be6ad75..091921161 100644
--- a/src/commands/status-all/channels.ts
+++ b/src/commands/status-all/channels.ts
@@ -1,4 +1,3 @@
-import crypto from "node:crypto";
import fs from "node:fs";
import type {
ChannelAccountSnapshot,
@@ -8,6 +7,7 @@ import type {
import type { OpenClawConfig } from "../../config/config.js";
import { resolveChannelDefaultAccountId } from "../../channels/plugins/helpers.js";
import { listChannelPlugins } from "../../channels/plugins/index.js";
+import { sha256HexPrefix } from "../../logging/redact-identifier.js";
import { formatAge } from "./format.js";
export type ChannelRow = {
@@ -57,17 +57,13 @@ function existsSyncMaybe(p: string | undefined): boolean | null {
}
}
-function sha256HexPrefix(value: string, len = 8): string {
- return crypto.createHash("sha256").update(value).digest("hex").slice(0, len);
-}
-
function formatTokenHint(token: string, opts: { showSecrets: boolean }): string {
const t = token.trim();
if (!t) {
return "empty";
}
if (!opts.showSecrets) {
- return `sha256:${sha256HexPrefix(t)} · len ${t.length}`;
+ return `sha256:${sha256HexPrefix(t, 8)} · len ${t.length}`;
}
const head = t.slice(0, 4);
const tail = t.slice(-4);
diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts
index 6a557db34..3273cb8f9 100644
--- a/src/cron/isolated-agent/run.ts
+++ b/src/cron/isolated-agent/run.ts
@@ -356,6 +356,7 @@ export async function runCronIsolatedAgentTurn(params: {
return runCliAgent({
sessionId: cronSession.sessionEntry.sessionId,
sessionKey: agentSessionKey,
+ agentId,
sessionFile,
workspaceDir,
config: cfgWithAgentDefaults,
@@ -371,6 +372,7 @@ export async function runCronIsolatedAgentTurn(params: {
return runEmbeddedPiAgent({
sessionId: cronSession.sessionEntry.sessionId,
sessionKey: agentSessionKey,
+ agentId,
messageChannel,
agentAccountId: resolvedDelivery.accountId,
sessionFile,
diff --git a/src/hooks/llm-slug-generator.ts b/src/hooks/llm-slug-generator.ts
index 95161b66b..67fdfe4c8 100644
--- a/src/hooks/llm-slug-generator.ts
+++ b/src/hooks/llm-slug-generator.ts
@@ -41,6 +41,7 @@ Reply with ONLY the slug, nothing else. Examples: "vendor-pitch", "api-design",
const result = await runEmbeddedPiAgent({
sessionId: `slug-generator-${Date.now()}`,
sessionKey: "temp:slug-generator",
+ agentId,
sessionFile: tempSessionFile,
workspaceDir,
agentDir,
diff --git a/src/logging/redact-identifier.ts b/src/logging/redact-identifier.ts
new file mode 100644
index 000000000..0ffdfb55d
--- /dev/null
+++ b/src/logging/redact-identifier.ts
@@ -0,0 +1,14 @@
+import crypto from "node:crypto";
+
+export function sha256HexPrefix(value: string, len = 12): string {
+ const safeLen = Number.isFinite(len) ? Math.max(1, Math.floor(len)) : 12;
+ return crypto.createHash("sha256").update(value).digest("hex").slice(0, safeLen);
+}
+
+export function redactIdentifier(value: string | undefined, opts?: { len?: number }): string {
+ const trimmed = value?.trim();
+ if (!trimmed) {
+ return "-";
+ }
+ return `sha256:${sha256HexPrefix(trimmed, opts?.len ?? 12)}`;
+}
diff --git a/src/routing/session-key.test.ts b/src/routing/session-key.test.ts
new file mode 100644
index 000000000..6c3539f73
--- /dev/null
+++ b/src/routing/session-key.test.ts
@@ -0,0 +1,25 @@
+import { describe, expect, it } from "vitest";
+import { classifySessionKeyShape } from "./session-key.js";
+
+describe("classifySessionKeyShape", () => {
+ it("classifies empty keys as missing", () => {
+ expect(classifySessionKeyShape(undefined)).toBe("missing");
+ expect(classifySessionKeyShape(" ")).toBe("missing");
+ });
+
+ it("classifies valid agent keys", () => {
+ expect(classifySessionKeyShape("agent:main:main")).toBe("agent");
+ expect(classifySessionKeyShape("agent:research:subagent:worker")).toBe("agent");
+ });
+
+ it("classifies malformed agent keys", () => {
+ expect(classifySessionKeyShape("agent::broken")).toBe("malformed_agent");
+ expect(classifySessionKeyShape("agent:main")).toBe("malformed_agent");
+ });
+
+ it("treats non-agent legacy or alias keys as non-malformed", () => {
+ expect(classifySessionKeyShape("main")).toBe("legacy_or_alias");
+ expect(classifySessionKeyShape("custom-main")).toBe("legacy_or_alias");
+ expect(classifySessionKeyShape("subagent:worker")).toBe("legacy_or_alias");
+ });
+});
diff --git a/src/routing/session-key.ts b/src/routing/session-key.ts
index 8f2b4ab0d..ad1d16431 100644
--- a/src/routing/session-key.ts
+++ b/src/routing/session-key.ts
@@ -10,6 +10,7 @@ export {
export const DEFAULT_AGENT_ID = "main";
export const DEFAULT_MAIN_KEY = "main";
export const DEFAULT_ACCOUNT_ID = "default";
+export type SessionKeyShape = "missing" | "agent" | "legacy_or_alias" | "malformed_agent";
// Pre-compiled regex
const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i;
@@ -58,6 +59,17 @@ export function resolveAgentIdFromSessionKey(sessionKey: string | undefined | nu
return normalizeAgentId(parsed?.agentId ?? DEFAULT_AGENT_ID);
}
+export function classifySessionKeyShape(sessionKey: string | undefined | null): SessionKeyShape {
+ const raw = (sessionKey ?? "").trim();
+ if (!raw) {
+ return "missing";
+ }
+ if (parseAgentSessionKey(raw)) {
+ return "agent";
+ }
+ return raw.toLowerCase().startsWith("agent:") ? "malformed_agent" : "legacy_or_alias";
+}
+
export function normalizeAgentId(value: string | undefined | null): string {
const trimmed = (value ?? "").trim();
if (!trimmed) {
diff --git a/src/utils.test.ts b/src/utils.test.ts
index 2b0d95e6b..3ae0be47c 100644
--- a/src/utils.test.ts
+++ b/src/utils.test.ts
@@ -79,15 +79,12 @@ describe("jidToE164", () => {
it("maps @lid using reverse mapping file", () => {
const mappingPath = path.join(CONFIG_DIR, "credentials", "lid-mapping-123_reverse.json");
const original = fs.readFileSync;
- const spy = vi
- .spyOn(fs, "readFileSync")
- // oxlint-disable-next-line typescript/no-explicit-any
- .mockImplementation((path: any, encoding?: any) => {
- if (path === mappingPath) {
- return `"5551234"`;
- }
- return original(path, encoding);
- });
+ const spy = vi.spyOn(fs, "readFileSync").mockImplementation((...args) => {
+ if (args[0] === mappingPath) {
+ return `"5551234"`;
+ }
+ return original(...args);
+ });
expect(jidToE164("123@lid")).toBe("+5551234");
spy.mockRestore();
});
@@ -167,4 +164,9 @@ describe("resolveUserPath", () => {
it("resolves relative paths", () => {
expect(resolveUserPath("tmp/dir")).toBe(path.resolve("tmp/dir"));
});
+
+ it("keeps blank paths blank", () => {
+ expect(resolveUserPath("")).toBe("");
+ expect(resolveUserPath(" ")).toBe("");
+ });
});
From e3d3893d5dbec30c2046166b6a71bacfe641ef78 Mon Sep 17 00:00:00 2001
From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
Date: Fri, 6 Feb 2026 13:29:11 -0600
Subject: [PATCH 10/35] Docs: revise PR and issue submission guides (#10617)
* Docs: revise PR submission guide
* Docs: revise issue submission guide
---
docs/help/submitting-a-pr.md | 373 ++++++++++++++++++++-----------
docs/help/submitting-an-issue.md | 161 ++++++-------
2 files changed, 317 insertions(+), 217 deletions(-)
diff --git a/docs/help/submitting-a-pr.md b/docs/help/submitting-a-pr.md
index 3baedc3a1..caa407a29 100644
--- a/docs/help/submitting-a-pr.md
+++ b/docs/help/submitting-a-pr.md
@@ -3,283 +3,396 @@ summary: "How to submit a high signal PR"
title: "Submitting a PR"
---
-# Submitting a PR
-
-Good PRs make it easy for reviewers to understand intent, verify behavior, and land changes safely. This guide focuses on high-signal, low-noise submissions that work well with both human review and LLM-assisted review.
+Good PRs are easy to review: reviewers should quickly know the intent, verify behavior, and land changes safely. This guide covers concise, high-signal submissions for human and LLM review.
## What makes a good PR
-- [ ] Clear intent: explain the problem, why it matters, and what the change does.
-- [ ] Tight scope: keep changes focused and avoid drive-by refactors.
-- [ ] Behavior summary: call out user-visible changes, config changes, and defaults.
-- [ ] Tests: list what ran, what was skipped, and why.
-- [ ] Evidence: include logs, screenshots, or short recordings for UI or workflows.
-- [ ] Code word: include “lobster-biscuit” somewhere in the PR description to confirm you read this guide.
-- [ ] Baseline checks: run the relevant `pnpm` commands for this repo and fix failures before opening the PR.
-- [ ] Due diligence: search the codebase for existing functionality and check GitHub for related issues or prior fixes.
-- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
-- [ ] Title guidance: use a verb + scope + outcome (for example `Docs: add PR and issue templates`).
+- [ ] Explain the problem, why it matters, and the change.
+- [ ] Keep changes focused. Avoid broad refactors.
+- [ ] Summarize user-visible/config/default changes.
+- [ ] List test coverage, skips, and reasons.
+- [ ] Add evidence: logs, screenshots, or recordings (UI/UX).
+- [ ] Code word: put “lobster-biscuit” in the PR description if you read this guide.
+- [ ] Run/fix relevant `pnpm` commands before creating PR.
+- [ ] Search codebase and GitHub for related functionality/issues/fixes.
+- [ ] Base claims on evidence or observation.
+- [ ] Good title: verb + scope + outcome (e.g., `Docs: add PR and issue templates`).
-Guideline: concision > grammar. Be terse if it makes review faster.
+Be concise; concise review > grammar. Omit any non-applicable sections.
-Baseline validation commands (run as appropriate for the change, and fix failures before submitting):
+### Baseline validation commands (run/fix failures for your change):
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
-- If you touch protocol code: `pnpm protocol:check`
+- Protocol changes: `pnpm protocol:check`
## Progressive disclosure
-Use a short top section, then deeper details as needed.
+- Top: summary/intent
+- Next: changes/risks
+- Next: test/verification
+- Last: implementation/evidence
-1. Summary and intent
-2. Behavior changes and risks
-3. Tests and verification
-4. Implementation details and evidence
+## Common PR types: specifics
-This keeps review fast while preserving deep context for anyone who needs it.
-
-## Common PR types and expectations
-
-- [ ] Fix: include clear repro, root cause summary, and verification steps.
-- [ ] Feature: include use cases, behavior changes, and screenshots or demos when UI is involved.
-- [ ] Refactor: explicitly state “no behavior change” and list what moved or was simplified.
-- [ ] Chore/Maintenance: note why it matters (build time, CI stability, dependency hygiene).
-- [ ] Docs: include before/after context and link to the updated page. Run `pnpm format`.
-- [ ] Test: explain the gap it covers and how it prevents regressions.
-- [ ] Perf: include baseline and after metrics, plus how they were measured.
-- [ ] UX/UI: include screenshots or short recordings and any accessibility impact.
-- [ ] Infra/Build: call out affected environments and how to validate.
-- [ ] Security: include threat or risk summary, repro steps, and verification plan. Avoid sensitive data in public logs.
-- [ ] Security: keep reports grounded in reality; avoid speculative claims.
+- [ ] Fix: Add repro, root cause, verification.
+- [ ] Feature: Add use cases, behavior/demos/screenshots (UI).
+- [ ] Refactor: State "no behavior change", list what moved/simplified.
+- [ ] Chore: State why (e.g., build time, CI, dependencies).
+- [ ] Docs: Before/after context, link updated page, run `pnpm format`.
+- [ ] Test: What gap is covered; how it prevents regressions.
+- [ ] Perf: Add before/after metrics, and how measured.
+- [ ] UX/UI: Screenshots/video, note accessibility impact.
+- [ ] Infra/Build: Environments/validation.
+- [ ] Security: Summarize risk, repro, verification, no sensitive data. Grounded claims only.
## Checklist
-- [ ] Problem and intent are clear
-- [ ] Scope is focused
-- [ ] Behavior changes are listed
-- [ ] Tests are listed with results
-- [ ] Evidence is attached when needed
-- [ ] No secrets or private data
-- [ ] Grounded in reality: no guesswork or invented context.
+- [ ] Clear problem/intent
+- [ ] Focused scope
+- [ ] List behavior changes
+- [ ] List and result of tests
+- [ ] Manual test steps (when applicable)
+- [ ] No secrets/private data
+- [ ] Evidence-based
-## Template
+## General PR Template
```md
-## Summary
+#### Summary
-## Behavior Changes
+#### Behavior Changes
-## Codebase and GitHub Search
+#### Codebase and GitHub Search
-## Tests
+#### Tests
-## Evidence
+#### Manual Testing (omit if N/A)
-## Sign-Off
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort (self-reported):
+- Agent notes (optional, cite evidence):
```
-## Templates by PR type
+## PR Type templates (replace with your type)
### Fix
```md
-## Summary
+#### Summary
-## Repro Steps
+#### Repro Steps
-## Root Cause
+#### Root Cause
-## Behavior Changes
+#### Behavior Changes
-## Tests
+#### Tests
-## Evidence
+#### Manual Testing (omit if N/A)
-## Sign-Off
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Feature
```md
-## Summary
+#### Summary
-## Use Cases
+#### Use Cases
-## Behavior Changes
+#### Behavior Changes
-## Existing Functionality Check
+#### Existing Functionality Check
-- [ ] I searched the codebase for existing functionality before implementing this.
+- [ ] I searched the codebase for existing functionality.
+ Searches performed (1-3 bullets):
+ -
+ -
-Searches performed (1-3 bullets, one sentence each):
+#### Tests
+
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
--
-
-## Tests
+### Steps
-## Evidence
+1.
+2.
-## Sign-Off
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Refactor
```md
-## Summary
+#### Summary
-## Scope
+#### Scope
-## No Behavior Change Statement
+#### No Behavior Change Statement
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Chore/Maintenance
```md
-## Summary
+#### Summary
-## Why This Matters
+#### Why This Matters
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Docs
```md
-## Summary
+#### Summary
-## Pages Updated
+#### Pages Updated
-## Screenshots or Before/After
+#### Before/After
-## Formatting
+#### Formatting
pnpm format
-## Sign-Off
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Test
```md
-## Summary
+#### Summary
-## Gap Covered
+#### Gap Covered
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Perf
```md
-## Summary
+#### Summary
-## Baseline
+#### Baseline
-## After
+#### After
-## Measurement Method
+#### Measurement Method
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### UX/UI
```md
-## Summary
+#### Summary
-## Screenshots or Video
+#### Screenshots or Video
-## Accessibility Impact
+#### Accessibility Impact
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2. **Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Infra/Build
```md
-## Summary
+#### Summary
-## Environments Affected
+#### Environments Affected
-## Validation Steps
+#### Validation Steps
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
### Security
```md
-## Summary
+#### Summary
-## Risk Summary
+#### Risk Summary
-## Repro Steps
+#### Repro Steps
-## Mitigation or Fix
+#### Mitigation or Fix
-## Verification
+#### Verification
-## Tests
+#### Tests
-## Sign-Off
+#### Manual Testing (omit if N/A)
+
+### Prerequisites
+
+-
+
+### Steps
+
+1.
+2.
+
+#### Evidence (omit if N/A)
+
+**Sign-Off**
- Models used:
-- Submitter effort summary (self-reported, subjective):
-- Agent notes (optional; brief; cite evidence):
+- Submitter effort:
+- Agent notes:
```
diff --git a/docs/help/submitting-an-issue.md b/docs/help/submitting-an-issue.md
index a91a4678b..5aa844445 100644
--- a/docs/help/submitting-an-issue.md
+++ b/docs/help/submitting-an-issue.md
@@ -1,165 +1,152 @@
---
-summary: "How to file high signal issues and bug reports"
+summary: "Filing high-signal issues and bug reports"
title: "Submitting an Issue"
---
-# Submitting an Issue
+## Submitting an Issue
-Good issues make it easy to reproduce, diagnose, and fix problems quickly. This guide covers what to include for bugs, regressions, and feature gaps.
+Clear, concise issues speed up diagnosis and fixes. Include the following for bugs, regressions, or feature gaps:
-## What makes a good issue
+### What to include
-- [ ] Clear title: include the area and the symptom.
-- [ ] Repro steps: minimal steps that consistently reproduce the issue.
-- [ ] Expected vs actual: what you thought would happen and what did.
-- [ ] Impact: who is affected and how severe the problem is.
-- [ ] Environment: OS, runtime, versions, and relevant config.
-- [ ] Evidence: logs, screenshots, or recordings (redacted; prefer non-PII data).
-- [ ] Scope: note if it is new, regression, or long-standing.
-- [ ] Code word: include “lobster-biscuit” somewhere in the issue description to confirm you read this guide.
-- [ ] Due diligence: search the codebase for existing functionality and check GitHub to see if the issue is already filed or fixed.
-- [ ] I searched for existing and recently closed issues/PRs.
-- [ ] For security reports: confirmed it has not already been fixed or addressed recently.
-- [ ] Grounded in reality: claims should be backed by evidence, reproduction, or direct observation.
+- [ ] Title: area & symptom
+- [ ] Minimal repro steps
+- [ ] Expected vs actual
+- [ ] Impact & severity
+- [ ] Environment: OS, runtime, versions, config
+- [ ] Evidence: redacted logs, screenshots (non-PII)
+- [ ] Scope: new, regression, or longstanding
+- [ ] Code word: lobster-biscuit in your issue
+- [ ] Searched codebase & GitHub for existing issue
+- [ ] Confirmed not recently fixed/addressed (esp. security)
+- [ ] Claims backed by evidence or repro
-Guideline: concision > grammar. Be terse if it makes review faster.
+Be brief. Terseness > perfect grammar.
-Baseline validation commands (run as appropriate for the change, and fix failures before submitting a PR):
+Validation (run/fix before PR):
- `pnpm lint`
- `pnpm check`
- `pnpm build`
- `pnpm test`
-- If you touch protocol code: `pnpm protocol:check`
+- If protocol code: `pnpm protocol:check`
-## Templates
+### Templates
-### Bug report
+#### Bug report
```md
-## Bug report checklist
-
-- [ ] Minimal repro steps
+- [ ] Minimal repro
- [ ] Expected vs actual
-- [ ] Versions and environment
-- [ ] Affected channels and where it does not reproduce
-- [ ] Logs or screenshots
-- [ ] Evidence is redacted and non-PII where possible
-- [ ] Impact and severity
-- [ ] Any known workarounds
+- [ ] Environment
+- [ ] Affected channels, where not seen
+- [ ] Logs/screenshots (redacted)
+- [ ] Impact/severity
+- [ ] Workarounds
-## Summary
+### Summary
-## Repro Steps
+### Repro Steps
-## Expected
+### Expected
-## Actual
+### Actual
-## Environment
+### Environment
-## Logs or Evidence
+### Logs/Evidence
-## Impact
+### Impact
-## Workarounds
+### Workarounds
```
-### Security issue
+#### Security issue
```md
-## Summary
+### Summary
-## Impact
+### Impact
-## Affected Versions
+### Versions
-## Repro Steps (if safe to share)
+### Repro Steps (safe to share)
-## Mitigation or Workaround
+### Mitigation/workaround
-## Evidence (redacted)
+### Evidence (redacted)
```
-Security note: avoid posting secrets or exploit details in public issues. If the report is sensitive, keep repro details minimal and ask for a private disclosure path.
+_Avoid secrets/exploit details in public. For sensitive issues, minimize detail and request private disclosure._
-### Regression report
+#### Regression report
```md
-## Summary
+### Summary
-## Last Known Good
+### Last Known Good
-## First Known Bad
+### First Known Bad
-## Repro Steps
+### Repro Steps
-## Expected
+### Expected
-## Actual
+### Actual
-## Environment
+### Environment
-## Logs or Evidence
+### Logs/Evidence
-## Impact
+### Impact
```
-### Feature request
+#### Feature request
```md
-## Summary
+### Summary
-## Problem
+### Problem
-## Proposed Solution
+### Proposed Solution
-## Alternatives Considered
+### Alternatives
-## Impact
+### Impact
-## Evidence or Examples
+### Evidence/examples
```
-### Enhancement request
+#### Enhancement
```md
-## Summary
+### Summary
-## Current Behavior
+### Current vs Desired Behavior
-## Desired Behavior
+### Rationale
-## Why This Matters
+### Alternatives
-## Alternatives Considered
-
-## Evidence or Examples
+### Evidence/examples
```
-### Investigation request
+#### Investigation
```md
-## Summary
+### Summary
-## Symptoms
+### Symptoms
-## What Was Tried
+### What Was Tried
-## Environment
+### Environment
-## Logs or Evidence
+### Logs/Evidence
-## Impact
+### Impact
```
-## If you are submitting a fix PR
+### Submitting a fix PR
-Creating a separate issue first is optional. If you skip it, include the relevant details in the PR description.
-
-- Keep the PR focused on the issue.
-- Include the issue number in the PR description.
-- Add tests when possible, or explain why they are not feasible.
-- Note any behavior changes and risks.
-- Include redacted logs, screenshots, or videos that validate the fix.
-- Run relevant `pnpm` validation commands and report results when appropriate.
+Issue before PR is optional. Include details in PR if skipping. Keep the PR focused, note issue number, add tests or explain absence, document behavior changes/risks, include redacted logs/screenshots as proof, and run proper validation before submitting.
From 6965a2cc9d78e680ea2a46a1266a97a96a40b25e Mon Sep 17 00:00:00 2001
From: Jake
Date: Sat, 7 Feb 2026 10:09:32 +1300
Subject: [PATCH 11/35] feat(memory): native Voyage AI support (#7078)
* feat(memory): add native Voyage AI embedding support with batching
Cherry-picked from PR #2519, resolved conflict in memory-search.ts
(hasRemote -> hasRemoteConfig rename + added voyage provider)
* fix(memory): optimize voyage batch memory usage with streaming and deduplicate code
Cherry-picked from PR #2519. Fixed lint error: changed this.runWithConcurrency
to use imported runWithConcurrency function after extraction to internal.ts
---
src/agents/memory-search.ts | 15 +-
src/config/schema.ts | 3 +-
src/config/types.tools.ts | 4 +-
src/config/zod-schema.agent-runtime.ts | 12 +-
src/memory/batch-voyage.test.ts | 170 ++++++++++++
src/memory/batch-voyage.ts | 363 +++++++++++++++++++++++++
src/memory/embeddings-voyage.test.ts | 100 +++++++
src/memory/embeddings-voyage.ts | 86 ++++++
src/memory/embeddings.ts | 22 +-
src/memory/internal.ts | 30 ++
src/memory/manager.ts | 132 ++++++---
11 files changed, 879 insertions(+), 58 deletions(-)
create mode 100644 src/memory/batch-voyage.test.ts
create mode 100644 src/memory/batch-voyage.ts
create mode 100644 src/memory/embeddings-voyage.test.ts
create mode 100644 src/memory/embeddings-voyage.ts
diff --git a/src/agents/memory-search.ts b/src/agents/memory-search.ts
index 658771a11..5394b640d 100644
--- a/src/agents/memory-search.ts
+++ b/src/agents/memory-search.ts
@@ -9,7 +9,7 @@ export type ResolvedMemorySearchConfig = {
enabled: boolean;
sources: Array<"memory" | "sessions">;
extraPaths: string[];
- provider: "openai" | "local" | "gemini" | "auto";
+ provider: "openai" | "local" | "gemini" | "voyage" | "auto";
remote?: {
baseUrl?: string;
apiKey?: string;
@@ -25,7 +25,7 @@ export type ResolvedMemorySearchConfig = {
experimental: {
sessionMemory: boolean;
};
- fallback: "openai" | "gemini" | "local" | "none";
+ fallback: "openai" | "gemini" | "local" | "voyage" | "none";
model: string;
local: {
modelPath?: string;
@@ -72,6 +72,7 @@ export type ResolvedMemorySearchConfig = {
const DEFAULT_OPENAI_MODEL = "text-embedding-3-small";
const DEFAULT_GEMINI_MODEL = "gemini-embedding-001";
+const DEFAULT_VOYAGE_MODEL = "voyage-4-large";
const DEFAULT_CHUNK_TOKENS = 400;
const DEFAULT_CHUNK_OVERLAP = 80;
const DEFAULT_WATCH_DEBOUNCE_MS = 1500;
@@ -136,7 +137,11 @@ function mergeConfig(
defaultRemote?.headers,
);
const includeRemote =
- hasRemoteConfig || provider === "openai" || provider === "gemini" || provider === "auto";
+ hasRemoteConfig ||
+ provider === "openai" ||
+ provider === "gemini" ||
+ provider === "voyage" ||
+ provider === "auto";
const batch = {
enabled: overrideRemote?.batch?.enabled ?? defaultRemote?.batch?.enabled ?? true,
wait: overrideRemote?.batch?.wait ?? defaultRemote?.batch?.wait ?? true,
@@ -163,7 +168,9 @@ function mergeConfig(
? DEFAULT_GEMINI_MODEL
: provider === "openai"
? DEFAULT_OPENAI_MODEL
- : undefined;
+ : provider === "voyage"
+ ? DEFAULT_VOYAGE_MODEL
+ : undefined;
const model = overrides?.model ?? defaults?.model ?? modelDefault ?? "";
const local = {
modelPath: overrides?.local?.modelPath ?? defaults?.local?.modelPath,
diff --git a/src/config/schema.ts b/src/config/schema.ts
index 175265ac1..a9c177c82 100644
--- a/src/config/schema.ts
+++ b/src/config/schema.ts
@@ -542,7 +542,8 @@ const FIELD_HELP: Record = {
"Extra paths to include in memory search (directories or .md files; relative paths resolved from workspace).",
"agents.defaults.memorySearch.experimental.sessionMemory":
"Enable experimental session transcript indexing for memory search (default: false).",
- "agents.defaults.memorySearch.provider": 'Embedding provider ("openai", "gemini", or "local").',
+ "agents.defaults.memorySearch.provider":
+ 'Embedding provider ("openai", "gemini", "voyage", or "local").',
"agents.defaults.memorySearch.remote.baseUrl":
"Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).",
"agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",
diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts
index b08032427..36700b6ce 100644
--- a/src/config/types.tools.ts
+++ b/src/config/types.tools.ts
@@ -234,7 +234,7 @@ export type MemorySearchConfig = {
sessionMemory?: boolean;
};
/** Embedding provider mode. */
- provider?: "openai" | "gemini" | "local";
+ provider?: "openai" | "gemini" | "local" | "voyage";
remote?: {
baseUrl?: string;
apiKey?: string;
@@ -253,7 +253,7 @@ export type MemorySearchConfig = {
};
};
/** Fallback behavior when embeddings fail. */
- fallback?: "openai" | "gemini" | "local" | "none";
+ fallback?: "openai" | "gemini" | "local" | "voyage" | "none";
/** Embedding model id (remote) or alias (local). */
model?: string;
/** Local embedding settings (node-llama-cpp). */
diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts
index c2e792f32..582853ff3 100644
--- a/src/config/zod-schema.agent-runtime.ts
+++ b/src/config/zod-schema.agent-runtime.ts
@@ -318,7 +318,9 @@ export const MemorySearchSchema = z
})
.strict()
.optional(),
- provider: z.union([z.literal("openai"), z.literal("local"), z.literal("gemini")]).optional(),
+ provider: z
+ .union([z.literal("openai"), z.literal("local"), z.literal("gemini"), z.literal("voyage")])
+ .optional(),
remote: z
.object({
baseUrl: z.string().optional(),
@@ -338,7 +340,13 @@ export const MemorySearchSchema = z
.strict()
.optional(),
fallback: z
- .union([z.literal("openai"), z.literal("gemini"), z.literal("local"), z.literal("none")])
+ .union([
+ z.literal("openai"),
+ z.literal("gemini"),
+ z.literal("local"),
+ z.literal("voyage"),
+ z.literal("none"),
+ ])
.optional(),
model: z.string().optional(),
local: z
diff --git a/src/memory/batch-voyage.test.ts b/src/memory/batch-voyage.test.ts
new file mode 100644
index 000000000..e0e757f19
--- /dev/null
+++ b/src/memory/batch-voyage.test.ts
@@ -0,0 +1,170 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { ReadableStream } from "node:stream/web";
+import type { VoyageBatchOutputLine, VoyageBatchRequest } from "./batch-voyage.js";
+import type { VoyageEmbeddingClient } from "./embeddings-voyage.js";
+
+// Mock internal.js if needed, but runWithConcurrency is simple enough to keep real.
+// We DO need to mock retryAsync to avoid actual delays/retries logic complicating tests
+vi.mock("../infra/retry.js", () => ({
+ retryAsync: async (fn: () => Promise) => fn(),
+}));
+
+describe("runVoyageEmbeddingBatches", () => {
+ afterEach(() => {
+ vi.resetAllMocks();
+ vi.unstubAllGlobals();
+ });
+
+ const mockClient: VoyageEmbeddingClient = {
+ baseUrl: "https://api.voyageai.com/v1",
+ headers: { Authorization: "Bearer test-key" },
+ model: "voyage-4-large",
+ };
+
+ const mockRequests: VoyageBatchRequest[] = [
+ { custom_id: "req-1", body: { input: "text1" } },
+ { custom_id: "req-2", body: { input: "text2" } },
+ ];
+
+ it("successfully submits batch, waits, and streams results", async () => {
+ const fetchMock = vi.fn();
+ vi.stubGlobal("fetch", fetchMock);
+
+ // Sequence of fetch calls:
+ // 1. Upload file
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ id: "file-123" }),
+ });
+
+ // 2. Create batch
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ id: "batch-abc", status: "pending" }),
+ });
+
+ // 3. Poll status (pending) - Optional depending on wait loop, let's say it finishes immediately for this test
+ // Actually the code does: initial check (if completed) -> wait loop.
+ // If create returns "pending", it enters waitForVoyageBatch.
+ // waitForVoyageBatch fetches status.
+
+ // 3. Poll status (completed)
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({
+ id: "batch-abc",
+ status: "completed",
+ output_file_id: "file-out-999",
+ }),
+ });
+
+ // 4. Download content (Streaming)
+ const outputLines: VoyageBatchOutputLine[] = [
+ {
+ custom_id: "req-1",
+ response: { status_code: 200, body: { data: [{ embedding: [0.1, 0.1] }] } },
+ },
+ {
+ custom_id: "req-2",
+ response: { status_code: 200, body: { data: [{ embedding: [0.2, 0.2] }] } },
+ },
+ ];
+
+ // Create a stream that emits the NDJSON lines
+ const stream = new ReadableStream({
+ start(controller) {
+ const text = outputLines.map((l) => JSON.stringify(l)).join("\n");
+ controller.enqueue(new TextEncoder().encode(text));
+ controller.close();
+ },
+ });
+
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ body: stream,
+ });
+
+ const { runVoyageEmbeddingBatches } = await import("./batch-voyage.js");
+
+ const results = await runVoyageEmbeddingBatches({
+ client: mockClient,
+ agentId: "agent-1",
+ requests: mockRequests,
+ wait: true,
+ pollIntervalMs: 1, // fast poll
+ timeoutMs: 1000,
+ concurrency: 1,
+ });
+
+ expect(results.size).toBe(2);
+ expect(results.get("req-1")).toEqual([0.1, 0.1]);
+ expect(results.get("req-2")).toEqual([0.2, 0.2]);
+
+ // Verify calls
+ expect(fetchMock).toHaveBeenCalledTimes(4);
+
+ // Verify File Upload
+ expect(fetchMock.mock.calls[0][0]).toContain("/files");
+ const uploadBody = fetchMock.mock.calls[0][1].body as FormData;
+ expect(uploadBody).toBeInstanceOf(FormData);
+ expect(uploadBody.get("purpose")).toBe("batch");
+
+ // Verify Batch Create
+ expect(fetchMock.mock.calls[1][0]).toContain("/batches");
+ const createBody = JSON.parse(fetchMock.mock.calls[1][1].body);
+ expect(createBody.input_file_id).toBe("file-123");
+ expect(createBody.completion_window).toBe("12h");
+
+ // Verify Content Fetch
+ expect(fetchMock.mock.calls[3][0]).toContain("/files/file-out-999/content");
+ });
+
+ it("handles empty lines and stream chunks correctly", async () => {
+ const fetchMock = vi.fn();
+ vi.stubGlobal("fetch", fetchMock);
+
+ // 1. Upload
+ fetchMock.mockResolvedValueOnce({ ok: true, json: async () => ({ id: "f1" }) });
+ // 2. Create (completed immediately)
+ fetchMock.mockResolvedValueOnce({
+ ok: true,
+ json: async () => ({ id: "b1", status: "completed", output_file_id: "out1" }),
+ });
+ // 3. Download Content (Streaming with chunks and newlines)
+ const stream = new ReadableStream({
+ start(controller) {
+ const line1 = JSON.stringify({
+ custom_id: "req-1",
+ response: { body: { data: [{ embedding: [1] }] } },
+ });
+ const line2 = JSON.stringify({
+ custom_id: "req-2",
+ response: { body: { data: [{ embedding: [2] }] } },
+ });
+
+ // Split across chunks
+ controller.enqueue(new TextEncoder().encode(line1 + "\n"));
+ controller.enqueue(new TextEncoder().encode("\n")); // empty line
+ controller.enqueue(new TextEncoder().encode(line2)); // no newline at EOF
+ controller.close();
+ },
+ });
+
+ fetchMock.mockResolvedValueOnce({ ok: true, body: stream });
+
+ const { runVoyageEmbeddingBatches } = await import("./batch-voyage.js");
+
+ const results = await runVoyageEmbeddingBatches({
+ client: mockClient,
+ agentId: "a1",
+ requests: mockRequests,
+ wait: true,
+ pollIntervalMs: 1,
+ timeoutMs: 1000,
+ concurrency: 1,
+ });
+
+ expect(results.get("req-1")).toEqual([1]);
+ expect(results.get("req-2")).toEqual([2]);
+ });
+});
diff --git a/src/memory/batch-voyage.ts b/src/memory/batch-voyage.ts
new file mode 100644
index 000000000..5e882738c
--- /dev/null
+++ b/src/memory/batch-voyage.ts
@@ -0,0 +1,363 @@
+import { createInterface } from "node:readline";
+import { Readable } from "node:stream";
+
+import { retryAsync } from "../infra/retry.js";
+import type { VoyageEmbeddingClient } from "./embeddings-voyage.js";
+import { hashText, runWithConcurrency } from "./internal.js";
+
+/**
+ * Voyage Batch API Input Line format.
+ * See: https://docs.voyageai.com/docs/batch-inference
+ */
+export type VoyageBatchRequest = {
+ custom_id: string;
+ body: {
+ input: string | string[];
+ };
+};
+
+export type VoyageBatchStatus = {
+ id?: string;
+ status?: string;
+ output_file_id?: string | null;
+ error_file_id?: string | null;
+};
+
+export type VoyageBatchOutputLine = {
+ custom_id?: string;
+ response?: {
+ status_code?: number;
+ body?: {
+ data?: Array<{ embedding?: number[]; index?: number }>;
+ error?: { message?: string };
+ };
+ };
+ error?: { message?: string };
+};
+
+export const VOYAGE_BATCH_ENDPOINT = "/v1/embeddings";
+const VOYAGE_BATCH_COMPLETION_WINDOW = "12h";
+const VOYAGE_BATCH_MAX_REQUESTS = 50000;
+
+function getVoyageBaseUrl(client: VoyageEmbeddingClient): string {
+ return client.baseUrl?.replace(/\/$/, "") ?? "";
+}
+
+function getVoyageHeaders(
+ client: VoyageEmbeddingClient,
+ params: { json: boolean },
+): Record {
+ const headers = client.headers ? { ...client.headers } : {};
+ if (params.json) {
+ if (!headers["Content-Type"] && !headers["content-type"]) {
+ headers["Content-Type"] = "application/json";
+ }
+ } else {
+ delete headers["Content-Type"];
+ delete headers["content-type"];
+ }
+ return headers;
+}
+
+function splitVoyageBatchRequests(requests: VoyageBatchRequest[]): VoyageBatchRequest[][] {
+ if (requests.length <= VOYAGE_BATCH_MAX_REQUESTS) return [requests];
+ const groups: VoyageBatchRequest[][] = [];
+ for (let i = 0; i < requests.length; i += VOYAGE_BATCH_MAX_REQUESTS) {
+ groups.push(requests.slice(i, i + VOYAGE_BATCH_MAX_REQUESTS));
+ }
+ return groups;
+}
+
+async function submitVoyageBatch(params: {
+ client: VoyageEmbeddingClient;
+ requests: VoyageBatchRequest[];
+ agentId: string;
+}): Promise {
+ const baseUrl = getVoyageBaseUrl(params.client);
+ const jsonl = params.requests.map((request) => JSON.stringify(request)).join("\n");
+ const form = new FormData();
+ form.append("purpose", "batch");
+ form.append(
+ "file",
+ new Blob([jsonl], { type: "application/jsonl" }),
+ `memory-embeddings.${hashText(String(Date.now()))}.jsonl`,
+ );
+
+ // 1. Upload file using Voyage Files API
+ const fileRes = await fetch(`${baseUrl}/files`, {
+ method: "POST",
+ headers: getVoyageHeaders(params.client, { json: false }),
+ body: form,
+ });
+ if (!fileRes.ok) {
+ const text = await fileRes.text();
+ throw new Error(`voyage batch file upload failed: ${fileRes.status} ${text}`);
+ }
+ const filePayload = (await fileRes.json()) as { id?: string };
+ if (!filePayload.id) {
+ throw new Error("voyage batch file upload failed: missing file id");
+ }
+
+ // 2. Create batch job using Voyage Batches API
+ const batchRes = await retryAsync(
+ async () => {
+ const res = await fetch(`${baseUrl}/batches`, {
+ method: "POST",
+ headers: getVoyageHeaders(params.client, { json: true }),
+ body: JSON.stringify({
+ input_file_id: filePayload.id,
+ endpoint: VOYAGE_BATCH_ENDPOINT,
+ completion_window: VOYAGE_BATCH_COMPLETION_WINDOW,
+ request_params: {
+ model: params.client.model,
+ },
+ metadata: {
+ source: "clawdbot-memory",
+ agent: params.agentId,
+ },
+ }),
+ });
+ if (!res.ok) {
+ const text = await res.text();
+ const err = new Error(`voyage batch create failed: ${res.status} ${text}`) as Error & {
+ status?: number;
+ };
+ err.status = res.status;
+ throw err;
+ }
+ return res;
+ },
+ {
+ attempts: 3,
+ minDelayMs: 300,
+ maxDelayMs: 2000,
+ jitter: 0.2,
+ shouldRetry: (err) => {
+ const status = (err as { status?: number }).status;
+ return status === 429 || (typeof status === "number" && status >= 500);
+ },
+ },
+ );
+ return (await batchRes.json()) as VoyageBatchStatus;
+}
+
+async function fetchVoyageBatchStatus(params: {
+ client: VoyageEmbeddingClient;
+ batchId: string;
+}): Promise {
+ const baseUrl = getVoyageBaseUrl(params.client);
+ const res = await fetch(`${baseUrl}/batches/${params.batchId}`, {
+ headers: getVoyageHeaders(params.client, { json: true }),
+ });
+ if (!res.ok) {
+ const text = await res.text();
+ throw new Error(`voyage batch status failed: ${res.status} ${text}`);
+ }
+ return (await res.json()) as VoyageBatchStatus;
+}
+
+async function readVoyageBatchError(params: {
+ client: VoyageEmbeddingClient;
+ errorFileId: string;
+}): Promise {
+ try {
+ const baseUrl = getVoyageBaseUrl(params.client);
+ const res = await fetch(`${baseUrl}/files/${params.errorFileId}/content`, {
+ headers: getVoyageHeaders(params.client, { json: true }),
+ });
+ if (!res.ok) {
+ const text = await res.text();
+ throw new Error(`voyage batch error file content failed: ${res.status} ${text}`);
+ }
+ const text = await res.text();
+ if (!text.trim()) return undefined;
+ const lines = text
+ .split("\n")
+ .map((line) => line.trim())
+ .filter(Boolean)
+ .map((line) => JSON.parse(line) as VoyageBatchOutputLine);
+ const first = lines.find((line) => line.error?.message || line.response?.body?.error);
+ const message =
+ first?.error?.message ??
+ (typeof first?.response?.body?.error?.message === "string"
+ ? first?.response?.body?.error?.message
+ : undefined);
+ return message;
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ return message ? `error file unavailable: ${message}` : undefined;
+ }
+}
+
+async function waitForVoyageBatch(params: {
+ client: VoyageEmbeddingClient;
+ batchId: string;
+ wait: boolean;
+ pollIntervalMs: number;
+ timeoutMs: number;
+ debug?: (message: string, data?: Record) => void;
+ initial?: VoyageBatchStatus;
+}): Promise<{ outputFileId: string; errorFileId?: string }> {
+ const start = Date.now();
+ let current: VoyageBatchStatus | undefined = params.initial;
+ while (true) {
+ const status =
+ current ??
+ (await fetchVoyageBatchStatus({
+ client: params.client,
+ batchId: params.batchId,
+ }));
+ const state = status.status ?? "unknown";
+ if (state === "completed") {
+ if (!status.output_file_id) {
+ throw new Error(`voyage batch ${params.batchId} completed without output file`);
+ }
+ return {
+ outputFileId: status.output_file_id,
+ errorFileId: status.error_file_id ?? undefined,
+ };
+ }
+ if (["failed", "expired", "cancelled", "canceled"].includes(state)) {
+ const detail = status.error_file_id
+ ? await readVoyageBatchError({ client: params.client, errorFileId: status.error_file_id })
+ : undefined;
+ const suffix = detail ? `: ${detail}` : "";
+ throw new Error(`voyage batch ${params.batchId} ${state}${suffix}`);
+ }
+ if (!params.wait) {
+ throw new Error(`voyage batch ${params.batchId} still ${state}; wait disabled`);
+ }
+ if (Date.now() - start > params.timeoutMs) {
+ throw new Error(`voyage batch ${params.batchId} timed out after ${params.timeoutMs}ms`);
+ }
+ params.debug?.(`voyage batch ${params.batchId} ${state}; waiting ${params.pollIntervalMs}ms`);
+ await new Promise((resolve) => setTimeout(resolve, params.pollIntervalMs));
+ current = undefined;
+ }
+}
+
+export async function runVoyageEmbeddingBatches(params: {
+ client: VoyageEmbeddingClient;
+ agentId: string;
+ requests: VoyageBatchRequest[];
+ wait: boolean;
+ pollIntervalMs: number;
+ timeoutMs: number;
+ concurrency: number;
+ debug?: (message: string, data?: Record) => void;
+}): Promise