mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-27 22:32:33 +00:00
docs: absorb docs sweep
Co-authored-by: Kai <kai@itskai.dev> Co-authored-by: Weihang <gwh7078@163.com> Co-authored-by: Scott Long <longstoryscott@gmail.com> Co-authored-by: moejaberr <mjaber@uoguelph.ca> Co-authored-by: huihui0822 <109355071+huihui0822@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- 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.
|
||||
- Packaging: exclude documentation images and assets from the npm tarball, reducing published package size without affecting runtime docs search or CLI behavior. Thanks @SebTardif.
|
||||
|
||||
@@ -253,12 +253,13 @@ Pre-req checklist:
|
||||
5) Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
|
||||
6) No interactive system dialogs should be pending before test start.
|
||||
7) Canvas host is enabled and reachable from the device (do not run gateway with `OPENCLAW_SKIP_CANVAS_HOST=1`; startup logs should include `canvas host mounted at .../__openclaw__/`).
|
||||
8) Local operator test client pairing is approved. If first run fails with `pairing required`, approve latest pending device pairing request, then rerun:
|
||||
8) Local operator test client pairing is approved. If first run fails with `pairing required`, preview the latest pending request, approve the printed request ID, then rerun:
|
||||
9) For A2UI checks, keep the app on **Screen** tab; the node now auto-refreshes canvas capability once on first A2UI reachability failure (TTL-safe retry).
|
||||
|
||||
```bash
|
||||
openclaw devices list
|
||||
openclaw devices approve --latest
|
||||
openclaw devices approve --latest # preview only; copy the requestId from output
|
||||
openclaw devices approve <requestId>
|
||||
```
|
||||
|
||||
Run:
|
||||
@@ -284,7 +285,7 @@ What it does:
|
||||
Common failure quick-fixes:
|
||||
|
||||
- `pairing required` before tests start:
|
||||
- approve pending device pairing (`openclaw devices approve --latest`) and rerun.
|
||||
- list pending requests (`openclaw devices list`), then approve with the exact ID (`openclaw devices approve <requestId>`) and rerun.
|
||||
- `A2UI host not reachable` / `A2UI_HOST_NOT_CONFIGURED`:
|
||||
- ensure the Canvas plugin host is running and reachable, keep the app on the **Screen** tab. The app refreshes the Canvas plugin surface URL once before failing; if it still fails, reconnect app and rerun.
|
||||
- `NODE_BACKGROUND_UNAVAILABLE: canvas unavailable`:
|
||||
|
||||
@@ -137,7 +137,18 @@
|
||||
"zh-CN/concepts/session",
|
||||
"zh-CN/concepts/session-pruning",
|
||||
"zh-CN/concepts/session-tool",
|
||||
"zh-CN/concepts/memory",
|
||||
{
|
||||
"group": "记忆",
|
||||
"pages": [
|
||||
"zh-CN/concepts/memory",
|
||||
"zh-CN/concepts/memory-builtin",
|
||||
"zh-CN/concepts/memory-qmd",
|
||||
"zh-CN/concepts/memory-honcho",
|
||||
"zh-CN/concepts/memory-search",
|
||||
"zh-CN/concepts/active-memory",
|
||||
"zh-CN/concepts/dreaming"
|
||||
]
|
||||
},
|
||||
"zh-CN/concepts/compaction"
|
||||
]
|
||||
},
|
||||
|
||||
@@ -166,6 +166,24 @@ Accounts map to `zalouser` profiles in OpenClaw state. Example:
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
The Zalo Personal plugin can also read profile selection from environment variables:
|
||||
|
||||
- `ZALOUSER_PROFILE`: profile name to use when no `profile` is set in channel or account config.
|
||||
- `ZCA_PROFILE`: legacy fallback profile name, used only when `ZALOUSER_PROFILE` is not set.
|
||||
|
||||
Profile names select the saved Zalo login credentials in OpenClaw state. Resolution order is:
|
||||
|
||||
1. Explicit `profile` in config.
|
||||
2. `ZALOUSER_PROFILE`.
|
||||
3. `ZCA_PROFILE`.
|
||||
4. The account id for non-default accounts, or `default` for the default account.
|
||||
|
||||
For multi-account setups, prefer setting `profile` on each account in config so
|
||||
one environment variable does not make multiple accounts share the same login
|
||||
session.
|
||||
|
||||
## Typing, reactions, and delivery acknowledgements
|
||||
|
||||
- OpenClaw sends a typing event before dispatching a reply (best-effort).
|
||||
|
||||
@@ -42,6 +42,8 @@ openclaw gateway run
|
||||
- `openclaw onboard --mode local` and `openclaw setup` are expected to write `gateway.mode=local`. If the file exists but `gateway.mode` is missing, treat that as a broken or clobbered config and repair it instead of assuming local mode implicitly.
|
||||
- If the file exists and `gateway.mode` is missing, the Gateway treats that as suspicious config damage and refuses to "guess local" for you.
|
||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||
- `lan`, `tailnet`, and `custom` currently resolve over IPv4-only BYOH paths.
|
||||
- IPv6-only BYOH is not natively supported on this path today. Use an IPv4 sidecar or proxy if the host itself is IPv6-only.
|
||||
- `SIGUSR1` triggers an in-process restart when authorized (`commands.restart` is enabled by default; set `commands.restart: false` to block manual restart, while gateway tool/config apply/update remain allowed).
|
||||
- `SIGINT`/`SIGTERM` handlers stop the gateway process, but they don't restore any custom terminal state. If you wrap the CLI with a TUI or raw-mode input, restore the terminal before exit.
|
||||
|
||||
@@ -54,7 +56,7 @@ openclaw gateway run
|
||||
WebSocket port (default comes from config/env; usually `18789`).
|
||||
</ParamField>
|
||||
<ParamField path="--bind <loopback|lan|tailnet|auto|custom>" type="string">
|
||||
Listener bind mode.
|
||||
Listener bind mode. `lan`, `tailnet`, and `custom` currently resolve over IPv4-only paths.
|
||||
</ParamField>
|
||||
<ParamField path="--auth <token|password>" type="string">
|
||||
Auth mode override.
|
||||
@@ -74,6 +76,9 @@ openclaw gateway run
|
||||
<ParamField path="--tailscale-reset-on-exit" type="boolean">
|
||||
Reset Tailscale serve/funnel config on shutdown.
|
||||
</ParamField>
|
||||
<ParamField path="--bind custom + gateway.customBindHost" type="string">
|
||||
Expects an IPv4 address today. For IPv6-only BYOH, place an IPv4 sidecar or proxy in front of the Gateway and point OpenClaw at that IPv4 endpoint.
|
||||
</ParamField>
|
||||
<ParamField path="--allow-unconfigured" type="boolean">
|
||||
Allow gateway start without `gateway.mode=local` in config. Bypasses the startup guard for ad-hoc/dev bootstrap only; does not write or repair the config file.
|
||||
</ParamField>
|
||||
|
||||
@@ -757,6 +757,14 @@ rather than the pre-handshake defaults.
|
||||
- `gateway.controlUi.dangerouslyDisableDeviceAuth=true` (break-glass, severe security downgrade).
|
||||
- direct-loopback `gateway-client` backend RPCs authenticated with the shared
|
||||
gateway token/password.
|
||||
- Omitting device identity has scope consequences. When a Control UI connection
|
||||
lacks device identity, `shouldClearUnboundScopesForMissingDeviceIdentity`
|
||||
clears self-declared scopes to an empty set for token, password, and
|
||||
trusted-proxy auth. The connection is allowed on explicit trust paths, but
|
||||
scope-gated methods fail. The exception is local Control UI token/password
|
||||
sessions with `allowInsecureAuth`, which preserve scopes. For other cases,
|
||||
set `gateway.controlUi.dangerouslyDisableDeviceAuth=true` only as a
|
||||
break-glass scope-preservation path.
|
||||
- All connections must sign the server-provided `connect.challenge` nonce.
|
||||
|
||||
### Device auth migration diagnostics
|
||||
|
||||
@@ -339,6 +339,94 @@ the config fields that accept SecretRefs.
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="password-store (`pass`)">
|
||||
Use a small resolver wrapper when you want SecretRef ids to map directly to
|
||||
`pass` entries. Save this as an executable in an absolute path that passes
|
||||
your exec-provider path checks, for example
|
||||
`/usr/local/bin/openclaw-pass-resolver`. The `#!/usr/bin/env node` shebang
|
||||
resolves `node` from the resolver process `PATH`, so include `PATH` in
|
||||
`passEnv`. If `pass` is not on that `PATH`, set `PASS_BIN` in the parent
|
||||
environment and include it in `passEnv` too:
|
||||
|
||||
```js
|
||||
#!/usr/bin/env node
|
||||
const { spawnSync } = require("node:child_process");
|
||||
|
||||
let stdin = "";
|
||||
process.stdin.setEncoding("utf8");
|
||||
process.stdin.on("data", (chunk) => {
|
||||
stdin += chunk;
|
||||
});
|
||||
process.stdin.on("error", (err) => {
|
||||
process.stderr.write(`${err.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
process.stdin.on("end", () => {
|
||||
let request;
|
||||
try {
|
||||
request = JSON.parse(stdin || "{}");
|
||||
} catch (err) {
|
||||
process.stderr.write(`Failed to parse request: ${err.message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const passBin = process.env.PASS_BIN || "pass";
|
||||
const values = {};
|
||||
const errors = {};
|
||||
|
||||
for (const id of request.ids ?? []) {
|
||||
const result = spawnSync(passBin, ["show", id], { encoding: "utf8" });
|
||||
if (result.status === 0) {
|
||||
values[id] = result.stdout.split(/\r?\n/, 1)[0] ?? "";
|
||||
} else {
|
||||
errors[id] = { message: (result.stderr || `pass exited ${result.status}`).trim() };
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(JSON.stringify({ protocolVersion: 1, values, errors }));
|
||||
});
|
||||
```
|
||||
|
||||
Then configure the exec provider and point `apiKey` at the `pass` entry path:
|
||||
|
||||
```json5
|
||||
{
|
||||
secrets: {
|
||||
providers: {
|
||||
pass_store: {
|
||||
source: "exec",
|
||||
command: "/usr/local/bin/openclaw-pass-resolver",
|
||||
passEnv: ["PATH", "HOME", "GNUPGHOME", "GPG_TTY", "PASSWORD_STORE_DIR", "PASS_BIN"],
|
||||
jsonOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
models: [{ id: "gpt-5", name: "gpt-5" }],
|
||||
apiKey: {
|
||||
source: "exec",
|
||||
provider: "pass_store",
|
||||
id: "openclaw/providers/openai/apiKey",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
Keep the secret on the first line of the `pass` entry, or customize the
|
||||
wrapper if you want to return the full `pass show` output instead. After
|
||||
updating config, verify both the static audit and the exec resolver path:
|
||||
|
||||
```bash
|
||||
openclaw secrets audit --check
|
||||
openclaw secrets audit --allow-exec
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="sops">
|
||||
```json5
|
||||
{
|
||||
|
||||
@@ -59,6 +59,19 @@ Implications:
|
||||
- Your reverse proxy auth policy and `allowUsers` become the effective access control.
|
||||
- Keep gateway ingress locked to trusted proxy IPs only (`gateway.trustedProxies` + firewall).
|
||||
|
||||
**Scope clearing without device identity:** Because the browser over plain HTTP
|
||||
cannot create the device identity that OpenClaw uses to bind operator scopes,
|
||||
trusted-proxy WebSocket connections that lack device identity have their
|
||||
self-declared scopes cleared to an empty set. The connection is allowed, but
|
||||
scope-gated methods (`operator.read`, `operator.write`, etc.) fail with
|
||||
`missing scope`.
|
||||
|
||||
To preserve operator scopes on trusted-proxy WebSocket connections without
|
||||
device identity, set `gateway.controlUi.dangerouslyDisableDeviceAuth: true`.
|
||||
This is a break-glass flag (`openclaw security audit` reports it as critical).
|
||||
Use it only when the reverse proxy is the sole path to the Gateway and device
|
||||
identity cannot be established.
|
||||
|
||||
## Configuration
|
||||
|
||||
```json5
|
||||
@@ -311,6 +324,11 @@ Loopback trusted-proxy identity headers still fail closed: same-host callers are
|
||||
|
||||
Trusted-proxy auth is an **identity-bearing** HTTP mode, so callers may optionally declare operator scopes with `x-openclaw-scopes`.
|
||||
|
||||
Note: `x-openclaw-scopes` applies to HTTP endpoints only. WebSocket scopes are
|
||||
determined by the Gateway protocol handshake and device identity binding. For
|
||||
WebSocket scope behavior with trusted-proxy, see
|
||||
[Control UI pairing behavior](#control-ui-pairing-behavior).
|
||||
|
||||
Examples:
|
||||
|
||||
- `x-openclaw-scopes: operator.read`
|
||||
@@ -407,6 +425,20 @@ The audit checks for:
|
||||
- You are not relying on wildcard origins unless you intentionally want allow-all behavior.
|
||||
- If you intentionally use Host-header fallback mode, `gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true` is set deliberately.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Connection succeeds but methods report missing scope">
|
||||
The WebSocket connects, but `chat.history` or `sessions.list` fails with
|
||||
`missing scope: operator.read`.
|
||||
|
||||
This is expected for trusted-proxy WebSocket connections without device
|
||||
identity. Connections lacking device identity have their scopes cleared. The
|
||||
browser cannot generate device identity over plain HTTP.
|
||||
|
||||
Fix:
|
||||
|
||||
- Set `gateway.controlUi.dangerouslyDisableDeviceAuth: true` to preserve operator scopes on trusted-proxy WebSocket connections, or
|
||||
- Use device identity pairing so scopes are bound to the device token.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="WebSocket still failing">
|
||||
Make sure your proxy:
|
||||
|
||||
@@ -69,6 +69,10 @@ export PEEKABOO_BRIDGE_SOCKET=/path/to/bridge.sock
|
||||
|
||||
- The bridge validates **caller code signatures**; an allowlist of TeamIDs is
|
||||
enforced (Peekaboo host TeamID + OpenClaw app TeamID).
|
||||
- Prefer the signed bridge/app identity over a generic `node` runtime for
|
||||
Accessibility. Granting Accessibility to `node` lets any package launched by
|
||||
that Node executable inherit GUI automation access; see
|
||||
[macOS permissions](/platforms/mac/permissions#accessibility-grants-for-node-and-cli-runtimes).
|
||||
- Requests time out after ~10 seconds.
|
||||
- If required permissions are missing, the bridge returns a clear error message
|
||||
rather than launching System Settings.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
summary: "macOS permission persistence (TCC) and signing requirements"
|
||||
read_when:
|
||||
- Debugging missing or stuck macOS permission prompts
|
||||
- Deciding whether to grant Accessibility to node or a CLI runtime
|
||||
- Packaging or signing the macOS app
|
||||
- Changing bundle IDs or app install paths
|
||||
title: "macOS permissions"
|
||||
@@ -22,6 +23,25 @@ macOS treats the app as new and may drop or hide prompts.
|
||||
Ad-hoc signatures generate a new identity every build. macOS will forget previous
|
||||
grants, and prompts can disappear entirely until the stale entries are cleared.
|
||||
|
||||
## Accessibility grants for Node and CLI runtimes
|
||||
|
||||
Prefer granting Accessibility to OpenClaw.app, Peekaboo.app, or another signed
|
||||
helper with its own bundle identifier instead of a generic `node` binary.
|
||||
|
||||
macOS TCC grants Accessibility to the code identity of the process it sees. If a
|
||||
Homebrew, nvm, pnpm, or npm workflow causes a shared `node` executable to
|
||||
receive Accessibility, any JavaScript package launched through that same
|
||||
executable may inherit GUI automation privileges.
|
||||
|
||||
Treat a `node` entry in System Settings as broad permission for that Node
|
||||
runtime, not as permission for one npm package. Avoid granting Accessibility to
|
||||
`node` unless you trust every script and package launched through that exact
|
||||
Node install.
|
||||
|
||||
If you accidentally granted Accessibility to `node`, remove that entry from
|
||||
System Settings -> Privacy & Security -> Accessibility. Then grant the signed
|
||||
app or helper that should own UI automation.
|
||||
|
||||
## Recovery checklist when prompts disappear
|
||||
|
||||
1. Quit the app.
|
||||
|
||||
@@ -104,7 +104,8 @@ If the app says `pairing required`:
|
||||
|
||||
```bash
|
||||
openclaw devices list
|
||||
openclaw devices approve --latest
|
||||
openclaw devices approve --latest # preview only; copy the requestId from output
|
||||
openclaw devices approve <requestId>
|
||||
```
|
||||
|
||||
If the app says `bootstrap token invalid or expired`:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { withMockedPlatform } from "../test-utils/vitest-spies.js";
|
||||
import {
|
||||
formatControlUiSshHint,
|
||||
handleReset,
|
||||
normalizeGatewayTokenInput,
|
||||
openUrl,
|
||||
@@ -150,6 +151,16 @@ describe("resolveBrowserOpenCommand", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("formatControlUiSshHint", () => {
|
||||
it("includes the IPv4-only BYOH note and workaround", () => {
|
||||
const hint = formatControlUiSshHint({ port: 18789 });
|
||||
expect(hint).toContain("BYOH note: lan, tailnet, and custom bind are currently IPv4-only.");
|
||||
expect(hint).toContain(
|
||||
"If your host is IPv6-only, use an IPv4 sidecar or proxy in front of the Gateway.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("probeGatewayReachable", () => {
|
||||
it("uses a hello-only probe for onboarding reachability", async () => {
|
||||
mocks.probeGateway.mockResolvedValueOnce({
|
||||
|
||||
@@ -199,6 +199,8 @@ export function formatControlUiSshHint(params: {
|
||||
"Then open:",
|
||||
localUrl,
|
||||
authedUrl,
|
||||
"BYOH note: lan, tailnet, and custom bind are currently IPv4-only.",
|
||||
"If your host is IPv6-only, use an IPv4 sidecar or proxy in front of the Gateway.",
|
||||
"Docs:",
|
||||
"https://docs.openclaw.ai/gateway/remote",
|
||||
"https://docs.openclaw.ai/web/control-ui",
|
||||
|
||||
@@ -456,14 +456,15 @@ export type GatewayConfig = {
|
||||
/**
|
||||
* Bind address policy for the Gateway WebSocket + Control UI HTTP server.
|
||||
* - auto: Loopback (127.0.0.1) if available, else 0.0.0.0 (fallback to all interfaces)
|
||||
* - lan: 0.0.0.0 (all interfaces, no fallback)
|
||||
* - lan: 0.0.0.0 (all interfaces, no fallback, current BYOH path is IPv4-only)
|
||||
* - loopback: 127.0.0.1 (local-only)
|
||||
* - tailnet: Tailnet IPv4 if available (100.64.0.0/10), else loopback
|
||||
* - custom: User-specified IP, fallback to 0.0.0.0 if unavailable (requires customBindHost)
|
||||
* - custom: User-specified IPv4 address, fallback to 0.0.0.0 if unavailable (requires customBindHost)
|
||||
* IPv6-only BYOH is not natively supported on this path today. Use an IPv4 sidecar or proxy.
|
||||
* Default: loopback (127.0.0.1).
|
||||
*/
|
||||
bind?: GatewayBindMode;
|
||||
/** Custom IP address for bind="custom" mode. Fallback: 0.0.0.0. */
|
||||
/** Custom IPv4 address for bind="custom" mode. IPv6-only BYOH requires an IPv4 sidecar or proxy. */
|
||||
customBindHost?: string;
|
||||
controlUi?: GatewayControlUiConfig;
|
||||
auth?: GatewayAuthConfig;
|
||||
|
||||
@@ -12,6 +12,8 @@ export function resolveControlUiLinks(params: {
|
||||
basePath?: string;
|
||||
tlsEnabled?: boolean;
|
||||
}): { httpUrl: string; wsUrl: string } {
|
||||
// Current BYOH truth: lan, tailnet, and custom bind resolve through IPv4-only helpers.
|
||||
// IPv6-only hosts need an IPv4 sidecar or proxy in front of the Gateway.
|
||||
const port = params.port;
|
||||
const bind = params.bind ?? "loopback";
|
||||
const customBindHost = params.customBindHost?.trim();
|
||||
|
||||
Reference in New Issue
Block a user