diff --git a/CHANGELOG.md b/CHANGELOG.md index 096ea8c72790..9495d905d517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Docs: clarify browser CDP diagnostics, Plugin SDK allowlist imports, status-reaction timing defaults, queue steering behavior, limited-tool troubleshooting, cron HEARTBEAT handling, Telegram multi-agent groups, Bitwarden SecretRef setup, and EasyRunner deployments. Thanks @Quratulain-bilal, @mbelinky, @Mickey-, @vancece, @xenouzik, @posigit, @surlymochan, @janaka, and @choiking. - Docs: clarify IPv4-only Gateway BYOH binding, trusted-proxy scope clearing, Android pairing approval, macOS Accessibility grants, Zalo profile env vars, password-store SecretRef setup, and Chinese memory navigation. Thanks @itskai-dev, @gwh7078, @longstoryscott, @MoeJaberr, and @yuaiccc. - Docs: consolidate GLM under Z.AI, add the Upstash Box install guide and Gateway exposure runbook, clarify MEDIA directives, Copilot and Voyage setup, config path quoting, real behavior proof, and memory-file write guidance. Thanks @BobDu, @alitariksahin, @Jefsky, @musaabhasan, @OmerZeyveli, @leno23, @WuKongAI-CMU, @luoyanglang, and @majin1102. - Docs: clarify media provider credentials, Codex/OpenClaw code-mode boundaries, Slack and Telegram ack reactions, Feishu dynamic agents, secrets plaintext boundaries, memory guidance, and Chinese glossary terms. Thanks @nielskaspers, @cosmopolitan033, @drclaw-iq, @alexgduarte, @zccyman, @chengoak, and @cassthebandit. diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index 5dd94b81bc09..1a7f1c846e9a 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -99,6 +99,13 @@ This fires ~5–6 times per month instead of 0–1 times per month. OpenClaw use **Main session** jobs enqueue a system event into a cron-owned run lane and optionally wake the heartbeat (`--wake now` or `--wake next-heartbeat`). They can use the target main session's last delivery context for replies, but they do not append routine cron turns to the human chat lane and do not extend daily/idle reset freshness for the target session. **Isolated** jobs run a dedicated agent turn with a fresh session. **Custom sessions** (`session:xxx`) persist context across runs, enabling workflows like daily standups that build on previous summaries. + + Main-session cron events are self-contained system-event reminders. They do + not automatically include the default heartbeat prompt's "Read + HEARTBEAT.md" instruction. If a recurring reminder should consult + `HEARTBEAT.md`, say that explicitly in the cron event text or in the + agent's own instructions. + For isolated jobs, "fresh session" means a new transcript/session id for each run. OpenClaw may carry safe preferences such as thinking/fast/verbose settings, labels, and explicit user-selected model/auth overrides, but it does not inherit ambient conversation context from an older cron row: channel/group routing, send or queue policy, elevation, origin, or ACP runtime binding. Use `current` or `session:` when a recurring job should deliberately build on the same conversation context. diff --git a/docs/concepts/multi-agent.md b/docs/concepts/multi-agent.md index d9f102be14f3..f31aa2cfba09 100644 --- a/docs/concepts/multi-agent.md +++ b/docs/concepts/multi-agent.md @@ -357,6 +357,11 @@ Common channels supporting this pattern include: - Create one bot per agent with BotFather and copy each token. - Tokens live in `channels.telegram.accounts..botToken` (default account can use `TELEGRAM_BOT_TOKEN`). + - For multiple bots in the same Telegram group, invite each bot and mention the bot that should answer. + - Disable BotFather Privacy Mode for each group bot, then re-add the bot so Telegram applies the setting. + - Allow groups with `channels.telegram.groups`, or use `groupPolicy: "open"` only for trusted group deployments. + - Put sender user IDs in `groupAllowFrom`. Group and supergroup IDs belong in `channels.telegram.groups`, not `groupAllowFrom`. + - Bind by `accountId` so each bot routes to its own agent. diff --git a/docs/concepts/queue.md b/docs/concepts/queue.md index b03688c84795..59789ffeb71a 100644 --- a/docs/concepts/queue.md +++ b/docs/concepts/queue.md @@ -78,6 +78,20 @@ quiet window in `steer` mode: Defaults: `debounceMs: 500`, `cap: 20`, `drop: summarize`. +## Steer and streaming + +When channel streaming is `partial` or `block`, steering can look like several +short visible replies while the active run reaches runtime boundaries: + +- `partial`: the preview may finalize early, then a new preview starts after + steering is accepted. +- `block`: draft-sized blocks can create the same sequential appearance. +- Without streaming, steering falls back to a followup after the active run when + the runtime cannot accept same-turn steering. + +`steer` does not abort in-flight tools. Use `/queue interrupt` when the newest +message should abort the current run. + ## Precedence For mode selection, OpenClaw resolves: diff --git a/docs/docs.json b/docs/docs.json index fdd601e3f452..1d4202043dfc 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1437,7 +1437,8 @@ "platforms/linux", "platforms/windows", "platforms/android", - "platforms/ios" + "platforms/ios", + "platforms/easyrunner" ] }, { diff --git a/docs/gateway/secrets.md b/docs/gateway/secrets.md index e88e5b94de49..82638a2e56a4 100644 --- a/docs/gateway/secrets.md +++ b/docs/gateway/secrets.md @@ -311,6 +311,60 @@ the config fields that accept SecretRefs. } ``` + + Use a resolver wrapper when you want SecretRef ids to map to Bitwarden + Secrets Manager item keys. The repository includes + `scripts/secrets/openclaw-bws-resolver.mjs`; install or copy it to an absolute + trusted path on the host that runs the Gateway. + + Requirements: + + - Bitwarden Secrets Manager CLI (`bws`) installed on the Gateway host. + - `BWS_ACCESS_TOKEN` available to the Gateway service. + - `PATH` passed to the resolver, or `BWS_BIN` set to the absolute `bws` + binary path. + + ```json5 + { + secrets: { + providers: { + bws: { + source: "exec", + command: "/usr/local/bin/openclaw-bws-resolver.mjs", + passEnv: ["BWS_ACCESS_TOKEN", "PATH", "BWS_BIN"], + jsonOnly: true, + }, + }, + }, + models: { + providers: { + openai: { + baseUrl: "https://api.openai.com/v1", + models: [{ id: "gpt-5", name: "gpt-5" }], + apiKey: { + source: "exec", + provider: "bws", + id: "openclaw/providers/openai/apiKey", + }, + }, + }, + }, + } + ``` + + The resolver batches requested ids, runs `bws secret list`, and returns + values for matching secret `key` fields. Use keys that satisfy the exec + SecretRef id contract, such as `openclaw/providers/openai/apiKey`; env-var + style keys with underscores are rejected before the resolver runs. If more + than one visible Bitwarden secret has the same requested key, the resolver + fails that id as ambiguous instead of choosing one. After updating config, + verify the resolver path: + + ```bash + openclaw secrets audit --allow-exec + ``` + + ```json5 { diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md index fb50dd24e85b..8c6b1a247f1f 100644 --- a/docs/help/troubleshooting.md +++ b/docs/help/troubleshooting.md @@ -34,6 +34,31 @@ Good output in one line: gateway is unreachable, the command falls back to config-only summaries. - `openclaw logs --follow` → steady activity, no repeating fatal errors. +## Assistant feels limited or missing tools + +If the assistant cannot inspect files, run commands, use browser automation, or +see expected tools, check the effective tool profile first: + +```bash +openclaw status +openclaw status --all +openclaw doctor +``` + +Common causes: + +- `tools.profile: "messaging"` is intentionally narrow for chat-only agents. +- `tools.profile: "coding"` is the usual profile for repository, file, shell, + and runtime workflows. +- `tools.profile: "full"` exposes the broadest tool set and should be limited + to trusted operator-controlled agents. +- Per-agent `agents.list[].tools` overrides can narrow or expand the root + profile for one agent. + +Change the root or per-agent tool profile, then restart or reload the Gateway +and run `openclaw status --all` again. See [Tools](/tools) for the profile +model and allow/deny overrides. + ## Anthropic long context 429 If you see: diff --git a/docs/platforms/easyrunner.md b/docs/platforms/easyrunner.md new file mode 100644 index 000000000000..48f259d8effa --- /dev/null +++ b/docs/platforms/easyrunner.md @@ -0,0 +1,109 @@ +--- +summary: "Run the OpenClaw Gateway on EasyRunner with Podman and Caddy" +read_when: + - Deploying OpenClaw on EasyRunner + - Running the Gateway behind EasyRunner's Caddy proxy + - Choosing persistent volumes and auth for a hosted Gateway +title: "EasyRunner" +--- + +EasyRunner can host the OpenClaw Gateway as a small containerized app behind its +Caddy proxy. This guide assumes an EasyRunner host that runs Podman-compatible +Compose apps and exposes HTTPS through Caddy. + +## Before you begin + +- An EasyRunner server with a domain routed to it. +- A built or published OpenClaw container image. +- A persistent config volume for `/home/node/.openclaw`. +- A persistent workspace volume for `/workspace`. +- A strong Gateway token or password. + +Keep device auth enabled when possible. If your reverse proxy deployment cannot +carry device identity correctly, fix trusted-proxy settings first; use +dangerous auth bypasses only for a fully private, operator-controlled network. + +## Compose app + +Create an EasyRunner app with a Compose file shaped like this: + +```yaml +services: + openclaw: + image: ghcr.io/openclaw/openclaw:latest + restart: unless-stopped + environment: + OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN} + OPENCLAW_HOME: /home/node + OPENCLAW_STATE_DIR: /home/node/.openclaw + OPENCLAW_CONFIG_PATH: /home/node/.openclaw/openclaw.json + OPENCLAW_WORKSPACE_DIR: /workspace + volumes: + - openclaw-config:/home/node/.openclaw + - openclaw-workspace:/workspace + labels: + caddy: openclaw.example.com + caddy.reverse_proxy: "{{upstreams 1455}}" + command: ["openclaw", "gateway", "--bind", "lan", "--port", "1455"] + +volumes: + openclaw-config: + openclaw-workspace: +``` + +Replace `openclaw.example.com` with your Gateway hostname. Store +`OPENCLAW_GATEWAY_TOKEN` in EasyRunner's secret/environment manager instead of +committing it to the app definition. + +## Configure OpenClaw + +Inside the persistent config volume, keep the Gateway reachable only through +the proxy and require auth: + +```json5 +{ + gateway: { + bind: "lan", + port: 1455, + auth: { + token: "${OPENCLAW_GATEWAY_TOKEN}", + }, + }, +} +``` + +If Caddy terminates TLS for the Gateway, configure trusted proxy settings for +the exact proxy path rather than disabling auth checks globally. See +[Trusted proxy auth](/gateway/trusted-proxy-auth). + +## Verify + +From your workstation: + +```bash +openclaw gateway probe --url https://openclaw.example.com --token +openclaw gateway status --url https://openclaw.example.com --token +``` + +From the EasyRunner host, check the app logs for a listening Gateway and no +startup SecretRef, plugin, or channel auth failures. + +## Updates and backups + +- Pull or build the new OpenClaw image, then redeploy the EasyRunner app. +- Back up the `openclaw-config` volume before updates. +- Back up `openclaw-workspace` if agents write durable project data there. +- Run `openclaw doctor` after major updates to catch config migrations and + service warnings. + +## Troubleshooting + +- `gateway probe` cannot connect: confirm the Caddy hostname points at the app + and that the container listens on `0.0.0.0:1455`. +- Auth fails: rotate the token in EasyRunner secrets and the local client + command together. +- Files are root-owned after restore: repair the mounted volumes so the + container user can write `/home/node/.openclaw` and `/workspace`. +- Browser or channel plugins fail: check whether the required external + binaries, network egress, and mounted credentials are available inside the + container. diff --git a/docs/platforms/index.md b/docs/platforms/index.md index 95baae3beaf7..ffdf38f0586d 100644 --- a/docs/platforms/index.md +++ b/docs/platforms/index.md @@ -30,6 +30,7 @@ Native companion apps for Windows are also planned; the Gateway is recommended v - GCP (Compute Engine): [GCP](/install/gcp) - Azure (Linux VM): [Azure](/install/azure) - exe.dev (VM + HTTPS proxy): [exe.dev](/install/exe-dev) +- EasyRunner (Podman + Caddy): [EasyRunner](/platforms/easyrunner) ## Common links diff --git a/docs/plugins/sdk-migration.md b/docs/plugins/sdk-migration.md index cac5fda66ee7..9d1232dbff4b 100644 --- a/docs/plugins/sdk-migration.md +++ b/docs/plugins/sdk-migration.md @@ -563,8 +563,7 @@ releases. | `plugin-sdk/fetch-runtime` | Wrapped fetch/proxy helpers | `resolveFetch`, proxy helpers, EnvHttpProxyAgent option helpers | | `plugin-sdk/host-runtime` | Host normalization helpers | `normalizeHostname`, `normalizeScpRemoteHost` | | `plugin-sdk/retry-runtime` | Retry helpers | `RetryConfig`, `retryAsync`, policy runners | - | `plugin-sdk/allow-from` | Allowlist formatting | `formatAllowFromLowercase` | - | `plugin-sdk/allowlist-resolution` | Allowlist input mapping | `mapAllowlistResolutionInputs` | + | `plugin-sdk/allow-from` | Allowlist formatting and input mapping | `formatAllowFromLowercase`, `mapAllowlistResolutionInputs` | | `plugin-sdk/command-auth` | Command gating and command-surface helpers | `resolveControlCommandGate`, sender-authorization helpers, command registry helpers including dynamic argument menu formatting | | `plugin-sdk/command-status` | Command status/help renderers | `buildCommandsMessage`, `buildCommandsMessagePaginated`, `buildHelpMessage` | | `plugin-sdk/secret-input` | Secret input parsing | Secret input helpers | diff --git a/docs/tools/browser.md b/docs/tools/browser.md index 5f94d2ec92bb..166066f1ffa3 100644 --- a/docs/tools/browser.md +++ b/docs/tools/browser.md @@ -442,6 +442,10 @@ CDP URL shapes and picks the right connection strategy automatically: providers can still use their root WebSocket endpoint when their discovery endpoint advertises a short-lived URL that is not suitable for Playwright CDP. +`openclaw browser doctor` uses the same discovery-first, WebSocket-fallback +logic as runtime attach, so a bare-root URL that connects successfully is not +reported as unreachable by diagnostics. + ### Browserbase [Browserbase](https://www.browserbase.com) is a cloud platform for running diff --git a/scripts/secrets/openclaw-bws-resolver.mjs b/scripts/secrets/openclaw-bws-resolver.mjs new file mode 100755 index 000000000000..4fa8bb317dd0 --- /dev/null +++ b/scripts/secrets/openclaw-bws-resolver.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +import { execFileSync } from "node:child_process"; + +const readStdin = () => + new Promise((resolve, reject) => { + let input = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => { + input += chunk; + }); + process.stdin.on("end", () => resolve(input)); + process.stdin.on("error", reject); + }); + +const parseRequest = (input) => { + try { + return JSON.parse(input || "{}"); + } catch (error) { + throw new Error(`Failed to parse request JSON: ${error.message}`); + } +}; + +const isSecretRecord = (value) => + value && + typeof value === "object" && + typeof value.key === "string" && + typeof value.value === "string"; + +const main = async () => { + const request = parseRequest(await readStdin()); + if (request.protocolVersion !== 1) { + throw new Error("Unsupported SecretRef protocolVersion"); + } + + const ids = Array.isArray(request.ids) + ? request.ids.filter((id) => typeof id === "string" && id.length > 0) + : []; + if (ids.length === 0) { + process.stdout.write(JSON.stringify({ protocolVersion: 1, values: {}, errors: {} })); + return; + } + + if (!process.env.BWS_ACCESS_TOKEN) { + throw new Error("BWS_ACCESS_TOKEN is required"); + } + + const bwsBin = + process.env.BWS_BIN && process.env.BWS_BIN.trim() ? process.env.BWS_BIN.trim() : "bws"; + const raw = execFileSync(bwsBin, ["secret", "list"], { + encoding: "utf8", + env: { + BWS_ACCESS_TOKEN: process.env.BWS_ACCESS_TOKEN, + PATH: process.env.PATH || "", + }, + maxBuffer: 1024 * 1024, + timeout: 15_000, + }); + + let secrets; + try { + secrets = JSON.parse(raw); + } catch (error) { + throw new Error(`Failed to parse bws output: ${error.message}`); + } + + const byKey = new Map(); + for (const secret of Array.isArray(secrets) ? secrets : []) { + if (isSecretRecord(secret)) { + const values = byKey.get(secret.key) || []; + values.push(secret.value); + byKey.set(secret.key, values); + } + } + + const values = {}; + const errors = {}; + for (const id of ids) { + const matches = byKey.get(id) || []; + if (matches.length === 1) { + values[id] = matches[0]; + } else if (matches.length > 1) { + errors[id] = { message: "ambiguous duplicate key" }; + } else { + errors[id] = { message: "not found" }; + } + } + + process.stdout.write(JSON.stringify({ protocolVersion: 1, values, errors })); +}; + +main().catch((error) => { + process.stderr.write(`${error.message}\n`); + process.exit(1); +}); diff --git a/src/config/schema.help.ts b/src/config/schema.help.ts index 6c643fe6b92a..fab418e88295 100644 --- a/src/config/schema.help.ts +++ b/src/config/schema.help.ts @@ -1918,7 +1918,7 @@ export const FIELD_HELP: Record = { "messages.statusReactions.emojis": "Override default status reaction emojis. Keys: queued, thinking, compacting, tool, coding, web, deploy, build, concierge, done, error, stallSoft, stallHard. Telegram chooses the first supported fallback when a configured emoji is not available in the chat.", "messages.statusReactions.timing": - "Override default timing. Keys: debounceMs (700), stallSoftMs (25000), stallHardMs (60000), doneHoldMs (1500), errorHoldMs (2500).", + "Override default timing. Keys: debounceMs (700), stallSoftMs (10000), stallHardMs (30000), doneHoldMs (1500), errorHoldMs (2500).", "messages.inbound.debounceMs": "Debounce window (ms) for batching rapid inbound messages from the same sender (0 to disable).", }; diff --git a/src/config/types.messages.ts b/src/config/types.messages.ts index 1bfccaf47eff..81fe64f333fb 100644 --- a/src/config/types.messages.ts +++ b/src/config/types.messages.ts @@ -79,9 +79,9 @@ export type StatusReactionsEmojiConfig = { export type StatusReactionsTimingConfig = { /** Debounce interval for intermediate states (ms). Default: 700. */ debounceMs?: number; - /** Soft stall warning timeout (ms). Default: 25000. */ + /** Soft stall warning timeout (ms). Default: 10000. */ stallSoftMs?: number; - /** Hard stall warning timeout (ms). Default: 60000. */ + /** Hard stall warning timeout (ms). Default: 30000. */ stallHardMs?: number; /** How long to hold done emoji before cleanup (ms). Default: 1500. */ doneHoldMs?: number;