mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 14:01:24 +08:00
Compare commits
1 Commits
fix/nodes-
...
client-sid
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c61fb69c1 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,3 +1 @@
|
||||
* text=auto eol=lf
|
||||
CLAUDE.md -text
|
||||
src/gateway/server-methods/CLAUDE.md -text
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,8 +17,6 @@ __pycache__/
|
||||
ui/src/ui/__screenshots__/
|
||||
ui/playwright-report/
|
||||
ui/test-results/
|
||||
packages/dashboard-next/.next/
|
||||
packages/dashboard-next/out/
|
||||
|
||||
# Mise configuration files
|
||||
mise.toml
|
||||
@@ -103,4 +101,3 @@ package-lock.json
|
||||
apps/ios/LocalSigning.xcconfig
|
||||
# Generated protocol schema (produced via pnpm protocol:gen)
|
||||
dist/protocol.schema.json
|
||||
.ant-colony/
|
||||
|
||||
2
.npmrc
2
.npmrc
@@ -1 +1 @@
|
||||
# pnpm build-script allowlist lives in package.json -> pnpm.onlyBuiltDependencies.
|
||||
allow-build-scripts=@whiskeysockets/baileys,sharp,esbuild,protobufjs,fs-ext,node-pty,@lydell/node-pty,@matrix-org/matrix-sdk-crypto-nodejs
|
||||
|
||||
@@ -11,14 +11,12 @@
|
||||
"ignorePatterns": [
|
||||
"apps/",
|
||||
"assets/",
|
||||
"CLAUDE.md",
|
||||
"docker-compose.yml",
|
||||
"dist/",
|
||||
"docs/_layouts/",
|
||||
"node_modules/",
|
||||
"patches/",
|
||||
"pnpm-lock.yaml/",
|
||||
"src/gateway/server-methods/CLAUDE.md",
|
||||
"src/auto-reply/reply/export-html/",
|
||||
"Swabble/",
|
||||
"vendor/",
|
||||
|
||||
@@ -116,15 +116,6 @@
|
||||
- If `git branch -d/-D <branch>` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/<branch>`.
|
||||
- Bulk PR close/reopen safety: if a close action would affect more than 5 PRs, first ask for explicit user confirmation with the exact PR count and target scope/query.
|
||||
|
||||
## GitHub Search (`gh`)
|
||||
|
||||
- Prefer targeted keyword search before proposing new work or duplicating fixes.
|
||||
- Use `--repo openclaw/openclaw` + `--match title,body` first; add `--match comments` when triaging follow-up threads.
|
||||
- PRs: `gh search prs --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"`
|
||||
- Issues: `gh search issues --repo openclaw/openclaw --match title,body --limit 50 -- "auto-update"`
|
||||
- Structured output example:
|
||||
`gh search issues --repo openclaw/openclaw --match title,body --limit 50 --json number,title,state,url,updatedAt -- "auto update" --jq '.[] | "\(.number) | \(.state) | \(.title) | \(.url)"'`
|
||||
|
||||
## Security & Configuration Tips
|
||||
|
||||
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
|
||||
|
||||
191
CHANGELOG.md
191
CHANGELOG.md
@@ -2,188 +2,42 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2026.2.23 (Unreleased)
|
||||
## 2026.2.22 (Unreleased)
|
||||
|
||||
### Changes
|
||||
|
||||
- Control UI/Cron: add full web cron edit parity (including clone and richer validation/help text), plus all-jobs run history with pagination/search/sort/multi-filter controls and improved cron page layout for cleaner scheduling and failure triage workflows.
|
||||
- Provider/Mistral: add support for the Mistral provider, including memory embeddings and voice support. (#23845) Thanks @vincentkoc.
|
||||
- Update/Core: add an optional built-in auto-updater for package installs (`update.auto.*`), default-off, with stable rollout delay+jitter and beta hourly cadence.
|
||||
- CLI/Update: add `openclaw update --dry-run` to preview channel/tag/target/restart actions without mutating config, installing, syncing plugins, or restarting.
|
||||
- Config/UI: add tag-aware settings filtering and broaden config labels/help copy so fields are easier to discover and understand in the dashboard config screen.
|
||||
- Channels/Synology Chat: add a native Synology Chat channel plugin with webhook ingress, direct-message routing, outbound send/media support, per-account config, and DM policy controls. (#23012)
|
||||
- iOS/Talk: prefetch TTS segments and suppress expected speech-cancellation errors for smoother talk playback. (#22833) Thanks @ngutman.
|
||||
- Memory/FTS: add Spanish and Portuguese stop-word filtering for query expansion in FTS-only search mode, improving conversational recall for both languages. Thanks @vincentkoc.
|
||||
- Memory/FTS: add Japanese-aware query expansion tokenization and stop-word filtering (including mixed-script terms like ASCII + katakana) for FTS-only search mode. Thanks @vincentkoc.
|
||||
- Memory/FTS: add Korean stop-word filtering and particle-aware keyword extraction (including mixed Korean/English stems) for query expansion in FTS-only search mode. (#18899) Thanks @ruypang.
|
||||
- Memory/FTS: add Arabic stop-word filtering for query expansion in FTS-only search mode to reduce conversational filler in Arabic memory searches. Thanks @vincentkoc.
|
||||
- Discord/Allowlist: canonicalize resolved Discord allowlist names to IDs and split resolution flow for clearer fail-closed behavior.
|
||||
- Channels/Config: unify channel preview streaming config handling with a shared resolver and canonical migration path.
|
||||
- Gateway/Auth: unify call/probe/status/auth credential-source precedence on shared resolver helpers, with table-driven parity coverage across gateway entrypoints.
|
||||
- Gateway/Auth: refactor gateway credential resolution and websocket auth handshake paths to use shared typed auth contexts, including explicit `auth.deviceToken` support in connect frames and tests.
|
||||
- Skills: remove bundled `food-order` skill from this repo; manage/install it from ClawHub instead.
|
||||
- Docs/Subagents: make thread-bound session guidance channel-first instead of Discord-specific, and list thread-supporting channels explicitly. (#23589) Thanks @osolmaz.
|
||||
- Discord/Allowlist: canonicalize resolved Discord allowlist names to IDs and split resolution flow for clearer fail-closed behavior.
|
||||
- Memory/FTS: add Korean stop-word filtering and particle-aware keyword extraction (including mixed Korean/English stems) for query expansion in FTS-only search mode. (#18899) Thanks @ruypang.
|
||||
- iOS/Talk: prefetch TTS segments and suppress expected speech-cancellation errors for smoother talk playback. (#22833) Thanks @ngutman.
|
||||
- Skills/Security: defense-in-depth security hardening for community skills (ClawHub installs). Adds capability declarations (`shell`, `filesystem`, `network`, `browser`, `sessions`), trust tier classification (builtin/verified/community/local), SKILL.md content scanning (blocks prompt injection, capability inflation, boundary spoofing), skill-aware tool policy enforcement (denies undeclared dangerous tools for community skills), command-dispatch gating, and before-tool-call audit monitoring with session context. Community skills that fail critical scanning are blocked from loading. `openclaw skills list/info/check` now show capabilities, trust tiers, scan results, and runtime policy.
|
||||
- Skills/Logging: all security-related log entries tagged with `category: "security"` for filtering. Skills CLI commands output structured JSON to the file logger (no more ASCII tables in logs). Web UI Logs tab adds a "Security" filter chip for security-only event views.
|
||||
|
||||
### Breaking
|
||||
|
||||
- **BREAKING:** removed Google Antigravity provider support and the bundled `google-antigravity-auth` plugin. Existing `google-antigravity/*` model/profile configs no longer work; migrate to `google-gemini-cli` or other supported providers.
|
||||
- **BREAKING:** tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require `/verbose on` or `/verbose full`.
|
||||
- **BREAKING:** CLI local onboarding now sets `session.dmScope` to `per-channel-peer` by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set `session.dmScope` to `main`. (#23468) Thanks @bmendonca3.
|
||||
- **BREAKING:** unify channel preview-streaming config to `channels.<channel>.streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names.
|
||||
- **BREAKING:** remove legacy Gateway device-auth signature `v1`. Device-auth clients must now sign `v2` payloads with the per-connection `connect.challenge` nonce and send `device.nonce`; nonce-less connects are rejected.
|
||||
- **BREAKING:** unify channel preview-streaming config to `channels.<channel>.streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/Compaction: count auto-compactions only after a non-retry `auto_compaction_end`, keeping session `compactionCount` aligned to completed compactions.
|
||||
- Security/CLI: redact sensitive values in `openclaw config get` output before printing config paths, preventing credential leakage to terminal output/history. (#13683) Thanks @SleuthCo.
|
||||
- Security/CLI: bind node camera payload URL downloads to the resolved node host/IP (with guarded redirect handling) so `nodes-camera` and node camera tool fetches cannot pivot to unrelated targets. (#21145) Thanks @Marvae and @vincentkoc.
|
||||
- Install/Discord Voice: make `@discordjs/opus` an optional dependency so `openclaw` install/update no longer hard-fails when native Opus builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.
|
||||
- Docker/Setup: precreate `$OPENCLAW_CONFIG_DIR/identity` during `docker-setup.sh` so CLI commands that need device identity (for example `devices list`) avoid `EACCES ... /home/node/.openclaw/identity` failures on restrictive bind mounts. (#23948) Thanks @ackson-beep.
|
||||
- Exec/Background: stop applying the default exec timeout to background sessions (`background: true` or explicit `yieldMs`) when no explicit timeout is set, so long-running background jobs are no longer terminated at the default timeout boundary. (#23303)
|
||||
- Slack/Threading: sessions: keep parent-session forking and thread-history context active beyond first turn by removing first-turn-only gates in session init, thread-history fetch, and reply prompt context injection. (#23843, #23090) Thanks @vincentkoc and @Taskle.
|
||||
- Slack/Threading: respect `replyToMode` when Slack auto-populates top-level `thread_ts`, and ignore inline `replyToId` directive tags when `replyToMode` is `off` so thread forcing stays disabled unless explicitly configured. (#23839, #23320, #23513) Thanks @vincentkoc and @dorukardahan.
|
||||
- Slack/Extension: forward `message read` `threadId` to `readMessages` and use delivery-context `threadId` as outbound `thread_ts` fallback so extension replies/reads stay in the correct Slack thread. (#22216, #22485, #23836) Thanks @vincentkoc, @lan17 and @dorukardahan.
|
||||
- Slack/Upload: resolve bare user IDs (U-prefix) to DM channel IDs via `conversations.open` before calling `files.uploadV2`, which rejects non-channel IDs. `chat.postMessage` tolerates user IDs directly, but `files.uploadV2` → `completeUploadExternal` validates `channel_id` against `^[CGDZ][A-Z0-9]{8,}$`, causing `invalid_arguments` when agents reply with media to DM conversations.
|
||||
- Webchat/Chat: apply assistant `final` payload messages directly to chat state so sent turns render without waiting for a full history refresh cycle. (#14928) Thanks @BradGroux.
|
||||
- Webchat/Chat: for out-of-band final events (for example tool-call side runs), append provided final assistant payloads directly instead of forcing a transient history reset. (#11139) Thanks @AkshayNavle.
|
||||
- Webchat/Performance: reload `chat.history` after final events only when the final payload lacks a renderable assistant message, avoiding expensive full-history refreshes on normal turns. (#20588) Thanks @amzzzzzzz.
|
||||
- Webchat/Sessions: preserve external session routing metadata when internal `chat.send` turns run under `webchat`, so explicit channel-keyed sessions (for example Telegram) no longer get rewritten to `webchat` and misroute follow-up delivery. (#23258) Thanks @binary64.
|
||||
- Webchat/Sessions: preserve existing session `label` across `/new` and `/reset` rollovers so reset sessions remain discoverable in session history lists. (#23755) Thanks @ThunderStormer.
|
||||
- Gateway/Chat UI: strip inline reply/audio directive tags from non-streaming final webchat broadcasts (including `chat.inject`) while preserving empty-string message content when tags are the entire reply. (#23298) Thanks @SidQin-cyber.
|
||||
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
|
||||
- Gateway/Chat UI: sanitize non-streaming final `chat.send`/`chat.inject` payload text with the same envelope/untrusted-context stripping used by `chat.history`, preventing `<<<EXTERNAL_UNTRUSTED_CONTENT...>>>` wrapper markup from rendering in Control UI chat. (#24012) Thanks @mittelaltergouda.
|
||||
- Telegram/Media: send a user-facing Telegram reply when media download fails (non-size errors) instead of silently dropping the message.
|
||||
- Telegram/Webhook: keep webhook monitors alive until gateway abort signals fire, preventing false channel exits and immediate webhook auto-restart loops.
|
||||
- Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions.
|
||||
- Telegram/Polling: clear Telegram webhooks (`deleteWebhook`) before starting long-poll `getUpdates`, including retry handling for transient cleanup failures.
|
||||
- Telegram/Webhook: add `channels.telegram.webhookPort` config support and pass it through plugin startup wiring to the monitor listener.
|
||||
- Browser/Extension Relay: refactor the MV3 worker to preserve debugger attachments across relay drops, auto-reconnect with bounded backoff+jitter, persist and rehydrate attached tab state via `chrome.storage.session`, recover from `target_closed` navigation detaches, guard stale socket handlers, enforce per-tab operation locks and per-request timeouts, and add lifecycle keepalive/badge refresh hooks (`alarms`, `webNavigation`). (#15099, #6175, #8468, #9807)
|
||||
- Browser/Relay: treat extension websocket as connected only when `OPEN`, allow reconnect when a stale `CLOSING/CLOSED` extension socket lingers, and guard stale socket message/close handlers so late events cannot clear active relay state; includes regression coverage for live-duplicate `409` rejection and immediate reconnect-after-close races. (#15099, #18698, #20688)
|
||||
- Browser/Remote CDP: extend stale-target recovery so `ensureTabAvailable()` now reuses the sole available tab for remote CDP profiles (same behavior as extension profiles) while preserving strict `tab not found` errors when multiple tabs exist; includes remote-profile regression tests. (#15989)
|
||||
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
|
||||
- Gateway/Pairing: auto-approve loopback `scope-upgrade` pairing requests (including device-token reconnects) so local clients do not disconnect on pairing-required scope elevation. (#23708) Thanks @widingmarcus-cyber.
|
||||
- Gateway/Scopes: include `operator.read` and `operator.write` in default operator connect scope bundles across CLI, Control UI, and macOS clients so write-scoped announce/sub-agent follow-up calls no longer hit `pairing required` disconnects on loopback gateways. (#22582) thanks @YuzuruS.
|
||||
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
||||
- Gateway/Restart: fix restart-loop edge cases by keeping `openclaw.mjs -> dist/entry.js` bootstrap detection explicit, reacquiring the gateway lock for in-process restart fallback paths, and tightening restart-loop regression coverage. (#23416) Thanks @jeffwnli.
|
||||
- Gateway/Lock: use optional gateway-port reachability as a primary stale-lock liveness signal (and wire gateway run-loop lock acquisition to the resolved port), reducing false "already running" lockouts after unclean exits. (#23760) Thanks @Operative-001.
|
||||
- Delivery/Queue: quarantine queue entries immediately on known permanent delivery errors (for example invalid recipients or missing conversation references) by moving them to `failed/` instead of retrying on every restart. (#23794) Thanks @aldoeliacim.
|
||||
- Cron/Status: split execution outcome (`lastRunStatus`) from delivery outcome (`lastDeliveryStatus`) in persisted cron state, finished events, and run history so failed/unknown announcement delivery is visible without conflating it with run errors.
|
||||
- Cron/Delivery: route text-only announce jobs with explicit thread/topic targets through direct outbound delivery so forum/thread destinations do not get dropped by intermediary announce turns. (#23841) Thanks @AndrewArto.
|
||||
- Cron: honor `cron.maxConcurrentRuns` in the timer loop so due jobs can execute up to the configured parallelism instead of always running serially. (#11595) Thanks @Takhoffman.
|
||||
- Cron/Run: enforce the same per-job timeout guard for manual `cron.run` executions as timer-driven runs, including abort propagation for isolated agent jobs, so forced runs cannot wedge indefinitely. (#23704) Thanks @tkuehnl.
|
||||
- Cron/Run: persist the manual-run `runningAtMs` marker before releasing the cron lock so overlapping timer ticks cannot start the same job concurrently.
|
||||
- Cron/Startup: enforce per-job timeout guards for startup catch-up replay runs so missed isolated jobs cannot hang indefinitely during gateway boot recovery.
|
||||
- Cron/Main session: honor abort/timeout signals while retrying `wakeMode=now` heartbeat contention loops so main-target cron runs stop promptly instead of waiting through the full busy-retry window.
|
||||
- Cron/Schedule: for `every` jobs, prefer `lastRunAtMs + everyMs` when still in the future after restarts, then fall back to anchor scheduling for catch-up windows, so NEXT timing matches the last successful cadence. (#22895) Thanks @SidQin-cyber.
|
||||
- Cron/Service: execute manual `cron.run` jobs outside the cron lock (while still persisting started/finished state atomically) so `cron.list` and `cron.status` remain responsive during long forced runs. (#23628) Thanks @dsgraves.
|
||||
- Cron/Timer: keep a watchdog recheck timer armed while `onTimer` is actively executing so the scheduler continues polling even if a due-run tick stalls for an extended period. (#23628) Thanks @dsgraves.
|
||||
- Cron/Run log: clean up settled per-path run-log write queue entries so long-running cron uptime does not retain stale promise bookkeeping in memory.
|
||||
- Cron/Run log: harden `cron.runs` run-log path resolution by rejecting path-separator `id`/`jobId` inputs and enforcing reads within the per-cron `runs/` directory.
|
||||
- Cron/Announce: when announce delivery target resolution fails (for example multiple configured channels with no explicit target), skip injecting fallback `Cron (error): ...` into the main session so runs fail cleanly without accidental last-route sends. (#24074)
|
||||
- Cron/Telegram: validate cron `delivery.to` with shared Telegram target parsing and resolve legacy `@username`/`t.me` targets to numeric IDs at send-time for deterministic delivery target writeback. (#21930) Thanks @kesor.
|
||||
- Cron/Isolation: force fresh session IDs for isolated cron runs so `sessionTarget="isolated"` executions never reuse prior run context. (#23470) Thanks @echoVic.
|
||||
- Plugins/Install: strip `workspace:*` devDependency entries from copied plugin manifests before `npm install --omit=dev`, preventing `EUNSUPPORTEDPROTOCOL` install failures for npm-published channel plugins (including Feishu and MS Teams).
|
||||
- Feishu/Plugins: restore bundled Feishu SDK availability for global installs and strip `openclaw: workspace:*` from plugin `devDependencies` during plugin-version sync so npm-installed Feishu plugins do not fail dependency install. (#23611, #23645, #23603)
|
||||
- Config/Channels: auto-enable built-in channels by writing `channels.<id>.enabled=true` (not `plugins.entries.<id>`), and stop adding built-ins to `plugins.allow`, preventing `plugins.entries.telegram: plugin not found` validation failures.
|
||||
- Config/Channels: when `plugins.allow` is active, auto-enable/enable flows now also allowlist configured built-in channels so `channels.<id>.enabled=true` cannot remain blocked by restrictive plugin allowlists.
|
||||
- Plugins/Discovery: ignore scanned extension backup/disabled directory patterns (for example `.backup-*`, `.bak`, `.disabled*`) and move updater backup directories under `.openclaw-install-backups`, preventing duplicate plugin-id collisions from archived copies.
|
||||
- Plugins/CLI: make `openclaw plugins enable` and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl.
|
||||
- Security/Voice Call: harden media stream WebSocket handling against pre-auth idle-connection DoS by adding strict pre-start timeouts, pending/per-IP connection limits, and total connection caps for streaming endpoints. This ships in the next npm release. Thanks @jiseoung for reporting.
|
||||
- Security/Sessions: redact sensitive token patterns from `sessions_history` tool output and surface `contentRedacted` metadata when masking occurs. (#16928) Thanks @aether-ai-agent.
|
||||
- Security/Exec: stop trusting `PATH`-derived directories for safe-bin allowlist checks, add explicit `tools.exec.safeBinTrustedDirs`, and pin safe-bin shell execution to resolved absolute executable paths to prevent binary-shadowing approval bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Elevated: match `tools.elevated.allowFrom` against sender identities only (not recipient `ctx.To`), closing a recipient-token bypass for `/elevated` authorization. This ships in the next npm release. Thanks @jiseoung for reporting.
|
||||
- Security/Feishu: enforce ID-only allowlist matching for DM/group sender authorization, normalize Feishu ID prefixes during checks, and ignore mutable display names so display-name collisions cannot satisfy allowlist entries. This ships in the next npm release. Thanks @jiseoung for reporting.
|
||||
- Security/Group policy: harden `channels.*.groups.*.toolsBySender` matching by requiring explicit sender-key types (`id:`, `e164:`, `username:`, `name:`), preventing cross-identifier collisions across mutable/display-name fields while keeping legacy untyped keys on a deprecated ID-only path. This ships in the next npm release. Thanks @jiseoung for reporting.
|
||||
- Channels/Group policy: fail closed when `groupPolicy: "allowlist"` is set without explicit `groups`, honor account-level `groupPolicy` overrides, and enforce `groupPolicy: "disabled"` as a hard group block. (#22215) Thanks @etereo.
|
||||
- Telegram/Discord extensions: propagate trusted `mediaLocalRoots` through extension outbound `sendMedia` options so extension direct-send media paths honor agent-scoped local-media allowlists. (#20029, #21903, #23227)
|
||||
- Agents/Exec: honor explicit agent context when resolving `tools.exec` defaults for runs with opaque/non-agent session keys, so per-agent `host/security/ask` policies are applied consistently. (#11832)
|
||||
- Doctor/Security: add an explicit warning that `approvals.exec.enabled=false` disables forwarding only, while enforcement remains driven by host-local `exec-approvals.json` policy. (#15047)
|
||||
- Sandbox/Docker: default sandbox container user to the workspace owner `uid:gid` when `agents.*.sandbox.docker.user` is unset, fixing non-root gateway file-tool permissions under capability-dropped containers. (#20979)
|
||||
- Plugins/Media sandbox: propagate trusted `mediaLocalRoots` through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718)
|
||||
- Agents/Workspace guard: map sandbox container-workdir file-tool paths (for example `/workspace/...` and `file:///workspace/...`) to host workspace roots before workspace-only validation, preventing false `Path escapes sandbox root` rejections for sandbox file tools. (#9560)
|
||||
- Gateway/Exec approvals: expire approval requests immediately when no approval-capable gateway clients are connected and no forwarding targets are available, avoiding delayed approvals after restarts/offline approver windows. (#22144)
|
||||
- Security/Exec approvals: when approving wrapper commands with allow-always in allowlist mode, persist inner executable paths for known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) and fail closed (no persisted entry) when wrapper unwrapping is not safe, preventing wrapper-path approval bypasses. Thanks @tdjackey for reporting.
|
||||
- Node/macOS exec host: default headless macOS node `system.run` to local execution and only route through the companion app when `OPENCLAW_NODE_EXEC_HOST=app` is explicitly set, avoiding companion-app filesystem namespace mismatches during exec. (#23547)
|
||||
- Sandbox/Media: map container workspace paths (`/workspace/...` and `file:///workspace/...`) back to the host sandbox root for outbound media validation, preventing false deny errors for sandbox-generated local media. (#23083) Thanks @echo931.
|
||||
- Sandbox/Docker: apply custom bind mounts after workspace mounts and prioritize bind-source resolution on overlapping paths, so explicit workspace binds are no longer ignored. (#22669) Thanks @tasaankaeris.
|
||||
- Exec approvals/Forwarding: restore Discord text forwarding when component approvals are not configured, and carry request snapshots through resolve events so resolved notices still forward after cache misses/restarts. (#22988) Thanks @bubmiller.
|
||||
- Control UI/WebSocket: stop and clear the browser gateway client on UI teardown so remounts cannot leave orphan websocket clients that create duplicate active connections. (#23422) Thanks @floatinggball-design.
|
||||
- Control UI/WebSocket: send a stable per-tab `instanceId` in websocket connect frames so reconnect cycles keep a consistent client identity for diagnostics and presence tracking. (#23616) Thanks @zq58855371-ui.
|
||||
- Config/Memory: allow `"mistral"` in `agents.defaults.memorySearch.provider` and `agents.defaults.memorySearch.fallback` schema validation. (#14934) Thanks @ThomsenDrake.
|
||||
- Feishu/Commands: in group chats, command authorization now falls back to top-level `channels.feishu.allowFrom` when per-group `allowFrom` is not set, so `/command` no longer gets blocked by an unintended empty allowlist. (#23756)
|
||||
- Dev tooling: prevent `CLAUDE.md` symlink target regressions by excluding CLAUDE symlink sentinels from `oxfmt` and marking them `-text` in `.gitattributes`, so formatter/EOL normalization cannot reintroduce trailing-newline targets. Thanks @vincentkoc.
|
||||
- Agents/Compaction: restore embedded compaction safeguard/context-pruning extension loading in production by wiring bundled extension factories into the resource loader instead of runtime file-path resolution. (#22349) Thanks @Glucksberg.
|
||||
- Feishu/Media: for inbound video messages that include both `file_key` (video) and `image_key` (thumbnail), prefer `file_key` when downloading media so video attachments are saved instead of silently failing on thumbnail keys. (#23633)
|
||||
- Hooks/Loader: avoid redundant hook-module recompilation on gateway restart by skipping cache-busting for bundled hooks and using stable file metadata keys (`mtime+size`) for mutable workspace/managed/plugin hook imports. (#16953) Thanks @mudrii.
|
||||
- Hooks/Cron: suppress duplicate main-session events for delivered hook turns and mark `SILENT_REPLY_TOKEN` (`NO_REPLY`) early exits as delivered to prevent hook context pollution. (#20678) Thanks @JonathanWorks.
|
||||
- Providers/OpenRouter: inject `cache_control` on system prompts for OpenRouter Anthropic models to improve prompt-cache reuse. (#17473) Thanks @rrenamed.
|
||||
- Installer/Smoke tests: remove legacy `OPENCLAW_USE_GUM` overrides from docker install-smoke runs so tests exercise installer auto TTY detection behavior directly.
|
||||
- Providers/OpenRouter: allow pass-through OpenRouter and Opencode model IDs in live model filtering so custom routed model IDs are treated as modern refs. (#14312) Thanks @Joly0.
|
||||
- Providers/OpenRouter: default reasoning to enabled when the selected model advertises `reasoning: true` and no session/directive override is set. (#22513) Thanks @zwffff.
|
||||
- Providers/OpenRouter: map `/think` levels to `reasoning.effort` in embedded runs while preserving explicit `reasoning.max_tokens` payloads. (#17236) Thanks @robbyczgw-cla.
|
||||
- Providers/OpenRouter: preserve stored session provider when model IDs are vendor-prefixed (for example, `anthropic/...`) so follow-up turns do not incorrectly route to direct provider APIs. (#22753) Thanks @dndodson.
|
||||
- Providers/OpenRouter: preserve the required `openrouter/` prefix for OpenRouter-native model IDs during model-ref normalization. (#12942) Thanks @omair445.
|
||||
- Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko.
|
||||
- Providers/OpenRouter: preserve model allowlist entries containing OpenRouter preset paths (for example `openrouter/@preset/...`) by treating `/model ...@profile` auth-profile parsing as a suffix-only override. (#14120) Thanks @NotMainstream.
|
||||
- Cron/Auth: propagate auth-profile resolution to isolated cron sessions so provider API keys are resolved the same way as main sessions, fixing 401 errors when using providers configured via auth-profiles. (#20689) Thanks @lailoo.
|
||||
- Cron/Follow-up: pass resolved `agentDir` through isolated cron and queued follow-up embedded runs so auth/profile lookups stay scoped to the correct agent directory. (#22845) Thanks @seilk.
|
||||
- Agents/Media: route tool-result `MEDIA:` extraction through shared parser validation so malformed prose like `MEDIA:-prefixed ...` is no longer treated as a local file path (prevents Telegram ENOENT tool-error overrides). (#18780) Thanks @HOYALIM.
|
||||
- Logging: cap single log-file size with `logging.maxFileBytes` (default 500 MB) and suppress additional writes after cap hit to prevent disk exhaustion from repeated error storms.
|
||||
- Memory/Remote HTTP: centralize remote memory HTTP calls behind a shared guarded helper (`withRemoteHttpResponse`) so embeddings and batch flows use one request/release path.
|
||||
- Memory/Embeddings: apply configured remote-base host pinning (`allowedHostnames`) across OpenAI/Voyage/Gemini embedding requests to keep private/self-hosted endpoints working without cross-host drift. (#18198) Thanks @ianpcook.
|
||||
- Memory/Batch: route OpenAI/Voyage/Gemini batch upload/create/status/download requests through the same guarded HTTP path for consistent SSRF policy enforcement.
|
||||
- Memory/Index: detect memory source-set changes (for example enabling `sessions` after an existing memory-only index) and trigger a full reindex so existing session transcripts are indexed without requiring `--force`. (#17576) Thanks @TarsAI-Agent.
|
||||
- Memory/Embeddings: enforce a per-input 8k safety cap before embedding batching and apply a conservative 2k fallback limit for local providers without declared input limits, preventing oversized session/memory chunks from triggering provider context-size failures during sync/indexing. (#6016) Thanks @batumilove.
|
||||
- Memory/QMD: on Windows, resolve bare `qmd`/`mcporter` command names to npm shim executables (`.cmd`) before spawning, so qmd boot updates and mcporter-backed searches no longer fail with `spawn ... ENOENT` on default npm installs. (#23899) Thanks @arcbuilder-ai.
|
||||
- Memory/QMD: parse plain-text `qmd collection list --json` output when older qmd builds ignore JSON mode, and retry memory searches once after re-ensuring managed collections when qmd returns `Collection not found ...`. (#23613) Thanks @leozhucn.
|
||||
- Signal/RPC: guard malformed Signal RPC JSON responses with a clear status-scoped error and add regression coverage for invalid JSON responses. (#22995) Thanks @adhitShet.
|
||||
- Gateway/Subagents: guard gateway and subagent session-key/message trim paths against undefined inputs to prevent early `Cannot read properties of undefined (reading 'trim')` crashes during subagent spawn and wait flows.
|
||||
- Agents/Workspace: guard `resolveUserPath` against undefined/null input to prevent `Cannot read properties of undefined (reading 'trim')` crashes when workspace paths are missing in embedded runner flows.
|
||||
- Auth/Profiles: keep active `cooldownUntil`/`disabledUntil` windows immutable across retries so mid-window failures cannot extend recovery indefinitely; only recompute a backoff window after the previous deadline has expired. This resolves cron/inbound retry loops that could trap gateways until manual `usageStats` cleanup. (#23516, #23536) Thanks @arosstale.
|
||||
- Channels/Security: fail closed on missing provider group policy config by defaulting runtime group policy to `allowlist` (instead of inheriting `channels.defaults.groupPolicy`) when `channels.<provider>` is absent across message channels, and align runtime + security warnings/docs to the same fallback behavior (Slack, Discord, iMessage, Telegram, WhatsApp, Signal, LINE, Matrix, Mattermost, Google Chat, IRC, Nextcloud Talk, Feishu, and Zalo user flows; plus Discord message/native-command paths). (#23367) Thanks @bmendonca3.
|
||||
- Gateway/Onboarding: harden remote gateway onboarding defaults and guidance by defaulting discovered direct URLs to `wss://`, rejecting insecure non-loopback `ws://` targets in onboarding validation, and expanding remote-security remediation messaging across gateway client/call/doctor flows. (#23476) Thanks @bmendonca3.
|
||||
- CLI/Sessions: pass the configured sessions directory when resolving transcript paths in `agentCommand`, so custom `session.store` locations resume sessions reliably. Thanks @davidrudduck.
|
||||
- Signal/Monitor: treat user-initiated abort shutdowns as clean exits when auto-started `signal-cli` is terminated, while still surfacing unexpected daemon exits as startup/runtime failures. (#23379) Thanks @frankekn.
|
||||
- Channels/Dedupe: centralize plugin dedupe primitives in plugin SDK (memory + persistent), move Feishu inbound dedupe to a namespace-scoped persistent store, and reuse shared dedupe cache logic for Zalo webhook replay + Tlon processed-message tracking to reduce duplicate handling during reconnect/replay paths. (#23377) Thanks @SidQin-cyber.
|
||||
- Channels/Delivery: remove hardcoded WhatsApp delivery fallbacks; require explicit/session channel context or auto-pick the sole configured channel when unambiguous. (#23357) Thanks @lbo728.
|
||||
- Channels/Dedupe: centralize plugin dedupe primitives in plugin SDK (memory + persistent), move Feishu inbound dedupe to a namespace-scoped persistent store, and reuse shared dedupe cache logic for Zalo webhook replay + Tlon processed-message tracking to reduce duplicate handling during reconnect/replay paths.
|
||||
- ACP/Gateway: wait for gateway hello before opening ACP requests, and fail fast on pre-hello connect failures to avoid startup hangs and early `gateway not connected` request races. (#23390) Thanks @janckerchen.
|
||||
- Gateway/Auth: preserve `OPENCLAW_GATEWAY_PASSWORD` env override precedence for remote gateway call credentials after shared resolver refactors, preventing stale configured remote passwords from overriding runtime secret rotation.
|
||||
- Gateway/Auth: preserve shared-token `gateway token mismatch` auth errors when `auth.token` fallback device-token checks fail, and reserve `device token mismatch` guidance for explicit `auth.deviceToken` failures.
|
||||
- Gateway/Tools: when agent tools pass an allowlisted `gatewayUrl` override, resolve local override tokens from env/config fallback but keep remote overrides strict to `gateway.remote.token`, preventing local token leakage to remote targets.
|
||||
- Gateway/Client: keep cached device-auth tokens on `device token mismatch` closes when the client used explicit shared token/password credentials, avoiding accidental pairing-token churn during explicit-auth failures.
|
||||
- Node host/Exec: keep strict Windows allowlist behavior for `cmd.exe /c` shell-wrapper runs, and return explicit approval guidance when blocked (`SYSTEM_RUN_DENIED: allowlist miss`).
|
||||
- Control UI: show pairing-required guidance (commands + mobile tokenized URL reminder) when the dashboard disconnects with `1008 pairing required`.
|
||||
- Security/Audit: add `openclaw security audit` detection for open group policies that expose runtime/filesystem tools without sandbox/workspace guards (`security.exposure.open_groups_with_runtime_or_fs`).
|
||||
- Security/Audit: make `gateway.real_ip_fallback_enabled` severity conditional for loopback trusted-proxy setups (warn for loopback-only `trustedProxies`, critical when non-loopback proxies are trusted). (#23428) Thanks @bmendonca3.
|
||||
- Security/Exec env: block request-scoped `HOME` and `ZDOTDIR` overrides in host exec env sanitizers (Node + macOS), preventing shell startup-file execution before allowlist-evaluated command bodies. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Exec env: block `SHELLOPTS`/`PS4` in host exec env sanitizers and restrict shell-wrapper (`bash|sh|zsh ... -c/-lc`) request env overrides to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`) on both node host and macOS companion paths, preventing xtrace prompt command-substitution allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- WhatsApp/Security: enforce `allowFrom` for direct-message outbound targets in all send modes (including `mode: "explicit"`), preventing sends to non-allowlisted numbers. (#20108) Thanks @zahlmann.
|
||||
- Security/Exec approvals: fail closed on shell line continuations (`\\\n`/`\\\r\n`) and treat shell-wrapper execution as approval-required in allowlist mode, preventing `$\\` newline command-substitution bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Gateway: emit a startup security warning when insecure/dangerous config flags are enabled (including `gateway.controlUi.dangerouslyDisableDeviceAuth=true`) and point operators to `openclaw security audit`.
|
||||
- Security/Hooks auth: normalize hook auth rate-limit client IP keys so IPv4 and IPv4-mapped IPv6 addresses share one throttle bucket, preventing dual-form auth-attempt budget bypasses. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
- Security/Exec approvals: treat `env` and shell-dispatch wrappers as transparent during allowlist analysis on node-host and macOS companion paths so policy checks match the effective executable/inline shell payload instead of the wrapper binary, blocking wrapper-smuggled allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Exec approvals: require explicit safe-bin profiles for `tools.exec.safeBins` entries in allowlist mode (remove generic safe-bin profile fallback), and add `tools.exec.safeBinProfiles` for safe custom binaries so unprofiled interpreter-style entries cannot be treated as stdin-safe. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Channels: harden Slack external menu token handling by switching to CSPRNG tokens, validating token shape, requiring user identity for external option lookups, and avoiding fabricated timestamp `trigger_id` fallbacks; also switch Tlon Urbit channel IDs to CSPRNG UUIDs, centralize secure ID/token generation via shared infra helpers, and add a guardrail test to block new runtime `Date.now()+Math.random()` token/id patterns.
|
||||
- Security/Hooks transforms: enforce symlink-safe containment for webhook transform module paths (including `hooks.transformsDir` and `hooks.mappings[].transform.module`) by resolving existing-path ancestors via realpath before import, while preserving in-root symlink support; add regression coverage for both escape and allow cases. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
- Telegram/WSL2: disable `autoSelectFamily` by default on WSL2 and memoize WSL2 detection in Telegram network decision logic to avoid repeated sync `/proc/version` probes on fetch/send paths. (#21916) Thanks @MizukiMachine.
|
||||
- Telegram/Network: default Node 22+ DNS result ordering to `ipv4first` for Telegram fetch paths and add `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER`/`channels.telegram.network.dnsResultOrder` overrides to reduce IPv6-path fetch failures. (#5405) Thanks @Glucksberg.
|
||||
- Telegram/Forward bursts: coalesce forwarded text+media updates through a dedicated forward lane debounce window that works with default inbound debounce config, while keeping forwarded control commands immediate. (#19476) thanks @napetrov.
|
||||
- Telegram/Streaming: preserve archived draft preview mapping after flush and clean superseded reasoning preview bubbles so multi-message preview finals no longer cross-edit or orphan stale messages under send/rotation races. (#23202) Thanks @obviyus.
|
||||
- Telegram/Replies: scope messaging-tool text/media dedupe to same-target sends only, so cross-target tool sends can no longer silently suppress Telegram final replies.
|
||||
- Telegram/Replies: normalize `file://` and local-path media variants during messaging dedupe so equivalent media paths do not produce duplicate Telegram replies.
|
||||
- Telegram/Replies: extract forwarded-origin context from unified reply targets (`reply_to_message` and `external_reply`) so forward+comment metadata is preserved across partial reply shapes. (#9720) thanks @mcaxtr.
|
||||
- Telegram/Polling: persist a safe update-offset watermark bounded by pending updates so crash/restart cannot skip queued lower `update_id` updates after out-of-order completion. (#23284) thanks @frankekn.
|
||||
- Telegram/Polling: force-restart stuck runner instances when recoverable unhandled network rejections escape the polling task path, so polling resumes instead of silently stalling. (#19721) Thanks @jg-noncelogic.
|
||||
- Slack/Slash commands: preserve the Bolt app receiver when registering external select options handlers so monitor startup does not crash on runtimes that require bound `app.options` calls. (#23209) Thanks @0xgaia.
|
||||
- Slack/Telegram slash sessions: await session metadata persistence before dispatch so first-turn native slash runs do not race session-origin metadata updates. (#23065) thanks @hydro13.
|
||||
- Slack/Queue routing: preserve string `thread_ts` values through collect-mode queue drain and DM `deliveryContext` updates so threaded follow-ups do not leak to the main channel when Slack thread IDs are strings. (#11934) Thanks @sandieman2 and @vincentkoc.
|
||||
- Telegram/Native commands: set `ctx.Provider="telegram"` for native slash-command context so elevated gate checks resolve provider correctly (fixes `provider (ctx.Provider)` failures in `/elevated` flows). (#23748) Thanks @serhii12.
|
||||
- Agents/Ollama: preserve unsafe integer tool-call arguments as exact strings during NDJSON parsing, preventing large numeric IDs from being rounded before tool execution. (#23170) Thanks @BestJoester.
|
||||
- Cron/Gateway: keep `cron.list` and `cron.status` responsive during startup catch-up by avoiding a long-held cron lock while missed jobs execute. (#23106) Thanks @jayleekr.
|
||||
- Gateway/Config reload: compare array-valued config paths structurally during diffing so unchanged `memory.qmd.paths` and `memory.qmd.scope.rules` no longer trigger false restart-required reloads. (#23185) Thanks @rex05ai.
|
||||
- Gateway/Config reload: retry short-lived missing config snapshots during reload before skipping, preventing atomic-write unlink windows from triggering restart loops. (#23343) Thanks @lbo728.
|
||||
- Cron/Scheduling: validate runtime cron expressions before schedule/stagger evaluation so malformed persisted jobs report a clear `invalid cron schedule: expr is required` error instead of crashing with `undefined.trim` failures and auto-disable churn. (#23223) Thanks @asimons81.
|
||||
- Memory/QMD: migrate legacy unscoped collection bindings (for example `memory-root`) to per-agent scoped names (for example `memory-root-main`) during startup when safe, so QMD-backed `memory_search` no longer fails with `Collection not found` after upgrades. (#23228, #20727) Thanks @JLDynamics and @AaronFaby.
|
||||
- Memory/QMD: normalize Han-script BM25 search queries before invoking `qmd search` so mixed CJK+Latin prompts no longer return empty results due to tokenizer mismatch. (#23426) Thanks @LunaLee0130.
|
||||
@@ -192,38 +46,31 @@ Docs: https://docs.openclaw.ai
|
||||
- TUI/Status: request immediate renders after setting `sending`/`waiting` activity states so in-flight runs always show visible progress indicators instead of appearing idle until completion. (#21549) Thanks @13Guinness.
|
||||
- TUI/Input: arm Ctrl+C exit timing when clearing non-empty composer text and add a SIGINT fallback path so double Ctrl+C exits remain responsive during active runs instead of requiring an extra press or appearing stuck. (#23407) Thanks @tinybluedev.
|
||||
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
|
||||
- Agents/Google: sanitize non-base64 `thought_signature`/`thoughtSignature` values from assistant replay transcripts for native Google Gemini requests while preserving valid signatures and tool-call order. (#23457) Thanks @echoVic.
|
||||
- Agents/Transcripts: validate assistant tool-call names (syntax/length + registered tool allowlist) before transcript persistence and during replay sanitization so malformed failover tool names no longer poison sessions with repeated provider HTTP 400 errors. (#23324) Thanks @johnsantry.
|
||||
- Agents/Mistral: sanitize tool-call IDs in the embedded agent loop and generate strict provider-safe pending tool-call IDs, preventing Mistral strict9 `HTTP 400` failures on tool continuations. (#23698) Thanks @echoVic.
|
||||
- Agents/Compaction: strip stale assistant usage snapshots from pre-compaction turns when replaying history after a compaction summary so context-token estimation no longer reuses pre-compaction totals and immediately re-triggers destructive follow-up compactions. (#19127) Thanks @tedwatson.
|
||||
- Agents/Replies: emit a default completion acknowledgement (`✅ Done.`) only for direct/private tool-only completions with no final assistant text, while suppressing synthetic acknowledgements for channel/group sessions and runs that already delivered output via messaging tools. (#22834) Thanks @Oldshue.
|
||||
- Agents/Replies: emit a default completion acknowledgement (`✅ Done.`) when runs execute tools successfully but return no final assistant text, preventing silent no-reply turns after tool-only completions. (#22834) Thanks @Oldshue.
|
||||
- Agents/Subagents: honor `tools.subagents.tools.alsoAllow` and explicit subagent `allow` entries when resolving built-in subagent deny defaults, so explicitly granted tools (for example `sessions_send`) are no longer blocked unless re-denied in `tools.subagents.tools.deny`. (#23359) Thanks @goren-beehero.
|
||||
- Agents/Subagents: make announce call timeouts configurable via `agents.defaults.subagents.announceTimeoutMs` and restore a 60s default to prevent false timeout failures on slower announce paths. (#22719) Thanks @Valadon.
|
||||
- Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.
|
||||
- Agents/Auth profiles: skip auth-profile cooldown writes for timeout failures in embedded runner rotation so model/network timeouts do not poison same-provider fallback model selection while still allowing in-turn account rotation. (#22622) Thanks @vageeshkumar.
|
||||
- Plugins/Hooks: run legacy `before_agent_start` once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710.
|
||||
- Models/Config: default missing Anthropic provider/model `api` fields to `anthropic-messages` during config validation so custom relay model entries are preserved instead of being dropped by runtime model registry validation. (#23332) Thanks @bigbigmonkey123.
|
||||
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
|
||||
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
|
||||
- Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.
|
||||
- Plugins/CLI: make `openclaw plugins enable` and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl.
|
||||
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
|
||||
- Infra/Network: classify undici `TypeError: fetch failed` as transient in unhandled-rejection detection even when nested causes are unclassified, preventing avoidable gateway crash loops on flaky networks. (#14345) Thanks @Unayung.
|
||||
- Telegram/Retry: classify undici `TypeError: fetch failed` as recoverable in both polling and send retry paths so transient fetch failures no longer fail fast. (#16699) thanks @Glucksberg.
|
||||
- Docs/Telegram: correct Node 22+ network defaults (`autoSelectFamily`, `dnsResultOrder`) and clarify Telegram setup does not use positional `openclaw channels login telegram`. (#23609) Thanks @ryanbastic.
|
||||
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
|
||||
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
|
||||
- BlueBubbles/Private API cache: treat unknown (`null`) private-API cache status as disabled for send/attachment/reply flows to avoid stale-cache 500s, and log a warning when reply/effect features are requested while capability is unknown. (#23459) Thanks @echoVic.
|
||||
- BlueBubbles/Webhooks: accept inbound/reaction webhook payloads when BlueBubbles omits `handle` but provides DM `chatGuid`, and harden payload extraction for array/string-wrapped message bodies so valid webhook events no longer get rejected as unparseable. (#23275) Thanks @toph31.
|
||||
- Security/Audit: add `openclaw security audit` finding `gateway.nodes.allow_commands_dangerous` for risky `gateway.nodes.allowCommands` overrides, with severity upgraded to critical on remote gateway exposure.
|
||||
- Gateway/Control plane: reduce cross-client write limiter contention by adding `connId` fallback keying when device ID and client IP are both unavailable.
|
||||
- Security/Config: block prototype-key traversal during config merge patch and legacy migration merge helpers (`__proto__`, `constructor`, `prototype`) to prevent prototype pollution during config mutation flows. (#22968) Thanks @Clawborn.
|
||||
- Security/Shell env: validate login-shell executable paths for shell-env fallback (`/etc/shells` + trusted prefixes), block `SHELL`/`HOME`/`ZDOTDIR` in config env ingestion before fallback execution, and sanitize fallback shell exec env to pin `HOME` to the real user home while dropping `ZDOTDIR` and other dangerous startup vars. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Network/SSRF: enable `autoSelectFamily` on pinned undici dispatchers (with attempt timeout) so IPv6-unreachable environments can quickly fall back to IPv4 for guarded fetch paths. (#19950) Thanks @ENAwareness.
|
||||
- Security/Shell env: validate login-shell executable paths for shell-env fallback (`/etc/shells` + trusted prefixes) and block `SHELL` in dangerous env override policy paths so untrusted shell-path injection falls back safely to `/bin/sh`. Thanks @athuljayaram for reporting.
|
||||
- Security/Config: make parsed chat allowlist checks fail closed when `allowFrom` is empty, restoring expected DM/pairing gating.
|
||||
- Security/Exec: in non-default setups that manually add `sort` to `tools.exec.safeBins`, block `sort --compress-program` so allowlist-mode safe-bin checks cannot bypass approval. Thanks @tdjackey for reporting.
|
||||
- Security/Exec approvals: when users choose `allow-always` for shell-wrapper commands (for example `/bin/zsh -lc ...`), persist allowlist patterns for the inner executable(s) instead of the wrapper shell binary, preventing accidental broad shell allowlisting in moderate mode. (#23276) Thanks @xrom2863.
|
||||
- Security/Exec: fail closed when `tools.exec.host=sandbox` is configured/requested but sandbox runtime is unavailable. (#23398) Thanks @bmendonca3.
|
||||
- Security/macOS app beta: enforce path-only `system.run` allowlist matching (drop basename matches like `echo`), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Agents: auto-generate and persist a dedicated `commands.ownerDisplaySecret` when `commands.ownerDisplay=hash`, remove gateway token fallback from owner-ID prompt hashing across CLI and embedded agent runners, and centralize owner-display secret resolution in one shared helper. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), centralize range checks into a single CIDR policy table, and reuse one shared host/IP classifier across literal + DNS checks to reduce classifier drift. This ships in the next npm release. Thanks @princeeismond-dot for reporting.
|
||||
- Security/SSRF: block RFC2544 benchmarking range (`198.18.0.0/15`) across direct and embedded-IP paths, and normalize IPv6 dotted-quad transition literals (for example `::127.0.0.1`, `64:ff9b::8.8.8.8`) in shared IP parsing/classification.
|
||||
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), and centralize range checks into a single CIDR policy table to reduce classifier drift.
|
||||
- Security/Archive: block zip symlink escapes during archive extraction.
|
||||
- Security/Media sandbox: keep tmp media allowance for absolute tmp paths only and enforce symlink-escape checks before sandbox-validated reads, preventing tmp symlink exfiltration and relative `../` sandbox escapes when sandboxes live under tmp. (#17892) Thanks @dashed.
|
||||
- Browser/Upload: accept canonical in-root upload paths when the configured uploads directory is a symlink alias (for example `/tmp` -> `/private/tmp` on macOS), so browser upload validation no longer rejects valid files during client->server revalidation. (#23300, #23222, #22848) Thanks @bgaither4, @parkerati, and @Nabsku.
|
||||
@@ -232,11 +79,9 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/Media: enforce inbound media byte limits during download/read across Discord, Telegram, Zalo, Microsoft Teams, and BlueBubbles to prevent oversized payload memory spikes before rejection. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Media/Understanding: preserve `application/pdf` MIME classification during text-like file heuristics so PDF uploads use PDF extraction paths instead of being inlined as raw text. (#23191) Thanks @claudeplay2026-byte.
|
||||
- Security/Control UI: block symlink-based out-of-root static file reads by enforcing realpath containment and file-identity checks when serving Control UI assets and SPA fallback `index.html`. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Gateway avatars: block symlink traversal during local avatar `data:` URL resolution by enforcing realpath containment and file-identity checks before reads. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/Gateway avatars: block symlink traversal during local avatar `data:` URL resolution by enforcing realpath containment and file-identity checks before reads. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
|
||||
- Security/Control UI: centralize avatar URL/path validation across gateway/config helpers and enforce a 2 MB max size for local agent avatar files before `/avatar` resolution, reducing oversized-avatar memory risk without changing supported avatar formats.
|
||||
- Security/Control UI avatars: harden `/avatar/:agentId` local avatar serving by rejecting symlink paths and requiring fd-level file identity + size checks before reads. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/MSTeams media: enforce allowlist checks for SharePoint reference attachment URLs and redirect targets during Graph-backed media fetches so redirect chains cannot escape configured media host boundaries. This ships in the next npm release. Thanks @tdjackey for reporting.
|
||||
- Security/MSTeams media: route attachment auth-retry and Graph SharePoint download redirects through shared `safeFetch` so each hop is validated with allowlist + DNS/IP checks across the full redirect chain. (#23598) Thanks @Asm3r96 and @lewiswigmore.
|
||||
- Security/macOS discovery: fail closed for unresolved discovery endpoints by clearing stale remote selection values, use resolved service host only for SSH target derivation, and keep remote URL config aligned with resolved endpoint availability. (#21618) Thanks @bmendonca3.
|
||||
- Chat/Usage/TUI: strip synthetic inbound metadata blocks (including `Conversation info` and trailing `Untrusted context` channel metadata wrappers) from displayed conversation history so internal prompt context no longer leaks into user-visible logs.
|
||||
- CI/Tests: fix TypeScript case-table typing and lint assertion regressions so `pnpm check` passes again after Synology Chat landing. (#23012) Thanks @druide67.
|
||||
@@ -244,7 +89,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Cron: persist `delivered` state in cron job records so delivery failures remain visible in status and logs. (#19174) Thanks @simonemacario.
|
||||
- Config/Doctor: only repair the OAuth credentials directory when affected channels are configured, avoiding fresh-install noise.
|
||||
- Config/Channels: whitelist `channels.modelByChannel` in config validation and exclude it from plugin auto-enable channel detection so model overrides no longer trigger `unknown channel id` validation errors or bogus `modelByChannel` plugin enables. (#23412) Thanks @ProspectOre.
|
||||
- Config/Bindings: allow optional `bindings[].comment` in strict config validation so annotated binding entries no longer fail load. (#23458) Thanks @echoVic.
|
||||
- Usage/Pricing: correct MiniMax M2.5 pricing defaults to fix inflated cost reporting. (#22755) Thanks @miloudbelarebia.
|
||||
- Gateway/Daemon: verify gateway health after daemon restart.
|
||||
- Agents/UI text: stop rewriting normal assistant billing/payment language outside explicit error contexts. (#17834) Thanks @niceysam.
|
||||
@@ -279,7 +123,6 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Agents/Bootstrap: skip malformed bootstrap files with missing/invalid paths instead of crashing agent sessions; hooks using `filePath` (or non-string `path`) are skipped with a warning. (#22693, #22698) Thanks @arosstale.
|
||||
- Security/Agents: cap embedded Pi runner outer retry loop with a higher profile-aware dynamic limit (32-160 attempts) and return an explicit `retry_limit` error payload when retries never converge, preventing unbounded internal retry cycles (`GHSA-76m6-pj3w-v7mf`).
|
||||
- Telegram: detect duplicate bot-token ownership across Telegram accounts at startup/status time, mark secondary accounts as not configured with an explicit fix message, and block duplicate account startup before polling to avoid endless `getUpdates` conflict loops.
|
||||
- Agents/Tool images: include source filenames in `agents/tool-images` resize logs so compression events can be traced back to specific files.
|
||||
@@ -411,7 +254,6 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Security: strip hidden text from `web_fetch` extracted content to prevent indirect prompt injection, covering CSS-hidden elements, class-based hiding (sr-only, d-none, etc.), invisible Unicode, color:transparent, offscreen transforms, and non-content tags. (#8027, #21074) Thanks @hydro13 for the fix and @LucasAIBuilder for reporting.
|
||||
- Agents/Streaming: keep assistant partial streaming active during reasoning streams, handle native `thinking_*` stream events consistently, dedupe mixed reasoning-end signals, and clear stale mutating tool errors after same-target retry success. (#20635) Thanks @obviyus.
|
||||
- iOS/Chat: use a dedicated iOS chat session key for ChatSheet routing to avoid cross-client session collisions with main-session traffic. (#21139) thanks @mbelinky.
|
||||
- iOS/Chat: auto-resync chat history after reconnect sequence gaps, clear stale pending runs, and avoid dead-end manual refresh errors after transient disconnects. (#21135) thanks @mbelinky.
|
||||
@@ -421,7 +263,6 @@ Docs: https://docs.openclaw.ai
|
||||
- iOS/Onboarding: stabilize pairing and reconnect behavior by resetting stale pairing request state on manual retry, disconnecting both operator and node gateways on operator failure, and avoiding duplicate pairing loops from operator transport identity attachment. (#20056) Thanks @mbelinky.
|
||||
- iOS/Signing: restore local auto-selected signing-team overrides during iOS project generation by wiring `.local-signing.xcconfig` into the active signing config and emitting `OPENCLAW_DEVELOPMENT_TEAM` in local signing setup. (#19993) Thanks @ngutman.
|
||||
- Telegram: unify message-like inbound handling so `message` and `channel_post` share the same dedupe/access/media pipeline and remain behaviorally consistent. (#20591) Thanks @obviyus.
|
||||
- Telegram: keep media-group processing resilient by skipping recoverable per-item download failures while still failing loud on non-recoverable media errors. (#20598) thanks @mcaxtr.
|
||||
- Telegram/Agents: gate exec/bash tool-failure warnings behind verbose mode so default Telegram replies stay clean while verbose sessions still surface diagnostics. (#20560) Thanks @obviyus.
|
||||
- Telegram/Cron/Heartbeat: honor explicit Telegram topic targets in cron and heartbeat delivery (`<chatId>:topic:<threadId>`) so scheduled sends land in the configured topic instead of the last active thread. (#19367) Thanks @Lukavyi.
|
||||
- Telegram/DM routing: prevent DM inbound origin metadata from leaking into main-session `lastRoute` updates and normalize DM `lastRoute.to` to provider-prefixed `telegram:<chatId>`. (#19491) thanks @guirguispierre.
|
||||
|
||||
10
SECURITY.md
10
SECURITY.md
@@ -49,7 +49,6 @@ When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (o
|
||||
- Using OpenClaw in ways that the docs recommend not to
|
||||
- Deployments where mutually untrusted/adversarial operators share one gateway host and config
|
||||
- Prompt injection attacks
|
||||
- Reports that require write access to trusted local state (`~/.openclaw`, workspace files like `MEMORY.md` / `memory/*.md`)
|
||||
|
||||
## Deployment Assumptions
|
||||
|
||||
@@ -60,15 +59,6 @@ OpenClaw security guidance assumes:
|
||||
- A single Gateway shared by mutually untrusted people is **not a recommended setup**. Use separate gateways (or at minimum separate OS users/hosts) per trust boundary.
|
||||
- Authenticated Gateway callers are treated as trusted operators. Session identifiers (for example `sessionKey`) are routing controls, not per-user authorization boundaries.
|
||||
|
||||
## Workspace Memory Trust Boundary
|
||||
|
||||
`MEMORY.md` and `memory/*.md` are plain workspace files and are treated as trusted local operator state.
|
||||
|
||||
- If someone can edit workspace memory files, they already crossed the trusted operator boundary.
|
||||
- Memory search indexing/recall over those files is expected behavior, not a sandbox/security boundary.
|
||||
- Example report pattern considered out of scope: "attacker writes malicious content into `memory/*.md`, then `memory_search` returns it."
|
||||
- If you need isolation between mutually untrusted users, split by OS user or host and run separate gateways.
|
||||
|
||||
## Plugin Trust Boundary
|
||||
|
||||
Plugins/extensions are loaded **in-process** with the Gateway and are treated as trusted code.
|
||||
|
||||
370
appcast.xml
370
appcast.xml
@@ -209,251 +209,155 @@
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.15/OpenClaw-2026.2.15.zip" length="22896513" type="application/octet-stream" sparkle:edSignature="MLGsd2NeHXFRH1Or0bFQnAjqfuuJDuhl1mvKFIqTQcRvwbeyvOyyLXrqSbmaOgJR3wBQBKLs6jYQ9dQ/3R8RCg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.2.22</title>
|
||||
<pubDate>Mon, 23 Feb 2026 01:51:13 +0100</pubDate>
|
||||
<title>2026.2.21</title>
|
||||
<pubDate>Sat, 21 Feb 2026 17:55:48 +0100</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>14126</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.2.22</sparkle:shortVersionString>
|
||||
<sparkle:version>13056</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.2.21</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.2.22</h2>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.2.21</h2>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Provider/Mistral: add support for the Mistral provider, including memory embeddings and voice support. (#23845) Thanks @vincentkoc.</li>
|
||||
<li>Update/Core: add an optional built-in auto-updater for package installs (<code>update.auto.*</code>), default-off, with stable rollout delay+jitter and beta hourly cadence.</li>
|
||||
<li>CLI/Update: add <code>openclaw update --dry-run</code> to preview channel/tag/target/restart actions without mutating config, installing, syncing plugins, or restarting.</li>
|
||||
<li>Config/UI: add tag-aware settings filtering and broaden config labels/help copy so fields are easier to discover and understand in the dashboard config screen.</li>
|
||||
<li>Channels/Synology Chat: add a native Synology Chat channel plugin with webhook ingress, direct-message routing, outbound send/media support, per-account config, and DM policy controls. (#23012)</li>
|
||||
<li>iOS/Talk: prefetch TTS segments and suppress expected speech-cancellation errors for smoother talk playback. (#22833) Thanks @ngutman.</li>
|
||||
<li>Memory/FTS: add Spanish and Portuguese stop-word filtering for query expansion in FTS-only search mode, improving conversational recall for both languages. Thanks @vincentkoc.</li>
|
||||
<li>Memory/FTS: add Japanese-aware query expansion tokenization and stop-word filtering (including mixed-script terms like ASCII + katakana) for FTS-only search mode. Thanks @vincentkoc.</li>
|
||||
<li>Memory/FTS: add Korean stop-word filtering and particle-aware keyword extraction (including mixed Korean/English stems) for query expansion in FTS-only search mode. (#18899) Thanks @ruypang.</li>
|
||||
<li>Memory/FTS: add Arabic stop-word filtering for query expansion in FTS-only search mode to reduce conversational filler in Arabic memory searches. Thanks @vincentkoc.</li>
|
||||
<li>Discord/Allowlist: canonicalize resolved Discord allowlist names to IDs and split resolution flow for clearer fail-closed behavior.</li>
|
||||
<li>Channels/Config: unify channel preview streaming config handling with a shared resolver and canonical migration path.</li>
|
||||
<li>Gateway/Auth: unify call/probe/status/auth credential-source precedence on shared resolver helpers, with table-driven parity coverage across gateway entrypoints.</li>
|
||||
<li>Gateway/Auth: refactor gateway credential resolution and websocket auth handshake paths to use shared typed auth contexts, including explicit <code>auth.deviceToken</code> support in connect frames and tests.</li>
|
||||
<li>Skills: remove bundled <code>food-order</code> skill from this repo; manage/install it from ClawHub instead.</li>
|
||||
<li>Docs/Subagents: make thread-bound session guidance channel-first instead of Discord-specific, and list thread-supporting channels explicitly. (#23589) Thanks @osolmaz.</li>
|
||||
</ul>
|
||||
<h3>Breaking</h3>
|
||||
<ul>
|
||||
<li><strong>BREAKING:</strong> tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require <code>/verbose on</code> or <code>/verbose full</code>.</li>
|
||||
<li><strong>BREAKING:</strong> CLI local onboarding now sets <code>session.dmScope</code> to <code>per-channel-peer</code> by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set <code>session.dmScope</code> to <code>main</code>. (#23468) Thanks @bmendonca3.</li>
|
||||
<li><strong>BREAKING:</strong> unify channel preview-streaming config to <code>channels.<channel>.streaming</code> with enum values <code>off | partial | block | progress</code>, and move Slack native stream toggle to <code>channels.slack.nativeStreaming</code>. Legacy keys (<code>streamMode</code>, Slack boolean <code>streaming</code>) are still read and migrated by <code>openclaw doctor --fix</code>, but canonical saved config/docs now use the unified names.</li>
|
||||
<li><strong>BREAKING:</strong> remove legacy Gateway device-auth signature <code>v1</code>. Device-auth clients must now sign <code>v2</code> payloads with the per-connection <code>connect.challenge</code> nonce and send <code>device.nonce</code>; nonce-less connects are rejected.</li>
|
||||
<li>Models/Google: add Gemini 3.1 support (<code>google/gemini-3.1-pro-preview</code>).</li>
|
||||
<li>Providers/Onboarding: add Volcano Engine (Doubao) and BytePlus providers/models (including coding variants), wire onboarding auth choices for interactive + non-interactive flows, and align docs to <code>volcengine-api-key</code>. (#7967) Thanks @funmore123.</li>
|
||||
<li>Channels/CLI: add per-account/channel <code>defaultTo</code> outbound routing fallback so <code>openclaw agent --deliver</code> can send without explicit <code>--reply-to</code> when a default target is configured. (#16985) Thanks @KirillShchetinin.</li>
|
||||
<li>Channels: allow per-channel model overrides via <code>channels.modelByChannel</code> and note them in /status. Thanks @thewilloftheshadow.</li>
|
||||
<li>Telegram/Streaming: simplify preview streaming config to <code>channels.telegram.streaming</code> (boolean), auto-map legacy <code>streamMode</code> values, and remove block-vs-partial preview branching. (#22012) thanks @obviyus.</li>
|
||||
<li>Discord/Streaming: add stream preview mode for live draft replies with partial/block options and configurable chunking. Thanks @thewilloftheshadow. Inspiration @neoagentic-ship-it.</li>
|
||||
<li>Discord/Telegram: add configurable lifecycle status reactions for queued/thinking/tool/done/error phases with a shared controller and emoji/timing overrides. Thanks @wolly-tundracube and @thewilloftheshadow.</li>
|
||||
<li>Discord/Voice: add voice channel join/leave/status via <code>/vc</code>, plus auto-join configuration for realtime voice conversations. Thanks @thewilloftheshadow.</li>
|
||||
<li>Discord: add configurable ephemeral defaults for slash-command responses. (#16563) Thanks @wei.</li>
|
||||
<li>Discord: support updating forum <code>available_tags</code> via channel edit actions for forum tag management. (#12070) Thanks @xiaoyaner0201.</li>
|
||||
<li>Discord: include channel topics in trusted inbound metadata on new sessions. Thanks @thewilloftheshadow.</li>
|
||||
<li>Discord/Subagents: add thread-bound subagent sessions on Discord with per-thread focus/list controls and thread-bound continuation routing for spawned helper agents. (#21805) Thanks @onutc.</li>
|
||||
<li>iOS/Chat: clean chat UI noise by stripping inbound untrusted metadata/timestamp prefixes, formatting tool outputs into concise summaries/errors, compacting the composer while typing, and supporting tap-to-dismiss keyboard in chat view. (#22122) thanks @mbelinky.</li>
|
||||
<li>iOS/Watch: bridge mirrored watch prompt notification actions into iOS quick-reply handling, including queued action handoff until app model initialization. (#22123) thanks @mbelinky.</li>
|
||||
<li>iOS/Gateway: stabilize background wake and reconnect behavior with background reconnect suppression/lease windows, BGAppRefresh wake fallback, location wake hook throttling, and APNs wake retry+nudge instrumentation. (#21226) thanks @mbelinky.</li>
|
||||
<li>Auto-reply/UI: add model fallback lifecycle visibility in verbose logs, /status active-model context with fallback reason, and cohesive WebUI fallback indicators. (#20704) Thanks @joshavant.</li>
|
||||
<li>MSTeams: dedupe sent-message cache storage by removing duplicate per-message Set storage and using timestamps Map keys as the single membership source. (#22514) Thanks @TaKO8Ki.</li>
|
||||
<li>Agents/Subagents: default subagent spawn depth now uses shared <code>maxSpawnDepth=2</code>, enabling depth-1 orchestrator spawning by default while keeping depth policy checks consistent across spawn and prompt paths. (#22223) Thanks @tyler6204.</li>
|
||||
<li>Security/Agents: make owner-ID obfuscation use a dedicated HMAC secret from configuration (<code>ownerDisplaySecret</code>) and update hashing behavior so obfuscation is decoupled from gateway token handling for improved control. (#7343) Thanks @vincentkoc.</li>
|
||||
<li>Security/Infra: switch gateway lock and tool-call synthetic IDs from SHA-1 to SHA-256 with unchanged truncation length to strengthen hash basis while keeping deterministic behavior and lock key format. (#7343) Thanks @vincentkoc.</li>
|
||||
<li>Dependencies/Tooling: add non-blocking dead-code scans in CI via Knip/ts-prune/ts-unused-exports to surface unused dependencies and exports earlier. (#22468) Thanks @vincentkoc.</li>
|
||||
<li>Dependencies/Unused Dependencies: remove or scope unused root and extension deps (<code>@larksuiteoapi/node-sdk</code>, <code>signal-utils</code>, <code>ollama</code>, <code>lit</code>, <code>@lit/context</code>, <code>@lit-labs/signals</code>, <code>@microsoft/agents-hosting-express</code>, <code>@microsoft/agents-hosting-extensions-teams</code>, and plugin-local <code>openclaw</code> devDeps in <code>extensions/open-prose</code>, <code>extensions/lobster</code>, and <code>extensions/llm-task</code>). (#22471, #22495) Thanks @vincentkoc.</li>
|
||||
<li>Dependencies/A2UI: harden dependency resolution after root cleanup (resolve <code>lit</code>, <code>@lit/context</code>, <code>@lit-labs/signals</code>, and <code>signal-utils</code> from workspace/root) and simplify bundling fallback behavior, including <code>pnpm dlx rolldown</code> compatibility. (#22481, #22507) Thanks @vincentkoc.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Security/CLI: redact sensitive values in <code>openclaw config get</code> output before printing config paths, preventing credential leakage to terminal output/history. (#13683) Thanks @SleuthCo.</li>
|
||||
<li>Install/Discord Voice: make <code>@discordjs/opus</code> an optional dependency so <code>openclaw</code> install/update no longer hard-fails when native Opus builds fail, while keeping <code>opusscript</code> as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.</li>
|
||||
<li>Docker/Setup: precreate <code>$OPENCLAW_CONFIG_DIR/identity</code> during <code>docker-setup.sh</code> so CLI commands that need device identity (for example <code>devices list</code>) avoid <code>EACCES ... /home/node/.openclaw/identity</code> failures on restrictive bind mounts. (#23948) Thanks @ackson-beep.</li>
|
||||
<li>Exec/Background: stop applying the default exec timeout to background sessions (<code>background: true</code> or explicit <code>yieldMs</code>) when no explicit timeout is set, so long-running background jobs are no longer terminated at the default timeout boundary. (#23303)</li>
|
||||
<li>Slack/Threading: sessions: keep parent-session forking and thread-history context active beyond first turn by removing first-turn-only gates in session init, thread-history fetch, and reply prompt context injection. (#23843, #23090) Thanks @vincentkoc and @Taskle.</li>
|
||||
<li>Slack/Threading: respect <code>replyToMode</code> when Slack auto-populates top-level <code>thread_ts</code>, and ignore inline <code>replyToId</code> directive tags when <code>replyToMode</code> is <code>off</code> so thread forcing stays disabled unless explicitly configured. (#23839, #23320, #23513) Thanks @vincentkoc and @dorukardahan.</li>
|
||||
<li>Slack/Extension: forward <code>message read</code> <code>threadId</code> to <code>readMessages</code> and use delivery-context <code>threadId</code> as outbound <code>thread_ts</code> fallback so extension replies/reads stay in the correct Slack thread. (#22216, #22485, #23836) Thanks @vincentkoc, @lan17 and @dorukardahan.</li>
|
||||
<li>Slack/Upload: resolve bare user IDs (U-prefix) to DM channel IDs via <code>conversations.open</code> before calling <code>files.uploadV2</code>, which rejects non-channel IDs. <code>chat.postMessage</code> tolerates user IDs directly, but <code>files.uploadV2</code> → <code>completeUploadExternal</code> validates <code>channel_id</code> against <code>^[CGDZ][A-Z0-9]{8,}$</code>, causing <code>invalid_arguments</code> when agents reply with media to DM conversations.</li>
|
||||
<li>Webchat/Chat: apply assistant <code>final</code> payload messages directly to chat state so sent turns render without waiting for a full history refresh cycle. (#14928) Thanks @BradGroux.</li>
|
||||
<li>Webchat/Chat: for out-of-band final events (for example tool-call side runs), append provided final assistant payloads directly instead of forcing a transient history reset. (#11139) Thanks @AkshayNavle.</li>
|
||||
<li>Webchat/Performance: reload <code>chat.history</code> after final events only when the final payload lacks a renderable assistant message, avoiding expensive full-history refreshes on normal turns. (#20588) Thanks @amzzzzzzz.</li>
|
||||
<li>Webchat/Sessions: preserve external session routing metadata when internal <code>chat.send</code> turns run under <code>webchat</code>, so explicit channel-keyed sessions (for example Telegram) no longer get rewritten to <code>webchat</code> and misroute follow-up delivery. (#23258) Thanks @binary64.</li>
|
||||
<li>Webchat/Sessions: preserve existing session <code>label</code> across <code>/new</code> and <code>/reset</code> rollovers so reset sessions remain discoverable in session history lists. (#23755) Thanks @ThunderStormer.</li>
|
||||
<li>Gateway/Chat UI: strip inline reply/audio directive tags from non-streaming final webchat broadcasts (including <code>chat.inject</code>) while preserving empty-string message content when tags are the entire reply. (#23298) Thanks @SidQin-cyber.</li>
|
||||
<li>Chat/UI: strip inline reply/audio directive tags (<code>[[reply_to_current]]</code>, <code>[[reply_to:<id>]]</code>, <code>[[audio_as_voice]]</code>) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.</li>
|
||||
<li>Telegram/Media: send a user-facing Telegram reply when media download fails (non-size errors) instead of silently dropping the message.</li>
|
||||
<li>Telegram/Webhook: keep webhook monitors alive until gateway abort signals fire, preventing false channel exits and immediate webhook auto-restart loops.</li>
|
||||
<li>Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions.</li>
|
||||
<li>Telegram/Polling: clear Telegram webhooks (<code>deleteWebhook</code>) before starting long-poll <code>getUpdates</code>, including retry handling for transient cleanup failures.</li>
|
||||
<li>Telegram/Webhook: add <code>channels.telegram.webhookPort</code> config support and pass it through plugin startup wiring to the monitor listener.</li>
|
||||
<li>Browser/Extension Relay: refactor the MV3 worker to preserve debugger attachments across relay drops, auto-reconnect with bounded backoff+jitter, persist and rehydrate attached tab state via <code>chrome.storage.session</code>, recover from <code>target_closed</code> navigation detaches, guard stale socket handlers, enforce per-tab operation locks and per-request timeouts, and add lifecycle keepalive/badge refresh hooks (<code>alarms</code>, <code>webNavigation</code>). (#15099, #6175, #8468, #9807)</li>
|
||||
<li>Browser/Relay: treat extension websocket as connected only when <code>OPEN</code>, allow reconnect when a stale <code>CLOSING/CLOSED</code> extension socket lingers, and guard stale socket message/close handlers so late events cannot clear active relay state; includes regression coverage for live-duplicate <code>409</code> rejection and immediate reconnect-after-close races. (#15099, #18698, #20688)</li>
|
||||
<li>Browser/Remote CDP: extend stale-target recovery so <code>ensureTabAvailable()</code> now reuses the sole available tab for remote CDP profiles (same behavior as extension profiles) while preserving strict <code>tab not found</code> errors when multiple tabs exist; includes remote-profile regression tests. (#15989)</li>
|
||||
<li>Gateway/Pairing: treat <code>operator.admin</code> as satisfying other <code>operator.*</code> scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.</li>
|
||||
<li>Gateway/Pairing: auto-approve loopback <code>scope-upgrade</code> pairing requests (including device-token reconnects) so local clients do not disconnect on pairing-required scope elevation. (#23708) Thanks @widingmarcus-cyber.</li>
|
||||
<li>Gateway/Scopes: include <code>operator.read</code> and <code>operator.write</code> in default operator connect scope bundles across CLI, Control UI, and macOS clients so write-scoped announce/sub-agent follow-up calls no longer hit <code>pairing required</code> disconnects on loopback gateways. (#22582) thanks @YuzuruS.</li>
|
||||
<li>Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.</li>
|
||||
<li>Gateway/Restart: fix restart-loop edge cases by keeping <code>openclaw.mjs -> dist/entry.js</code> bootstrap detection explicit, reacquiring the gateway lock for in-process restart fallback paths, and tightening restart-loop regression coverage. (#23416) Thanks @jeffwnli.</li>
|
||||
<li>Gateway/Lock: use optional gateway-port reachability as a primary stale-lock liveness signal (and wire gateway run-loop lock acquisition to the resolved port), reducing false "already running" lockouts after unclean exits. (#23760) Thanks @Operative-001.</li>
|
||||
<li>Delivery/Queue: quarantine queue entries immediately on known permanent delivery errors (for example invalid recipients or missing conversation references) by moving them to <code>failed/</code> instead of retrying on every restart. (#23794) Thanks @aldoeliacim.</li>
|
||||
<li>Cron/Status: split execution outcome (<code>lastRunStatus</code>) from delivery outcome (<code>lastDeliveryStatus</code>) in persisted cron state, finished events, and run history so failed/unknown announcement delivery is visible without conflating it with run errors.</li>
|
||||
<li>Cron/Delivery: route text-only announce jobs with explicit thread/topic targets through direct outbound delivery so forum/thread destinations do not get dropped by intermediary announce turns. (#23841) Thanks @AndrewArto.</li>
|
||||
<li>Security/Agents: cap embedded Pi runner outer retry loop with a higher profile-aware dynamic limit (32-160 attempts) and return an explicit <code>retry_limit</code> error payload when retries never converge, preventing unbounded internal retry cycles (<code>GHSA-76m6-pj3w-v7mf</code>).</li>
|
||||
<li>Telegram: detect duplicate bot-token ownership across Telegram accounts at startup/status time, mark secondary accounts as not configured with an explicit fix message, and block duplicate account startup before polling to avoid endless <code>getUpdates</code> conflict loops.</li>
|
||||
<li>Agents/Tool images: include source filenames in <code>agents/tool-images</code> resize logs so compression events can be traced back to specific files.</li>
|
||||
<li>Providers/OAuth: harden Qwen and Chutes refresh handling by validating refresh response expiry values and preserving prior refresh tokens when providers return empty refresh token fields, with regression coverage for empty-token responses.</li>
|
||||
<li>Models/Kimi-Coding: add missing implicit provider template for <code>kimi-coding</code> with correct <code>anthropic-messages</code> API type and base URL, fixing 403 errors when using Kimi for Coding. (#22409)</li>
|
||||
<li>Auto-reply/Tools: forward <code>senderIsOwner</code> through embedded queued/followup runner params so owner-only tools remain available for authorized senders. (#22296) thanks @hcoj.</li>
|
||||
<li>Discord: restore model picker back navigation when a provider is missing and document the Discord picker flow. (#21458) Thanks @pejmanjohn and @thewilloftheshadow.</li>
|
||||
<li>Memory/QMD: respect per-agent <code>memorySearch.enabled=false</code> during gateway QMD startup initialization, split multi-collection QMD searches into per-collection queries (<code>search</code>/<code>vsearch</code>/<code>query</code>) to avoid sparse-term drops, prefer collection-hinted doc resolution to avoid stale-hash collisions, retry boot updates on transient lock/timeout failures, skip <code>qmd embed</code> in BM25-only <code>search</code> mode (including <code>memory index --force</code>), and serialize embed runs globally with failure backoff to prevent CPU storms on multi-agent hosts. (#20581, #21590, #20513, #20001, #21266, #21583, #20346, #19493) Thanks @danielrevivo, @zanderkrause, @sunyan034-cmd, @tilleulenspiegel, @dae-oss, @adamlongcreativellc, @jonathanadams96, and @kiliansitel.</li>
|
||||
<li>Memory/Builtin: prevent automatic sync races with manager shutdown by skipping post-close sync starts and waiting for in-flight sync before closing SQLite, so <code>onSearch</code>/<code>onSessionStart</code> no longer fail with <code>database is not open</code> in ephemeral CLI flows. (#20556, #7464) Thanks @FuzzyTG and @henrybottter.</li>
|
||||
<li>Providers/Copilot: drop persisted assistant <code>thinking</code> blocks for Claude models (while preserving turn structure/tool blocks) so follow-up requests no longer fail on invalid <code>thinkingSignature</code> payloads. (#19459) Thanks @jackheuberger.</li>
|
||||
<li>Providers/Copilot: add <code>claude-sonnet-4.6</code> and <code>claude-sonnet-4.5</code> to the default GitHub Copilot model catalog and add coverage for model-list/definition helpers. (#20270, fixes #20091) Thanks @Clawborn.</li>
|
||||
<li>Auto-reply/WebChat: avoid defaulting inbound runtime channel labels to unrelated providers (for example <code>whatsapp</code>) for webchat sessions so channel-specific formatting guidance stays accurate. (#21534) Thanks @lbo728.</li>
|
||||
<li>Status: include persisted <code>cacheRead</code>/<code>cacheWrite</code> in session summaries so compact <code>/status</code> output consistently shows cache hit percentages from real session data.</li>
|
||||
<li>Heartbeat/Cron: restore interval heartbeat behavior so missing <code>HEARTBEAT.md</code> no longer suppresses runs (only effectively empty files skip), preserving prompt-driven and tagged-cron execution paths.</li>
|
||||
<li>WhatsApp/Cron/Heartbeat: enforce allowlisted routing for implicit scheduled/system delivery by merging pairing-store + configured <code>allowFrom</code> recipients, selecting authorized recipients when last-route context points to a non-allowlisted chat, and preventing heartbeat fan-out to recent unauthorized chats.</li>
|
||||
<li>Heartbeat/Active hours: constrain active-hours <code>24</code> sentinel parsing to <code>24:00</code> in time validation so invalid values like <code>24:30</code> are rejected early. (#21410) thanks @adhitShet.</li>
|
||||
<li>Heartbeat: treat <code>activeHours</code> windows with identical <code>start</code>/<code>end</code> times as zero-width (always outside the window) instead of always-active. (#21408) thanks @adhitShet.</li>
|
||||
<li>CLI/Pairing: default <code>pairing list</code> and <code>pairing approve</code> to the sole available pairing channel when omitted, so TUI-only setups can recover from <code>pairing required</code> without guessing channel arguments. (#21527) Thanks @losts1.</li>
|
||||
<li>TUI/Pairing: show explicit pairing-required recovery guidance after gateway disconnects that return <code>pairing required</code>, including approval steps to unblock quickstart TUI hatching on fresh installs. (#21841) Thanks @nicolinux.</li>
|
||||
<li>TUI/Input: suppress duplicate backspace events arriving in the same input burst window so SSH sessions no longer delete two characters per backspace press in the composer. (#19318) Thanks @eheimer.</li>
|
||||
<li>TUI/Heartbeat: suppress heartbeat ACK/prompt noise in chat streaming when <code>showOk</code> is disabled, while still preserving non-ACK heartbeat alerts in final output. (#20228) Thanks @bhalliburton.</li>
|
||||
<li>TUI/History: cap chat-log component growth and prune stale render nodes/references so large default history loads no longer overflow render recursion with <code>RangeError: Maximum call stack size exceeded</code>. (#18068) Thanks @JaniJegoroff.</li>
|
||||
<li>Memory/QMD: diversify mixed-source search ranking when both session and memory collections are present so session transcript hits no longer crowd out durable memory-file matches in top results. (#19913) Thanks @alextempr.</li>
|
||||
<li>Memory/Tools: return explicit <code>unavailable</code> warnings/actions from <code>memory_search</code> when embedding/provider failures occur (including quota exhaustion), so disabled memory does not look like an empty recall result. (#21894) Thanks @XBS9.</li>
|
||||
<li>Session/Startup: require the <code>/new</code> and <code>/reset</code> greeting path to run Session Startup file-reading instructions before responding, so daily memory startup context is not skipped on fresh-session greetings. (#22338) Thanks @armstrong-pv.</li>
|
||||
<li>Auth/Onboarding: align OAuth profile-id config mapping with stored credential IDs for OpenAI Codex and Chutes flows, preventing <code>provider:default</code> mismatches when OAuth returns email-scoped credentials. (#12692) thanks @mudrii.</li>
|
||||
<li>Provider/HTTP: treat HTTP 503 as failover-eligible for LLM provider errors. (#21086) Thanks @Protocol-zero-0.</li>
|
||||
<li>Slack: pass <code>recipient_team_id</code> / <code>recipient_user_id</code> through Slack native streaming calls so <code>chat.startStream</code>/<code>appendStream</code>/<code>stopStream</code> work reliably across DMs and Slack Connect setups, and disable block streaming when native streaming is active. (#20988) Thanks @Dithilli. Earlier recipient-ID groundwork was contributed in #20377 by @AsserAl1012.</li>
|
||||
<li>CLI/Config: add canonical <code>--strict-json</code> parsing for <code>config set</code> and keep <code>--json</code> as a legacy alias to reduce help/behavior drift. (#21332) thanks @adhitShet.</li>
|
||||
<li>CLI: keep <code>openclaw -v</code> as a root-only version alias so subcommand <code>-v, --verbose</code> flags (for example ACP/hooks/skills) are no longer intercepted globally. (#21303) thanks @adhitShet.</li>
|
||||
<li>Memory: return empty snippets when <code>memory_get</code>/QMD read files that have not been created yet, and harden memory indexing/session helpers against ENOENT races so missing Markdown no longer crashes tools. (#20680) Thanks @pahdo.</li>
|
||||
<li>Telegram/Streaming: always clean up draft previews even when dispatch throws before fallback handling, preventing orphaned preview messages during failed runs. (#19041) thanks @mudrii.</li>
|
||||
<li>Telegram/Streaming: split reasoning and answer draft preview lanes to prevent cross-lane overwrites, and ignore literal <code><think></code> tags inside inline/fenced code snippets so sample markup is not misrouted as reasoning. (#20774) Thanks @obviyus.</li>
|
||||
<li>Telegram/Streaming: restore 30-char first-preview debounce and scope <code>NO_REPLY</code> prefix suppression to partial sentinel fragments so normal <code>No...</code> text is not filtered. (#22613) thanks @obviyus.</li>
|
||||
<li>Telegram/Status reactions: refresh stall timers on repeated phase updates and honor ack-reaction scope when lifecycle reactions are enabled, preventing false stall emojis and unwanted group reactions. Thanks @wolly-tundracube and @thewilloftheshadow.</li>
|
||||
<li>Telegram/Status reactions: keep lifecycle reactions active when available-reactions lookup fails by falling back to unrestricted variant selection instead of suppressing reaction updates. (#22380) thanks @obviyus.</li>
|
||||
<li>Discord/Streaming: apply <code>replyToMode: first</code> only to the first Discord chunk so block-streamed replies do not spam mention pings. (#20726) Thanks @thewilloftheshadow for the report.</li>
|
||||
<li>Discord/Components: map DM channel targets back to user-scoped component sessions so button/select interactions stay in the main DM session. Thanks @thewilloftheshadow.</li>
|
||||
<li>Discord/Allowlist: lazy-load guild lists when resolving Discord user allowlists so ID-only entries resolve even if guild fetch fails. (#20208) Thanks @zhangjunmengyang.</li>
|
||||
<li>Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.</li>
|
||||
<li>Discord: ingest inbound stickers as media so sticker-only messages and forwarded stickers are visible to agents. Thanks @thewilloftheshadow.</li>
|
||||
<li>Auto-reply/Runner: emit <code>onAgentRunStart</code> only after agent lifecycle or tool activity begins (and only once per run), so fallback preflight errors no longer mark runs as started. (#21165) Thanks @shakkernerd.</li>
|
||||
<li>Auto-reply/Tool results: serialize tool-result delivery and keep the delivery chain progressing after individual failures so concurrent tool outputs preserve user-visible ordering. (#21231) thanks @ahdernasr.</li>
|
||||
<li>Auto-reply/Prompt caching: restore prefix-cache stability by keeping inbound system metadata session-stable and moving per-message IDs (<code>message_id</code>, <code>message_id_full</code>, <code>reply_to_id</code>, <code>sender_id</code>) into untrusted conversation context. (#20597) Thanks @anisoptera.</li>
|
||||
<li>iOS/Watch: add actionable watch approval/reject controls and quick-reply actions so watch-originated approvals and responses can be sent directly from notification flows. (#21996) Thanks @mbelinky.</li>
|
||||
<li>iOS/Watch: refresh iOS and watch app icon assets with the lobster icon set to keep phone/watch branding aligned. (#21997) Thanks @mbelinky.</li>
|
||||
<li>CLI/Onboarding: fix Anthropic-compatible custom provider verification by normalizing base URLs to avoid duplicate <code>/v1</code> paths during setup checks. (#21336) Thanks @17jmumford.</li>
|
||||
<li>iOS/Gateway/Tools: prefer uniquely connected node matches when duplicate display names exist, surface actionable <code>nodes invoke</code> pairing-required guidance with request IDs, and refresh active iOS gateway registration after location-capability setting changes so capability updates apply immediately. (#22120) thanks @mbelinky.</li>
|
||||
<li>Gateway/Auth: require <code>gateway.trustedProxies</code> to include a loopback proxy address when <code>auth.mode="trusted-proxy"</code> and <code>bind="loopback"</code>, preventing same-host proxy misconfiguration from silently blocking auth. (#22082, follow-up to #20097) thanks @mbelinky.</li>
|
||||
<li>Gateway/Auth: allow trusted-proxy mode with loopback bind for same-host reverse-proxy deployments, while still requiring configured <code>gateway.trustedProxies</code>. (#20097) thanks @xinhuagu.</li>
|
||||
<li>Gateway/Auth: allow authenticated clients across roles/scopes to call <code>health</code> while preserving role and scope enforcement for non-health methods. (#19699) thanks @Nachx639.</li>
|
||||
<li>Gateway/Hooks: include transform export name in hook-transform cache keys so distinct exports from the same module do not reuse the wrong cached transform function. (#13855) thanks @mcaxtr.</li>
|
||||
<li>Gateway/Control UI: return 404 for missing static-asset paths instead of serving SPA fallback HTML, while preserving client-route fallback behavior for extensionless and non-asset dotted paths. (#12060) thanks @mcaxtr.</li>
|
||||
<li>Gateway/Pairing: prevent device-token rotate scope escalation by enforcing an approved-scope baseline, preserving approved scopes across metadata updates, and rejecting rotate requests that exceed approved role scope implications. (#20703) thanks @coygeek.</li>
|
||||
<li>Gateway/Pairing: clear persisted paired-device state when the gateway client closes with <code>device token mismatch</code> (<code>1008</code>) so reconnect flows can cleanly re-enter pairing. (#22071) Thanks @mbelinky.</li>
|
||||
<li>Gateway/Config: allow <code>gateway.customBindHost</code> in strict config validation when <code>gateway.bind="custom"</code> so valid custom bind-host configurations no longer fail startup. (#20318, fixes #20289) Thanks @MisterGuy420.</li>
|
||||
<li>Gateway/Pairing: tolerate legacy paired devices missing <code>roles</code>/<code>scopes</code> metadata in websocket upgrade checks and backfill metadata on reconnect. (#21447, fixes #21236) Thanks @joshavant.</li>
|
||||
<li>Gateway/Pairing/CLI: align read-scope compatibility in pairing/device-token checks and add local <code>openclaw devices</code> fallback recovery for loopback <code>pairing required</code> deadlocks, with explicit fallback notice to unblock approval bootstrap flows. (#21616) Thanks @shakkernerd.</li>
|
||||
<li>Cron: honor <code>cron.maxConcurrentRuns</code> in the timer loop so due jobs can execute up to the configured parallelism instead of always running serially. (#11595) Thanks @Takhoffman.</li>
|
||||
<li>Cron/Run: enforce the same per-job timeout guard for manual <code>cron.run</code> executions as timer-driven runs, including abort propagation for isolated agent jobs, so forced runs cannot wedge indefinitely. (#23704) Thanks @tkuehnl.</li>
|
||||
<li>Cron/Run: persist the manual-run <code>runningAtMs</code> marker before releasing the cron lock so overlapping timer ticks cannot start the same job concurrently.</li>
|
||||
<li>Cron/Startup: enforce per-job timeout guards for startup catch-up replay runs so missed isolated jobs cannot hang indefinitely during gateway boot recovery.</li>
|
||||
<li>Cron/Main session: honor abort/timeout signals while retrying <code>wakeMode=now</code> heartbeat contention loops so main-target cron runs stop promptly instead of waiting through the full busy-retry window.</li>
|
||||
<li>Cron/Schedule: for <code>every</code> jobs, prefer <code>lastRunAtMs + everyMs</code> when still in the future after restarts, then fall back to anchor scheduling for catch-up windows, so NEXT timing matches the last successful cadence. (#22895) Thanks @SidQin-cyber.</li>
|
||||
<li>Cron/Service: execute manual <code>cron.run</code> jobs outside the cron lock (while still persisting started/finished state atomically) so <code>cron.list</code> and <code>cron.status</code> remain responsive during long forced runs. (#23628) Thanks @dsgraves.</li>
|
||||
<li>Cron/Timer: keep a watchdog recheck timer armed while <code>onTimer</code> is actively executing so the scheduler continues polling even if a due-run tick stalls for an extended period. (#23628) Thanks @dsgraves.</li>
|
||||
<li>Cron/Run log: clean up settled per-path run-log write queue entries so long-running cron uptime does not retain stale promise bookkeeping in memory.</li>
|
||||
<li>Cron/Isolation: force fresh session IDs for isolated cron runs so <code>sessionTarget="isolated"</code> executions never reuse prior run context. (#23470) Thanks @echoVic.</li>
|
||||
<li>Plugins/Install: strip <code>workspace:*</code> devDependency entries from copied plugin manifests before <code>npm install --omit=dev</code>, preventing <code>EUNSUPPORTEDPROTOCOL</code> install failures for npm-published channel plugins (including Feishu and MS Teams).</li>
|
||||
<li>Feishu/Plugins: restore bundled Feishu SDK availability for global installs and strip <code>openclaw: workspace:*</code> from plugin <code>devDependencies</code> during plugin-version sync so npm-installed Feishu plugins do not fail dependency install. (#23611, #23645, #23603)</li>
|
||||
<li>Config/Channels: auto-enable built-in channels by writing <code>channels.<id>.enabled=true</code> (not <code>plugins.entries.<id></code>), and stop adding built-ins to <code>plugins.allow</code>, preventing <code>plugins.entries.telegram: plugin not found</code> validation failures.</li>
|
||||
<li>Config/Channels: when <code>plugins.allow</code> is active, auto-enable/enable flows now also allowlist configured built-in channels so <code>channels.<id>.enabled=true</code> cannot remain blocked by restrictive plugin allowlists.</li>
|
||||
<li>Plugins/Discovery: ignore scanned extension backup/disabled directory patterns (for example <code>.backup-*</code>, <code>.bak</code>, <code>.disabled*</code>) and move updater backup directories under <code>.openclaw-install-backups</code>, preventing duplicate plugin-id collisions from archived copies.</li>
|
||||
<li>Plugins/CLI: make <code>openclaw plugins enable</code> and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl.</li>
|
||||
<li>Security/Voice Call: harden media stream WebSocket handling against pre-auth idle-connection DoS by adding strict pre-start timeouts, pending/per-IP connection limits, and total connection caps for streaming endpoints. This ships in the next npm release. Thanks @jiseoung for reporting.</li>
|
||||
<li>Security/Sessions: redact sensitive token patterns from <code>sessions_history</code> tool output and surface <code>contentRedacted</code> metadata when masking occurs. (#16928) Thanks @aether-ai-agent.</li>
|
||||
<li>Security/Exec: stop trusting <code>PATH</code>-derived directories for safe-bin allowlist checks, add explicit <code>tools.exec.safeBinTrustedDirs</code>, and pin safe-bin shell execution to resolved absolute executable paths to prevent binary-shadowing approval bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Elevated: match <code>tools.elevated.allowFrom</code> against sender identities only (not recipient <code>ctx.To</code>), closing a recipient-token bypass for <code>/elevated</code> authorization. This ships in the next npm release. Thanks @jiseoung for reporting.</li>
|
||||
<li>Security/Feishu: enforce ID-only allowlist matching for DM/group sender authorization, normalize Feishu ID prefixes during checks, and ignore mutable display names so display-name collisions cannot satisfy allowlist entries. This ships in the next npm release. Thanks @jiseoung for reporting.</li>
|
||||
<li>Security/Group policy: harden <code>channels.*.groups.*.toolsBySender</code> matching by requiring explicit sender-key types (<code>id:</code>, <code>e164:</code>, <code>username:</code>, <code>name:</code>), preventing cross-identifier collisions across mutable/display-name fields while keeping legacy untyped keys on a deprecated ID-only path. This ships in the next npm release. Thanks @jiseoung for reporting.</li>
|
||||
<li>Channels/Group policy: fail closed when <code>groupPolicy: "allowlist"</code> is set without explicit <code>groups</code>, honor account-level <code>groupPolicy</code> overrides, and enforce <code>groupPolicy: "disabled"</code> as a hard group block. (#22215) Thanks @etereo.</li>
|
||||
<li>Telegram/Discord extensions: propagate trusted <code>mediaLocalRoots</code> through extension outbound <code>sendMedia</code> options so extension direct-send media paths honor agent-scoped local-media allowlists. (#20029, #21903, #23227)</li>
|
||||
<li>Agents/Exec: honor explicit agent context when resolving <code>tools.exec</code> defaults for runs with opaque/non-agent session keys, so per-agent <code>host/security/ask</code> policies are applied consistently. (#11832)</li>
|
||||
<li>Doctor/Security: add an explicit warning that <code>approvals.exec.enabled=false</code> disables forwarding only, while enforcement remains driven by host-local <code>exec-approvals.json</code> policy. (#15047)</li>
|
||||
<li>Sandbox/Docker: default sandbox container user to the workspace owner <code>uid:gid</code> when <code>agents.*.sandbox.docker.user</code> is unset, fixing non-root gateway file-tool permissions under capability-dropped containers. (#20979)</li>
|
||||
<li>Plugins/Media sandbox: propagate trusted <code>mediaLocalRoots</code> through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718)</li>
|
||||
<li>Agents/Workspace guard: map sandbox container-workdir file-tool paths (for example <code>/workspace/...</code> and <code>file:///workspace/...</code>) to host workspace roots before workspace-only validation, preventing false <code>Path escapes sandbox root</code> rejections for sandbox file tools. (#9560)</li>
|
||||
<li>Gateway/Exec approvals: expire approval requests immediately when no approval-capable gateway clients are connected and no forwarding targets are available, avoiding delayed approvals after restarts/offline approver windows. (#22144)</li>
|
||||
<li>Security/Exec approvals: when approving wrapper commands with allow-always in allowlist mode, persist inner executable paths for known dispatch wrappers (<code>env</code>, <code>nice</code>, <code>nohup</code>, <code>stdbuf</code>, <code>timeout</code>) and fail closed (no persisted entry) when wrapper unwrapping is not safe, preventing wrapper-path approval bypasses. Thanks @tdjackey for reporting.</li>
|
||||
<li>Node/macOS exec host: default headless macOS node <code>system.run</code> to local execution and only route through the companion app when <code>OPENCLAW_NODE_EXEC_HOST=app</code> is explicitly set, avoiding companion-app filesystem namespace mismatches during exec. (#23547)</li>
|
||||
<li>Sandbox/Media: map container workspace paths (<code>/workspace/...</code> and <code>file:///workspace/...</code>) back to the host sandbox root for outbound media validation, preventing false deny errors for sandbox-generated local media. (#23083) Thanks @echo931.</li>
|
||||
<li>Sandbox/Docker: apply custom bind mounts after workspace mounts and prioritize bind-source resolution on overlapping paths, so explicit workspace binds are no longer ignored. (#22669) Thanks @tasaankaeris.</li>
|
||||
<li>Exec approvals/Forwarding: restore Discord text forwarding when component approvals are not configured, and carry request snapshots through resolve events so resolved notices still forward after cache misses/restarts. (#22988) Thanks @bubmiller.</li>
|
||||
<li>Control UI/WebSocket: stop and clear the browser gateway client on UI teardown so remounts cannot leave orphan websocket clients that create duplicate active connections. (#23422) Thanks @floatinggball-design.</li>
|
||||
<li>Control UI/WebSocket: send a stable per-tab <code>instanceId</code> in websocket connect frames so reconnect cycles keep a consistent client identity for diagnostics and presence tracking. (#23616) Thanks @zq58855371-ui.</li>
|
||||
<li>Config/Memory: allow <code>"mistral"</code> in <code>agents.defaults.memorySearch.provider</code> and <code>agents.defaults.memorySearch.fallback</code> schema validation. (#14934) Thanks @ThomsenDrake.</li>
|
||||
<li>Feishu/Commands: in group chats, command authorization now falls back to top-level <code>channels.feishu.allowFrom</code> when per-group <code>allowFrom</code> is not set, so <code>/command</code> no longer gets blocked by an unintended empty allowlist. (#23756)</li>
|
||||
<li>Dev tooling: prevent <code>CLAUDE.md</code> symlink target regressions by excluding CLAUDE symlink sentinels from <code>oxfmt</code> and marking them <code>-text</code> in <code>.gitattributes</code>, so formatter/EOL normalization cannot reintroduce trailing-newline targets. Thanks @vincentkoc.</li>
|
||||
<li>Agents/Compaction: restore embedded compaction safeguard/context-pruning extension loading in production by wiring bundled extension factories into the resource loader instead of runtime file-path resolution. (#22349) Thanks @Glucksberg.</li>
|
||||
<li>Feishu/Media: for inbound video messages that include both <code>file_key</code> (video) and <code>image_key</code> (thumbnail), prefer <code>file_key</code> when downloading media so video attachments are saved instead of silently failing on thumbnail keys. (#23633)</li>
|
||||
<li>Hooks/Loader: avoid redundant hook-module recompilation on gateway restart by skipping cache-busting for bundled hooks and using stable file metadata keys (<code>mtime+size</code>) for mutable workspace/managed/plugin hook imports. (#16953) Thanks @mudrii.</li>
|
||||
<li>Hooks/Cron: suppress duplicate main-session events for delivered hook turns and mark <code>SILENT_REPLY_TOKEN</code> (<code>NO_REPLY</code>) early exits as delivered to prevent hook context pollution. (#20678) Thanks @JonathanWorks.</li>
|
||||
<li>Providers/OpenRouter: inject <code>cache_control</code> on system prompts for OpenRouter Anthropic models to improve prompt-cache reuse. (#17473) Thanks @rrenamed.</li>
|
||||
<li>Installer/Smoke tests: remove legacy <code>OPENCLAW_USE_GUM</code> overrides from docker install-smoke runs so tests exercise installer auto TTY detection behavior directly.</li>
|
||||
<li>Providers/OpenRouter: allow pass-through OpenRouter and Opencode model IDs in live model filtering so custom routed model IDs are treated as modern refs. (#14312) Thanks @Joly0.</li>
|
||||
<li>Providers/OpenRouter: default reasoning to enabled when the selected model advertises <code>reasoning: true</code> and no session/directive override is set. (#22513) Thanks @zwffff.</li>
|
||||
<li>Providers/OpenRouter: map <code>/think</code> levels to <code>reasoning.effort</code> in embedded runs while preserving explicit <code>reasoning.max_tokens</code> payloads. (#17236) Thanks @robbyczgw-cla.</li>
|
||||
<li>Providers/OpenRouter: preserve stored session provider when model IDs are vendor-prefixed (for example, <code>anthropic/...</code>) so follow-up turns do not incorrectly route to direct provider APIs. (#22753) Thanks @dndodson.</li>
|
||||
<li>Providers/OpenRouter: preserve the required <code>openrouter/</code> prefix for OpenRouter-native model IDs during model-ref normalization. (#12942) Thanks @omair445.</li>
|
||||
<li>Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko.</li>
|
||||
<li>Providers/OpenRouter: preserve model allowlist entries containing OpenRouter preset paths (for example <code>openrouter/@preset/...</code>) by treating <code>/model ...@profile</code> auth-profile parsing as a suffix-only override. (#14120) Thanks @NotMainstream.</li>
|
||||
<li>Cron/Auth: propagate auth-profile resolution to isolated cron sessions so provider API keys are resolved the same way as main sessions, fixing 401 errors when using providers configured via auth-profiles. (#20689) Thanks @lailoo.</li>
|
||||
<li>Cron/Follow-up: pass resolved <code>agentDir</code> through isolated cron and queued follow-up embedded runs so auth/profile lookups stay scoped to the correct agent directory. (#22845) Thanks @seilk.</li>
|
||||
<li>Agents/Media: route tool-result <code>MEDIA:</code> extraction through shared parser validation so malformed prose like <code>MEDIA:-prefixed ...</code> is no longer treated as a local file path (prevents Telegram ENOENT tool-error overrides). (#18780) Thanks @HOYALIM.</li>
|
||||
<li>Logging: cap single log-file size with <code>logging.maxFileBytes</code> (default 500 MB) and suppress additional writes after cap hit to prevent disk exhaustion from repeated error storms.</li>
|
||||
<li>Memory/Remote HTTP: centralize remote memory HTTP calls behind a shared guarded helper (<code>withRemoteHttpResponse</code>) so embeddings and batch flows use one request/release path.</li>
|
||||
<li>Memory/Embeddings: apply configured remote-base host pinning (<code>allowedHostnames</code>) across OpenAI/Voyage/Gemini embedding requests to keep private/self-hosted endpoints working without cross-host drift. (#18198) Thanks @ianpcook.</li>
|
||||
<li>Memory/Batch: route OpenAI/Voyage/Gemini batch upload/create/status/download requests through the same guarded HTTP path for consistent SSRF policy enforcement.</li>
|
||||
<li>Memory/Index: detect memory source-set changes (for example enabling <code>sessions</code> after an existing memory-only index) and trigger a full reindex so existing session transcripts are indexed without requiring <code>--force</code>. (#17576) Thanks @TarsAI-Agent.</li>
|
||||
<li>Memory/Embeddings: enforce a per-input 8k safety cap before embedding batching and apply a conservative 2k fallback limit for local providers without declared input limits, preventing oversized session/memory chunks from triggering provider context-size failures during sync/indexing. (#6016) Thanks @batumilove.</li>
|
||||
<li>Memory/QMD: on Windows, resolve bare <code>qmd</code>/<code>mcporter</code> command names to npm shim executables (<code>.cmd</code>) before spawning, so qmd boot updates and mcporter-backed searches no longer fail with <code>spawn ... ENOENT</code> on default npm installs. (#23899) Thanks @arcbuilder-ai.</li>
|
||||
<li>Memory/QMD: parse plain-text <code>qmd collection list --json</code> output when older qmd builds ignore JSON mode, and retry memory searches once after re-ensuring managed collections when qmd returns <code>Collection not found ...</code>. (#23613) Thanks @leozhucn.</li>
|
||||
<li>Signal/RPC: guard malformed Signal RPC JSON responses with a clear status-scoped error and add regression coverage for invalid JSON responses. (#22995) Thanks @adhitShet.</li>
|
||||
<li>Gateway/Subagents: guard gateway and subagent session-key/message trim paths against undefined inputs to prevent early <code>Cannot read properties of undefined (reading 'trim')</code> crashes during subagent spawn and wait flows.</li>
|
||||
<li>Agents/Workspace: guard <code>resolveUserPath</code> against undefined/null input to prevent <code>Cannot read properties of undefined (reading 'trim')</code> crashes when workspace paths are missing in embedded runner flows.</li>
|
||||
<li>Auth/Profiles: keep active <code>cooldownUntil</code>/<code>disabledUntil</code> windows immutable across retries so mid-window failures cannot extend recovery indefinitely; only recompute a backoff window after the previous deadline has expired. This resolves cron/inbound retry loops that could trap gateways until manual <code>usageStats</code> cleanup. (#23516, #23536) Thanks @arosstale.</li>
|
||||
<li>Channels/Security: fail closed on missing provider group policy config by defaulting runtime group policy to <code>allowlist</code> (instead of inheriting <code>channels.defaults.groupPolicy</code>) when <code>channels.<provider></code> is absent across message channels, and align runtime + security warnings/docs to the same fallback behavior (Slack, Discord, iMessage, Telegram, WhatsApp, Signal, LINE, Matrix, Mattermost, Google Chat, IRC, Nextcloud Talk, Feishu, and Zalo user flows; plus Discord message/native-command paths). (#23367) Thanks @bmendonca3.</li>
|
||||
<li>Gateway/Onboarding: harden remote gateway onboarding defaults and guidance by defaulting discovered direct URLs to <code>wss://</code>, rejecting insecure non-loopback <code>ws://</code> targets in onboarding validation, and expanding remote-security remediation messaging across gateway client/call/doctor flows. (#23476) Thanks @bmendonca3.</li>
|
||||
<li>CLI/Sessions: pass the configured sessions directory when resolving transcript paths in <code>agentCommand</code>, so custom <code>session.store</code> locations resume sessions reliably. Thanks @davidrudduck.</li>
|
||||
<li>Signal/Monitor: treat user-initiated abort shutdowns as clean exits when auto-started <code>signal-cli</code> is terminated, while still surfacing unexpected daemon exits as startup/runtime failures. (#23379) Thanks @frankekn.</li>
|
||||
<li>Channels/Dedupe: centralize plugin dedupe primitives in plugin SDK (memory + persistent), move Feishu inbound dedupe to a namespace-scoped persistent store, and reuse shared dedupe cache logic for Zalo webhook replay + Tlon processed-message tracking to reduce duplicate handling during reconnect/replay paths. (#23377) Thanks @SidQin-cyber.</li>
|
||||
<li>Channels/Delivery: remove hardcoded WhatsApp delivery fallbacks; require explicit/session channel context or auto-pick the sole configured channel when unambiguous. (#23357) Thanks @lbo728.</li>
|
||||
<li>ACP/Gateway: wait for gateway hello before opening ACP requests, and fail fast on pre-hello connect failures to avoid startup hangs and early <code>gateway not connected</code> request races. (#23390) Thanks @janckerchen.</li>
|
||||
<li>Gateway/Auth: preserve <code>OPENCLAW_GATEWAY_PASSWORD</code> env override precedence for remote gateway call credentials after shared resolver refactors, preventing stale configured remote passwords from overriding runtime secret rotation.</li>
|
||||
<li>Gateway/Auth: preserve shared-token <code>gateway token mismatch</code> auth errors when <code>auth.token</code> fallback device-token checks fail, and reserve <code>device token mismatch</code> guidance for explicit <code>auth.deviceToken</code> failures.</li>
|
||||
<li>Gateway/Tools: when agent tools pass an allowlisted <code>gatewayUrl</code> override, resolve local override tokens from env/config fallback but keep remote overrides strict to <code>gateway.remote.token</code>, preventing local token leakage to remote targets.</li>
|
||||
<li>Gateway/Client: keep cached device-auth tokens on <code>device token mismatch</code> closes when the client used explicit shared token/password credentials, avoiding accidental pairing-token churn during explicit-auth failures.</li>
|
||||
<li>Node host/Exec: keep strict Windows allowlist behavior for <code>cmd.exe /c</code> shell-wrapper runs, and return explicit approval guidance when blocked (<code>SYSTEM_RUN_DENIED: allowlist miss</code>).</li>
|
||||
<li>Control UI: show pairing-required guidance (commands + mobile tokenized URL reminder) when the dashboard disconnects with <code>1008 pairing required</code>.</li>
|
||||
<li>Security/Audit: add <code>openclaw security audit</code> detection for open group policies that expose runtime/filesystem tools without sandbox/workspace guards (<code>security.exposure.open_groups_with_runtime_or_fs</code>).</li>
|
||||
<li>Security/Audit: make <code>gateway.real_ip_fallback_enabled</code> severity conditional for loopback trusted-proxy setups (warn for loopback-only <code>trustedProxies</code>, critical when non-loopback proxies are trusted). (#23428) Thanks @bmendonca3.</li>
|
||||
<li>Security/Exec env: block request-scoped <code>HOME</code> and <code>ZDOTDIR</code> overrides in host exec env sanitizers (Node + macOS), preventing shell startup-file execution before allowlist-evaluated command bodies. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Exec env: block <code>SHELLOPTS</code>/<code>PS4</code> in host exec env sanitizers and restrict shell-wrapper (<code>bash|sh|zsh ... -c/-lc</code>) request env overrides to a small explicit allowlist (<code>TERM</code>, <code>LANG</code>, <code>LC_*</code>, <code>COLORTERM</code>, <code>NO_COLOR</code>, <code>FORCE_COLOR</code>) on both node host and macOS companion paths, preventing xtrace prompt command-substitution allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>WhatsApp/Security: enforce <code>allowFrom</code> for direct-message outbound targets in all send modes (including <code>mode: "explicit"</code>), preventing sends to non-allowlisted numbers. (#20108) Thanks @zahlmann.</li>
|
||||
<li>Security/Exec approvals: fail closed on shell line continuations (<code>\\\n</code>/<code>\\\r\n</code>) and treat shell-wrapper execution as approval-required in allowlist mode, preventing <code>$\\</code> newline command-substitution bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Gateway: emit a startup security warning when insecure/dangerous config flags are enabled (including <code>gateway.controlUi.dangerouslyDisableDeviceAuth=true</code>) and point operators to <code>openclaw security audit</code>.</li>
|
||||
<li>Security/Hooks auth: normalize hook auth rate-limit client IP keys so IPv4 and IPv4-mapped IPv6 addresses share one throttle bucket, preventing dual-form auth-attempt budget bypasses. This ships in the next npm release. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>Security/Exec approvals: treat <code>env</code> and shell-dispatch wrappers as transparent during allowlist analysis on node-host and macOS companion paths so policy checks match the effective executable/inline shell payload instead of the wrapper binary, blocking wrapper-smuggled allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Exec approvals: require explicit safe-bin profiles for <code>tools.exec.safeBins</code> entries in allowlist mode (remove generic safe-bin profile fallback), and add <code>tools.exec.safeBinProfiles</code> for safe custom binaries so unprofiled interpreter-style entries cannot be treated as stdin-safe. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Channels: harden Slack external menu token handling by switching to CSPRNG tokens, validating token shape, requiring user identity for external option lookups, and avoiding fabricated timestamp <code>trigger_id</code> fallbacks; also switch Tlon Urbit channel IDs to CSPRNG UUIDs, centralize secure ID/token generation via shared infra helpers, and add a guardrail test to block new runtime <code>Date.now()+Math.random()</code> token/id patterns.</li>
|
||||
<li>Security/Hooks transforms: enforce symlink-safe containment for webhook transform module paths (including <code>hooks.transformsDir</code> and <code>hooks.mappings[].transform.module</code>) by resolving existing-path ancestors via realpath before import, while preserving in-root symlink support; add regression coverage for both escape and allow cases. This ships in the next npm release. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>Telegram/WSL2: disable <code>autoSelectFamily</code> by default on WSL2 and memoize WSL2 detection in Telegram network decision logic to avoid repeated sync <code>/proc/version</code> probes on fetch/send paths. (#21916) Thanks @MizukiMachine.</li>
|
||||
<li>Telegram/Network: default Node 22+ DNS result ordering to <code>ipv4first</code> for Telegram fetch paths and add <code>OPENCLAW_TELEGRAM_DNS_RESULT_ORDER</code>/<code>channels.telegram.network.dnsResultOrder</code> overrides to reduce IPv6-path fetch failures. (#5405) Thanks @Glucksberg.</li>
|
||||
<li>Telegram/Forward bursts: coalesce forwarded text+media updates through a dedicated forward lane debounce window that works with default inbound debounce config, while keeping forwarded control commands immediate. (#19476) thanks @napetrov.</li>
|
||||
<li>Telegram/Streaming: preserve archived draft preview mapping after flush and clean superseded reasoning preview bubbles so multi-message preview finals no longer cross-edit or orphan stale messages under send/rotation races. (#23202) Thanks @obviyus.</li>
|
||||
<li>Telegram/Replies: scope messaging-tool text/media dedupe to same-target sends only, so cross-target tool sends can no longer silently suppress Telegram final replies.</li>
|
||||
<li>Telegram/Replies: normalize <code>file://</code> and local-path media variants during messaging dedupe so equivalent media paths do not produce duplicate Telegram replies.</li>
|
||||
<li>Telegram/Replies: extract forwarded-origin context from unified reply targets (<code>reply_to_message</code> and <code>external_reply</code>) so forward+comment metadata is preserved across partial reply shapes. (#9720) thanks @mcaxtr.</li>
|
||||
<li>Telegram/Polling: persist a safe update-offset watermark bounded by pending updates so crash/restart cannot skip queued lower <code>update_id</code> updates after out-of-order completion. (#23284) thanks @frankekn.</li>
|
||||
<li>Telegram/Polling: force-restart stuck runner instances when recoverable unhandled network rejections escape the polling task path, so polling resumes instead of silently stalling. (#19721) Thanks @jg-noncelogic.</li>
|
||||
<li>Slack/Slash commands: preserve the Bolt app receiver when registering external select options handlers so monitor startup does not crash on runtimes that require bound <code>app.options</code> calls. (#23209) Thanks @0xgaia.</li>
|
||||
<li>Slack/Telegram slash sessions: await session metadata persistence before dispatch so first-turn native slash runs do not race session-origin metadata updates. (#23065) thanks @hydro13.</li>
|
||||
<li>Slack/Queue routing: preserve string <code>thread_ts</code> values through collect-mode queue drain and DM <code>deliveryContext</code> updates so threaded follow-ups do not leak to the main channel when Slack thread IDs are strings. (#11934) Thanks @sandieman2 and @vincentkoc.</li>
|
||||
<li>Telegram/Native commands: set <code>ctx.Provider="telegram"</code> for native slash-command context so elevated gate checks resolve provider correctly (fixes <code>provider (ctx.Provider)</code> failures in <code>/elevated</code> flows). (#23748) Thanks @serhii12.</li>
|
||||
<li>Agents/Ollama: preserve unsafe integer tool-call arguments as exact strings during NDJSON parsing, preventing large numeric IDs from being rounded before tool execution. (#23170) Thanks @BestJoester.</li>
|
||||
<li>Cron/Gateway: keep <code>cron.list</code> and <code>cron.status</code> responsive during startup catch-up by avoiding a long-held cron lock while missed jobs execute. (#23106) Thanks @jayleekr.</li>
|
||||
<li>Gateway/Config reload: compare array-valued config paths structurally during diffing so unchanged <code>memory.qmd.paths</code> and <code>memory.qmd.scope.rules</code> no longer trigger false restart-required reloads. (#23185) Thanks @rex05ai.</li>
|
||||
<li>Gateway/Config reload: retry short-lived missing config snapshots during reload before skipping, preventing atomic-write unlink windows from triggering restart loops. (#23343) Thanks @lbo728.</li>
|
||||
<li>Cron/Scheduling: validate runtime cron expressions before schedule/stagger evaluation so malformed persisted jobs report a clear <code>invalid cron schedule: expr is required</code> error instead of crashing with <code>undefined.trim</code> failures and auto-disable churn. (#23223) Thanks @asimons81.</li>
|
||||
<li>Memory/QMD: migrate legacy unscoped collection bindings (for example <code>memory-root</code>) to per-agent scoped names (for example <code>memory-root-main</code>) during startup when safe, so QMD-backed <code>memory_search</code> no longer fails with <code>Collection not found</code> after upgrades. (#23228, #20727) Thanks @JLDynamics and @AaronFaby.</li>
|
||||
<li>Memory/QMD: normalize Han-script BM25 search queries before invoking <code>qmd search</code> so mixed CJK+Latin prompts no longer return empty results due to tokenizer mismatch. (#23426) Thanks @LunaLee0130.</li>
|
||||
<li>TUI/Input: enable multiline-paste burst coalescing on macOS Terminal.app and iTerm so pasted blocks no longer submit line-by-line as separate messages. (#18809) Thanks @fwends.</li>
|
||||
<li>TUI/RTL: isolate right-to-left script lines (Arabic/Hebrew ranges) with Unicode bidi isolation marks in TUI text sanitization so RTL assistant output no longer renders in reversed visual order in terminal chat panes. (#21936) Thanks @Asm3r96.</li>
|
||||
<li>TUI/Status: request immediate renders after setting <code>sending</code>/<code>waiting</code> activity states so in-flight runs always show visible progress indicators instead of appearing idle until completion. (#21549) Thanks @13Guinness.</li>
|
||||
<li>TUI/Input: arm Ctrl+C exit timing when clearing non-empty composer text and add a SIGINT fallback path so double Ctrl+C exits remain responsive during active runs instead of requiring an extra press or appearing stuck. (#23407) Thanks @tinybluedev.</li>
|
||||
<li>Agents/Fallbacks: treat JSON payloads with <code>type: "api_error"</code> + <code>"Internal server error"</code> as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.</li>
|
||||
<li>Agents/Google: sanitize non-base64 <code>thought_signature</code>/<code>thoughtSignature</code> values from assistant replay transcripts for native Google Gemini requests while preserving valid signatures and tool-call order. (#23457) Thanks @echoVic.</li>
|
||||
<li>Agents/Transcripts: validate assistant tool-call names (syntax/length + registered tool allowlist) before transcript persistence and during replay sanitization so malformed failover tool names no longer poison sessions with repeated provider HTTP 400 errors. (#23324) Thanks @johnsantry.</li>
|
||||
<li>Agents/Mistral: sanitize tool-call IDs in the embedded agent loop and generate strict provider-safe pending tool-call IDs, preventing Mistral strict9 <code>HTTP 400</code> failures on tool continuations. (#23698) Thanks @echoVic.</li>
|
||||
<li>Agents/Compaction: strip stale assistant usage snapshots from pre-compaction turns when replaying history after a compaction summary so context-token estimation no longer reuses pre-compaction totals and immediately re-triggers destructive follow-up compactions. (#19127) Thanks @tedwatson.</li>
|
||||
<li>Agents/Replies: emit a default completion acknowledgement (<code>✅ Done.</code>) only for direct/private tool-only completions with no final assistant text, while suppressing synthetic acknowledgements for channel/group sessions and runs that already delivered output via messaging tools. (#22834) Thanks @Oldshue.</li>
|
||||
<li>Agents/Subagents: honor <code>tools.subagents.tools.alsoAllow</code> and explicit subagent <code>allow</code> entries when resolving built-in subagent deny defaults, so explicitly granted tools (for example <code>sessions_send</code>) are no longer blocked unless re-denied in <code>tools.subagents.tools.deny</code>. (#23359) Thanks @goren-beehero.</li>
|
||||
<li>Agents/Subagents: make announce call timeouts configurable via <code>agents.defaults.subagents.announceTimeoutMs</code> and restore a 60s default to prevent false timeout failures on slower announce paths. (#22719) Thanks @Valadon.</li>
|
||||
<li>Agents/Diagnostics: include resolved lifecycle error text in <code>embedded run agent end</code> warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.</li>
|
||||
<li>Agents/Auth profiles: skip auth-profile cooldown writes for timeout failures in embedded runner rotation so model/network timeouts do not poison same-provider fallback model selection while still allowing in-turn account rotation. (#22622) Thanks @vageeshkumar.</li>
|
||||
<li>Plugins/Hooks: run legacy <code>before_agent_start</code> once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710.</li>
|
||||
<li>Models/Config: default missing Anthropic provider/model <code>api</code> fields to <code>anthropic-messages</code> during config validation so custom relay model entries are preserved instead of being dropped by runtime model registry validation. (#23332) Thanks @bigbigmonkey123.</li>
|
||||
<li>Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit <code>scopes</code>, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.</li>
|
||||
<li>Memory/QMD: add optional <code>memory.qmd.mcporter</code> search routing so QMD <code>query/search/vsearch</code> can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.</li>
|
||||
<li>Infra/Network: classify undici <code>TypeError: fetch failed</code> as transient in unhandled-rejection detection even when nested causes are unclassified, preventing avoidable gateway crash loops on flaky networks. (#14345) Thanks @Unayung.</li>
|
||||
<li>Telegram/Retry: classify undici <code>TypeError: fetch failed</code> as recoverable in both polling and send retry paths so transient fetch failures no longer fail fast. (#16699) thanks @Glucksberg.</li>
|
||||
<li>Docs/Telegram: correct Node 22+ network defaults (<code>autoSelectFamily</code>, <code>dnsResultOrder</code>) and clarify Telegram setup does not use positional <code>openclaw channels login telegram</code>. (#23609) Thanks @ryanbastic.</li>
|
||||
<li>BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.</li>
|
||||
<li>BlueBubbles/Private API cache: treat unknown (<code>null</code>) private-API cache status as disabled for send/attachment/reply flows to avoid stale-cache 500s, and log a warning when reply/effect features are requested while capability is unknown. (#23459) Thanks @echoVic.</li>
|
||||
<li>BlueBubbles/Webhooks: accept inbound/reaction webhook payloads when BlueBubbles omits <code>handle</code> but provides DM <code>chatGuid</code>, and harden payload extraction for array/string-wrapped message bodies so valid webhook events no longer get rejected as unparseable. (#23275) Thanks @toph31.</li>
|
||||
<li>Security/Audit: add <code>openclaw security audit</code> finding <code>gateway.nodes.allow_commands_dangerous</code> for risky <code>gateway.nodes.allowCommands</code> overrides, with severity upgraded to critical on remote gateway exposure.</li>
|
||||
<li>Gateway/Control plane: reduce cross-client write limiter contention by adding <code>connId</code> fallback keying when device ID and client IP are both unavailable.</li>
|
||||
<li>Security/Config: block prototype-key traversal during config merge patch and legacy migration merge helpers (<code>__proto__</code>, <code>constructor</code>, <code>prototype</code>) to prevent prototype pollution during config mutation flows. (#22968) Thanks @Clawborn.</li>
|
||||
<li>Security/Shell env: validate login-shell executable paths for shell-env fallback (<code>/etc/shells</code> + trusted prefixes), block <code>SHELL</code>/<code>HOME</code>/<code>ZDOTDIR</code> in config env ingestion before fallback execution, and sanitize fallback shell exec env to pin <code>HOME</code> to the real user home while dropping <code>ZDOTDIR</code> and other dangerous startup vars. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Network/SSRF: enable <code>autoSelectFamily</code> on pinned undici dispatchers (with attempt timeout) so IPv6-unreachable environments can quickly fall back to IPv4 for guarded fetch paths. (#19950) Thanks @ENAwareness.</li>
|
||||
<li>Security/Config: make parsed chat allowlist checks fail closed when <code>allowFrom</code> is empty, restoring expected DM/pairing gating.</li>
|
||||
<li>Security/Exec: in non-default setups that manually add <code>sort</code> to <code>tools.exec.safeBins</code>, block <code>sort --compress-program</code> so allowlist-mode safe-bin checks cannot bypass approval. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Exec approvals: when users choose <code>allow-always</code> for shell-wrapper commands (for example <code>/bin/zsh -lc ...</code>), persist allowlist patterns for the inner executable(s) instead of the wrapper shell binary, preventing accidental broad shell allowlisting in moderate mode. (#23276) Thanks @xrom2863.</li>
|
||||
<li>Security/Exec: fail closed when <code>tools.exec.host=sandbox</code> is configured/requested but sandbox runtime is unavailable. (#23398) Thanks @bmendonca3.</li>
|
||||
<li>Security/macOS app beta: enforce path-only <code>system.run</code> allowlist matching (drop basename matches like <code>echo</code>), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Agents: auto-generate and persist a dedicated <code>commands.ownerDisplaySecret</code> when <code>commands.ownerDisplay=hash</code>, remove gateway token fallback from owner-ID prompt hashing across CLI and embedded agent runners, and centralize owner-display secret resolution in one shared helper. This ships in the next npm release. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), centralize range checks into a single CIDR policy table, and reuse one shared host/IP classifier across literal + DNS checks to reduce classifier drift. This ships in the next npm release. Thanks @princeeismond-dot for reporting.</li>
|
||||
<li>Security/SSRF: block RFC2544 benchmarking range (<code>198.18.0.0/15</code>) across direct and embedded-IP paths, and normalize IPv6 dotted-quad transition literals (for example <code>::127.0.0.1</code>, <code>64:ff9b::8.8.8.8</code>) in shared IP parsing/classification.</li>
|
||||
<li>Security/Archive: block zip symlink escapes during archive extraction.</li>
|
||||
<li>Security/Media sandbox: keep tmp media allowance for absolute tmp paths only and enforce symlink-escape checks before sandbox-validated reads, preventing tmp symlink exfiltration and relative <code>../</code> sandbox escapes when sandboxes live under tmp. (#17892) Thanks @dashed.</li>
|
||||
<li>Browser/Upload: accept canonical in-root upload paths when the configured uploads directory is a symlink alias (for example <code>/tmp</code> -> <code>/private/tmp</code> on macOS), so browser upload validation no longer rejects valid files during client->server revalidation. (#23300, #23222, #22848) Thanks @bgaither4, @parkerati, and @Nabsku.</li>
|
||||
<li>Security/Discord: add <code>openclaw security audit</code> warnings for name/tag-based Discord allowlist entries (DM allowlists, guild/channel <code>users</code>, and pairing-store entries), highlighting slug-collision risk while keeping name-based matching supported, and canonicalize resolved Discord allowlist names to IDs at runtime without rewriting config files. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Gateway: block node-role connections when device identity metadata is missing.</li>
|
||||
<li>Security/Media: enforce inbound media byte limits during download/read across Discord, Telegram, Zalo, Microsoft Teams, and BlueBubbles to prevent oversized payload memory spikes before rejection. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Media/Understanding: preserve <code>application/pdf</code> MIME classification during text-like file heuristics so PDF uploads use PDF extraction paths instead of being inlined as raw text. (#23191) Thanks @claudeplay2026-byte.</li>
|
||||
<li>Security/Control UI: block symlink-based out-of-root static file reads by enforcing realpath containment and file-identity checks when serving Control UI assets and SPA fallback <code>index.html</code>. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Gateway avatars: block symlink traversal during local avatar <code>data:</code> URL resolution by enforcing realpath containment and file-identity checks before reads. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Control UI: centralize avatar URL/path validation across gateway/config helpers and enforce a 2 MB max size for local agent avatar files before <code>/avatar</code> resolution, reducing oversized-avatar memory risk without changing supported avatar formats.</li>
|
||||
<li>Security/Control UI avatars: harden <code>/avatar/:agentId</code> local avatar serving by rejecting symlink paths and requiring fd-level file identity + size checks before reads. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/MSTeams media: enforce allowlist checks for SharePoint reference attachment URLs and redirect targets during Graph-backed media fetches so redirect chains cannot escape configured media host boundaries. This ships in the next npm release. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/MSTeams media: route attachment auth-retry and Graph SharePoint download redirects through shared <code>safeFetch</code> so each hop is validated with allowlist + DNS/IP checks across the full redirect chain. (#23598) Thanks @Asm3r96 and @lewiswigmore.</li>
|
||||
<li>Security/macOS discovery: fail closed for unresolved discovery endpoints by clearing stale remote selection values, use resolved service host only for SSH target derivation, and keep remote URL config aligned with resolved endpoint availability. (#21618) Thanks @bmendonca3.</li>
|
||||
<li>Chat/Usage/TUI: strip synthetic inbound metadata blocks (including <code>Conversation info</code> and trailing <code>Untrusted context</code> channel metadata wrappers) from displayed conversation history so internal prompt context no longer leaks into user-visible logs.</li>
|
||||
<li>CI/Tests: fix TypeScript case-table typing and lint assertion regressions so <code>pnpm check</code> passes again after Synology Chat landing. (#23012) Thanks @druide67.</li>
|
||||
<li>Security/Browser relay: harden extension relay auth token handling for <code>/extension</code> and <code>/cdp</code> pathways.</li>
|
||||
<li>Cron: persist <code>delivered</code> state in cron job records so delivery failures remain visible in status and logs. (#19174) Thanks @simonemacario.</li>
|
||||
<li>Config/Doctor: only repair the OAuth credentials directory when affected channels are configured, avoiding fresh-install noise.</li>
|
||||
<li>Config/Channels: whitelist <code>channels.modelByChannel</code> in config validation and exclude it from plugin auto-enable channel detection so model overrides no longer trigger <code>unknown channel id</code> validation errors or bogus <code>modelByChannel</code> plugin enables. (#23412) Thanks @ProspectOre.</li>
|
||||
<li>Config/Bindings: allow optional <code>bindings[].comment</code> in strict config validation so annotated binding entries no longer fail load. (#23458) Thanks @echoVic.</li>
|
||||
<li>Usage/Pricing: correct MiniMax M2.5 pricing defaults to fix inflated cost reporting. (#22755) Thanks @miloudbelarebia.</li>
|
||||
<li>Gateway/Daemon: verify gateway health after daemon restart.</li>
|
||||
<li>Agents/UI text: stop rewriting normal assistant billing/payment language outside explicit error contexts. (#17834) Thanks @niceysam.</li>
|
||||
<li>Agents/Subagents: restore announce-chain delivery to agent injection, defer nested announce output until descendant follow-up content is ready, and prevent descendant deferrals from consuming announce retry budget so deep chains do not drop final completions. (#22223) Thanks @tyler6204.</li>
|
||||
<li>Agents/System Prompt: label allowlisted senders as authorized senders to avoid implying ownership. Thanks @thewilloftheshadow.</li>
|
||||
<li>Agents/Tool display: fix exec cwd suffix inference so <code>pushd ... && popd ... && <command></code> does not keep stale <code>(in <dir>)</code> context in summaries. (#21925) Thanks @Lukavyi.</li>
|
||||
<li>Tools/web_search: handle xAI Responses API payloads that emit top-level <code>output_text</code> blocks (without a <code>message</code> wrapper) so Grok web_search no longer returns <code>No response</code> for those results. (#20508) Thanks @echoVic.</li>
|
||||
<li>Agents/Failover: treat non-default override runs as direct fallback-to-configured-primary (skip configured fallback chain), normalize default-model detection for provider casing/whitespace, and add regression coverage for override/auth error paths. (#18820) Thanks @Glucksberg.</li>
|
||||
<li>Docker/Build: include <code>ownerDisplay</code> in <code>CommandsSchema</code> object-level defaults so Docker <code>pnpm build</code> no longer fails with <code>TS2769</code> during plugin SDK d.ts generation. (#22558) Thanks @obviyus.</li>
|
||||
<li>Docker/Browser: install Playwright Chromium into <code>/home/node/.cache/ms-playwright</code> and set <code>node:node</code> ownership so browser binaries are available to the runtime user in browser-enabled images. (#22585) thanks @obviyus.</li>
|
||||
<li>Hooks/Session memory: trigger bundled <code>session-memory</code> persistence on both <code>/new</code> and <code>/reset</code> so reset flows no longer skip markdown transcript capture before archival. (#21382) Thanks @mofesolapaul.</li>
|
||||
<li>Dependencies/Agents: bump embedded Pi SDK packages (<code>@mariozechner/pi-agent-core</code>, <code>@mariozechner/pi-ai</code>, <code>@mariozechner/pi-coding-agent</code>, <code>@mariozechner/pi-tui</code>) to <code>0.54.0</code>. (#21578) Thanks @Takhoffman.</li>
|
||||
<li>Config/Agents: expose Pi compaction tuning values <code>agents.defaults.compaction.reserveTokens</code> and <code>agents.defaults.compaction.keepRecentTokens</code> in config schema/types and apply them in embedded Pi runner settings overrides with floor enforcement via <code>reserveTokensFloor</code>. (#21568) Thanks @Takhoffman.</li>
|
||||
<li>Docker: pin base images to SHA256 digests in Docker builds to prevent mutable tag drift. (#7734) Thanks @coygeek.</li>
|
||||
<li>Docker: run build steps as the <code>node</code> user and use <code>COPY --chown</code> to avoid recursive ownership changes, trimming image size and layer churn. Thanks @huntharo.</li>
|
||||
<li>Config/Memory: restore schema help/label metadata for hybrid <code>mmr</code> and <code>temporalDecay</code> settings so configuration surfaces show correct names and guidance. (#18786) Thanks @rodrigouroz.</li>
|
||||
<li>Skills/SonosCLI: add troubleshooting guidance for <code>sonos discover</code> failures on macOS direct mode (<code>sendto: no route to host</code>) and sandbox network restrictions (<code>bind: operation not permitted</code>). (#21316) Thanks @huntharo.</li>
|
||||
<li>macOS/Build: default release packaging to <code>BUNDLE_ID=ai.openclaw.mac</code> in <code>scripts/package-mac-dist.sh</code>, so Sparkle feed URL is retained and auto-update no longer fails with an empty appcast feed. (#19750) thanks @loganprit.</li>
|
||||
<li>Signal/Outbound: preserve case for Base64 group IDs during outbound target normalization so cross-context routing and policy checks no longer break when group IDs include uppercase characters. (#5578) Thanks @heyhudson.</li>
|
||||
<li>Anthropic/Agents: preserve required pi-ai default OAuth beta headers when <code>context1m</code> injects <code>anthropic-beta</code>, preventing 401 auth failures for <code>sk-ant-oat-*</code> tokens. (#19789, fixes #19769) Thanks @minupla.</li>
|
||||
<li>Security/Exec: block unquoted heredoc body expansion tokens in shell allowlist analysis, reject unterminated heredocs, and require explicit approval for allowlisted heredoc execution on gateway hosts to prevent heredoc substitution allowlist bypass. Thanks @torturado for reporting.</li>
|
||||
<li>macOS/Security: evaluate <code>system.run</code> allowlists per shell segment in macOS node runtime and companion exec host (including chained shell operators), fail closed on shell/process substitution parsing, and require explicit approval on unsafe parse cases to prevent allowlist bypass via <code>rawCommand</code> chaining. Thanks @tdjackey for reporting.</li>
|
||||
<li>WhatsApp/Security: enforce allowlist JID authorization for reaction actions so authenticated callers cannot target non-allowlisted chats by forging <code>chatJid</code> + valid <code>messageId</code> pairs. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>ACP/Security: escape control and delimiter characters in ACP <code>resource_link</code> title/URI metadata before prompt interpolation to prevent metadata-driven prompt injection through resource links. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>TTS/Security: make model-driven provider switching opt-in by default (<code>messages.tts.modelOverrides.allowProvider=false</code> unless explicitly enabled), while keeping voice/style overrides available, to reduce prompt-injection-driven provider hops and unexpected TTS cost escalation. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>Security/Agents: keep overflow compaction retry budgeting global across tool-result truncation recovery so successful truncation cannot reset the overflow retry counter and amplify retry/cost cycles. Thanks @aether-ai-agent for reporting.</li>
|
||||
<li>BlueBubbles/Security: require webhook token authentication for all BlueBubbles webhook requests (including loopback/proxied setups), removing passwordless webhook fallback behavior. Thanks @zpbrent.</li>
|
||||
<li>iOS/Security: force <code>https://</code> for non-loopback manual gateway hosts during iOS onboarding to block insecure remote transport URLs. (#21969) Thanks @mbelinky.</li>
|
||||
<li>Gateway/Security: remove shared-IP fallback for canvas endpoints and require token or session capability for canvas access. Thanks @thewilloftheshadow.</li>
|
||||
<li>Gateway/Security: require secure context and paired-device checks for Control UI auth even when <code>gateway.controlUi.allowInsecureAuth</code> is set, and align audit messaging with the hardened behavior. (#20684) Thanks @coygeek and @Vasco0x4 for reporting.</li>
|
||||
<li>Gateway/Security: scope tokenless Tailscale forwarded-header auth to Control UI websocket auth only, so HTTP gateway routes still require token/password even on trusted hosts. Thanks @zpbrent for reporting.</li>
|
||||
<li>Docker/Security: run E2E and install-sh test images as non-root by adding appuser directives. Thanks @thewilloftheshadow.</li>
|
||||
<li>Skills/Security: sanitize skill env overrides to block unsafe runtime injection variables and only allow sensitive keys when declared in skill metadata, with warnings for suspicious values. Thanks @thewilloftheshadow.</li>
|
||||
<li>Security/Commands: block prototype-key injection in runtime <code>/debug</code> overrides and require own-property checks for gated command flags (<code>bash</code>, <code>config</code>, <code>debug</code>) so inherited prototype values cannot enable privileged commands. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Browser: block non-network browser navigation protocols (including <code>file:</code>, <code>data:</code>, and <code>javascript:</code>) while preserving <code>about:blank</code>, preventing local file reads via browser tool navigation. Thanks @q1uf3ng for reporting.</li>
|
||||
<li>Security/Exec: block shell startup-file env injection (<code>BASH_ENV</code>, <code>ENV</code>, <code>BASH_FUNC_*</code>, <code>LD_*</code>, <code>DYLD_*</code>) across config env ingestion, node-host inherited environment sanitization, and macOS exec host runtime to prevent pre-command execution from attacker-controlled environment variables. Thanks @tdjackey.</li>
|
||||
<li>Security/Exec (Windows): canonicalize <code>cmd.exe /c</code> command text across validation, approval binding, and audit/event rendering to prevent trailing-argument approval mismatches in <code>system.run</code>. Thanks @tdjackey for reporting.</li>
|
||||
<li>Security/Gateway/Hooks: block <code>__proto__</code>, <code>constructor</code>, and <code>prototype</code> traversal in webhook template path resolution to prevent prototype-chain payload data leakage in <code>messageTemplate</code> rendering. (#22213) Thanks @SleuthCo.</li>
|
||||
<li>Security/OpenClawKit/UI: prevent injected inbound user context metadata blocks from leaking into chat history in TUI, webchat, and macOS surfaces by stripping all untrusted metadata prefixes at display boundaries. (#22142) Thanks @Mellowambience, @vincentkoc.</li>
|
||||
<li>Security/OpenClawKit/UI: strip inbound metadata blocks from user messages in TUI rendering while preserving user-authored content. (#22345) Thanks @kansodata, @vincentkoc.</li>
|
||||
<li>Security/OpenClawKit/UI: prevent inbound metadata leaks and reply-tag streaming artifacts in TUI rendering by stripping untrusted metadata prefixes at display boundaries. (#22346) Thanks @akramcodez, @vincentkoc.</li>
|
||||
<li>Security/Agents: restrict local MEDIA tool attachments to core tools and the OpenClaw temp root to prevent untrusted MCP tool file exfiltration. Thanks @NucleiAv and @thewilloftheshadow.</li>
|
||||
<li>Security/Net: strip sensitive headers (<code>Authorization</code>, <code>Proxy-Authorization</code>, <code>Cookie</code>, <code>Cookie2</code>) on cross-origin redirects in <code>fetchWithSsrFGuard</code> to prevent credential forwarding across origin boundaries. (#20313) Thanks @afurm.</li>
|
||||
<li>Security/Systemd: reject CR/LF in systemd unit environment values and fix argument escaping so generated units cannot be injected with extra directives. Thanks @thewilloftheshadow.</li>
|
||||
<li>Security/Tools: add per-wrapper random IDs to untrusted-content markers from <code>wrapExternalContent</code>/<code>wrapWebContent</code>, preventing marker spoofing from escaping content boundaries. (#19009) Thanks @Whoaa512.</li>
|
||||
<li>Shared/Security: reject insecure deep links that use <code>ws://</code> non-loopback gateway URLs to prevent plaintext remote websocket configuration. (#21970) Thanks @mbelinky.</li>
|
||||
<li>macOS/Security: reject non-loopback <code>ws://</code> remote gateway URLs in macOS remote config to block insecure plaintext websocket endpoints. (#21971) Thanks @mbelinky.</li>
|
||||
<li>Browser/Security: block upload path symlink escapes so browser upload sources cannot traverse outside the allowed workspace via symlinked paths. (#21972) Thanks @mbelinky.</li>
|
||||
<li>Security/Dependencies: bump transitive <code>hono</code> usage to <code>4.11.10</code> to incorporate timing-safe authentication comparison hardening for <code>basicAuth</code>/<code>bearerAuth</code> (<code>GHSA-gq3j-xvxp-8hrf</code>). Thanks @vincentkoc.</li>
|
||||
<li>Security/Gateway: parse <code>X-Forwarded-For</code> with trust-preserving semantics when requests come from configured trusted proxies, preventing proxy-chain spoofing from influencing client IP classification and rate-limit identity. Thanks @AnthonyDiSanti and @vincentkoc.</li>
|
||||
<li>Security/Sandbox: remove default <code>--no-sandbox</code> for the browser container entrypoint, add explicit opt-in via <code>OPENCLAW_BROWSER_NO_SANDBOX</code> / <code>CLAWDBOT_BROWSER_NO_SANDBOX</code>, and add security-audit checks for stale/missing sandbox browser Docker hash labels. Thanks @TerminalsandCoffee and @vincentkoc.</li>
|
||||
<li>Security/Sandbox Browser: require VNC password auth for noVNC observer sessions in the sandbox browser entrypoint, plumb per-container noVNC passwords from runtime, and emit short-lived noVNC observer token URLs while keeping loopback-only host port publishing. Thanks @TerminalsandCoffee for reporting.</li>
|
||||
<li>Security/Sandbox Browser: default browser sandbox containers to a dedicated Docker network (<code>openclaw-sandbox-browser</code>), add optional CDP ingress source-range restrictions, auto-create missing dedicated networks, and warn in <code>openclaw security --audit</code> when browser sandboxing runs on bridge without source-range limits. Thanks @TerminalsandCoffee for reporting.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.22-beta.1/OpenClaw-2026.2.22.zip" length="23096856" type="application/octet-stream" sparkle:edSignature="aoVaCQPj9ajiSD+OjMZdUOyNzACFlMxU7m4ns+4LF1eWaizGLGHk4S0OPnHVQ+DAQY2DCHua+z4F0SMI6o01DA=="/>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.21/OpenClaw-2026.2.21.zip" length="23065599" type="application/octet-stream" sparkle:edSignature="Wg3P8rMvYO3uWoVR7Izxjm5hC5W0C5jCG2dR4WFSe8ULpUUU79YDJc99NMBnl8ym7ZVbelS3kZ0QSg0Wq2GhCw=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -21,8 +21,8 @@ android {
|
||||
applicationId = "ai.openclaw.android"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 202602230
|
||||
versionName = "2026.2.23"
|
||||
versionCode = 202602210
|
||||
versionName = "2026.2.21"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260223</string>
|
||||
<string>20260220</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
@@ -32,7 +32,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260223</string>
|
||||
<string>20260220</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260223</string>
|
||||
<string>20260220</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260223</string>
|
||||
<string>20260220</string>
|
||||
<key>WKCompanionAppBundleIdentifier</key>
|
||||
<string>$(OPENCLAW_APP_BUNDLE_ID)</string>
|
||||
<key>WKWatchKitApp</key>
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260223</string>
|
||||
<string>20260220</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
|
||||
@@ -92,8 +92,8 @@ targets:
|
||||
- CFBundleURLName: ai.openclaw.ios
|
||||
CFBundleURLSchemes:
|
||||
- openclaw
|
||||
CFBundleShortVersionString: "2026.2.23"
|
||||
CFBundleVersion: "20260223"
|
||||
CFBundleShortVersionString: "2026.2.21"
|
||||
CFBundleVersion: "20260220"
|
||||
UILaunchScreen: {}
|
||||
UIApplicationSceneManifest:
|
||||
UIApplicationSupportsMultipleScenes: false
|
||||
@@ -146,8 +146,8 @@ targets:
|
||||
path: ShareExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw Share
|
||||
CFBundleShortVersionString: "2026.2.23"
|
||||
CFBundleVersion: "20260223"
|
||||
CFBundleShortVersionString: "2026.2.21"
|
||||
CFBundleVersion: "20260220"
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.share-services
|
||||
NSExtensionPrincipalClass: "$(PRODUCT_MODULE_NAME).ShareViewController"
|
||||
@@ -176,8 +176,8 @@ targets:
|
||||
path: WatchApp/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw
|
||||
CFBundleShortVersionString: "2026.2.23"
|
||||
CFBundleVersion: "20260223"
|
||||
CFBundleShortVersionString: "2026.2.21"
|
||||
CFBundleVersion: "20260220"
|
||||
WKCompanionAppBundleIdentifier: "$(OPENCLAW_APP_BUNDLE_ID)"
|
||||
WKWatchKitApp: true
|
||||
|
||||
@@ -200,8 +200,8 @@ targets:
|
||||
path: WatchExtension/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClaw
|
||||
CFBundleShortVersionString: "2026.2.23"
|
||||
CFBundleVersion: "20260223"
|
||||
CFBundleShortVersionString: "2026.2.21"
|
||||
CFBundleVersion: "20260220"
|
||||
NSExtension:
|
||||
NSExtensionAttributes:
|
||||
WKAppBundleIdentifier: "$(OPENCLAW_WATCH_APP_BUNDLE_ID)"
|
||||
@@ -228,5 +228,5 @@ targets:
|
||||
path: Tests/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: OpenClawTests
|
||||
CFBundleShortVersionString: "2026.2.23"
|
||||
CFBundleVersion: "20260223"
|
||||
CFBundleShortVersionString: "2026.2.21"
|
||||
CFBundleVersion: "20260220"
|
||||
|
||||
@@ -28,8 +28,7 @@ enum ExecApprovalEvaluator {
|
||||
let approvals = ExecApprovalsStore.resolve(agentId: normalizedAgentId)
|
||||
let security = approvals.agent.security
|
||||
let ask = approvals.agent.ask
|
||||
let shellWrapper = ExecShellWrapperParser.extract(command: command, rawCommand: rawCommand).isWrapper
|
||||
let env = HostEnvSanitizer.sanitize(overrides: envOverrides, shellWrapper: shellWrapper)
|
||||
let env = HostEnvSanitizer.sanitize(overrides: envOverrides)
|
||||
let displayCommand = ExecCommandFormatter.displayString(for: command, rawCommand: rawCommand)
|
||||
let allowlistResolutions = ExecCommandResolution.resolveForAllowlist(
|
||||
command: command,
|
||||
|
||||
@@ -15,8 +15,6 @@ enum HostEnvSanitizer {
|
||||
"BASH_ENV",
|
||||
"ENV",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
"PS4",
|
||||
"GCONV_PATH",
|
||||
"IFS",
|
||||
"SSLKEYLOGFILE",
|
||||
@@ -31,36 +29,13 @@ enum HostEnvSanitizer {
|
||||
"HOME",
|
||||
"ZDOTDIR",
|
||||
]
|
||||
private static let shellWrapperAllowedOverrideKeys: Set<String> = [
|
||||
"TERM",
|
||||
"LANG",
|
||||
"LC_ALL",
|
||||
"LC_CTYPE",
|
||||
"LC_MESSAGES",
|
||||
"COLORTERM",
|
||||
"NO_COLOR",
|
||||
"FORCE_COLOR",
|
||||
]
|
||||
|
||||
private static func isBlocked(_ upperKey: String) -> Bool {
|
||||
if self.blockedKeys.contains(upperKey) { return true }
|
||||
return self.blockedPrefixes.contains(where: { upperKey.hasPrefix($0) })
|
||||
}
|
||||
|
||||
private static func filterOverridesForShellWrapper(_ overrides: [String: String]?) -> [String: String]? {
|
||||
guard let overrides else { return nil }
|
||||
var filtered: [String: String] = [:]
|
||||
for (rawKey, value) in overrides {
|
||||
let key = rawKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !key.isEmpty else { continue }
|
||||
if self.shellWrapperAllowedOverrideKeys.contains(key.uppercased()) {
|
||||
filtered[key] = value
|
||||
}
|
||||
}
|
||||
return filtered.isEmpty ? nil : filtered
|
||||
}
|
||||
|
||||
static func sanitize(overrides: [String: String]?, shellWrapper: Bool = false) -> [String: String] {
|
||||
static func sanitize(overrides: [String: String]?) -> [String: String] {
|
||||
var merged: [String: String] = [:]
|
||||
for (rawKey, value) in ProcessInfo.processInfo.environment {
|
||||
let key = rawKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -70,12 +45,8 @@ enum HostEnvSanitizer {
|
||||
merged[key] = value
|
||||
}
|
||||
|
||||
let effectiveOverrides = shellWrapper
|
||||
? self.filterOverridesForShellWrapper(overrides)
|
||||
: overrides
|
||||
|
||||
guard let effectiveOverrides else { return merged }
|
||||
for (rawKey, value) in effectiveOverrides {
|
||||
guard let overrides else { return merged }
|
||||
for (rawKey, value) in overrides {
|
||||
let key = rawKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !key.isEmpty else { continue }
|
||||
let upper = key.uppercased()
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.2.23</string>
|
||||
<string>2026.2.21</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>202602230</string>
|
||||
<string>202602210</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -15,7 +15,7 @@ struct ConnectOptions {
|
||||
var clientMode: String = "ui"
|
||||
var displayName: String?
|
||||
var role: String = "operator"
|
||||
var scopes: [String] = defaultOperatorConnectScopes
|
||||
var scopes: [String] = ["operator.admin", "operator.approvals", "operator.pairing"]
|
||||
var help: Bool = false
|
||||
|
||||
static func parse(_ args: [String]) -> ConnectOptions {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
let defaultOperatorConnectScopes: [String] = [
|
||||
"operator.admin",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
"operator.approvals",
|
||||
"operator.pairing",
|
||||
]
|
||||
@@ -251,7 +251,7 @@ actor GatewayWizardClient {
|
||||
let clientMode = "ui"
|
||||
let role = "operator"
|
||||
// Explicit scopes; gateway no longer defaults empty scopes to admin.
|
||||
let scopes = defaultOperatorConnectScopes
|
||||
let scopes: [String] = ["operator.admin", "operator.approvals", "operator.pairing"]
|
||||
let client: [String: ProtoAnyCodable] = [
|
||||
"id": ProtoAnyCodable(clientId),
|
||||
"displayName": ProtoAnyCodable(Host.current().localizedName ?? "OpenClaw macOS Wizard CLI"),
|
||||
|
||||
@@ -2306,39 +2306,15 @@ public struct CronJob: Codable, Sendable {
|
||||
|
||||
public struct CronListParams: Codable, Sendable {
|
||||
public let includedisabled: Bool?
|
||||
public let limit: Int?
|
||||
public let offset: Int?
|
||||
public let query: String?
|
||||
public let enabled: AnyCodable?
|
||||
public let sortby: AnyCodable?
|
||||
public let sortdir: AnyCodable?
|
||||
|
||||
public init(
|
||||
includedisabled: Bool?,
|
||||
limit: Int?,
|
||||
offset: Int?,
|
||||
query: String?,
|
||||
enabled: AnyCodable?,
|
||||
sortby: AnyCodable?,
|
||||
sortdir: AnyCodable?)
|
||||
includedisabled: Bool?)
|
||||
{
|
||||
self.includedisabled = includedisabled
|
||||
self.limit = limit
|
||||
self.offset = offset
|
||||
self.query = query
|
||||
self.enabled = enabled
|
||||
self.sortby = sortby
|
||||
self.sortdir = sortdir
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case includedisabled = "includeDisabled"
|
||||
case limit
|
||||
case offset
|
||||
case query
|
||||
case enabled
|
||||
case sortby = "sortBy"
|
||||
case sortdir = "sortDir"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2398,60 +2374,6 @@ public struct CronAddParams: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronRunsParams: Codable, Sendable {
|
||||
public let scope: AnyCodable?
|
||||
public let id: String?
|
||||
public let jobid: String?
|
||||
public let limit: Int?
|
||||
public let offset: Int?
|
||||
public let statuses: [AnyCodable]?
|
||||
public let status: AnyCodable?
|
||||
public let deliverystatuses: [AnyCodable]?
|
||||
public let deliverystatus: AnyCodable?
|
||||
public let query: String?
|
||||
public let sortdir: AnyCodable?
|
||||
|
||||
public init(
|
||||
scope: AnyCodable?,
|
||||
id: String?,
|
||||
jobid: String?,
|
||||
limit: Int?,
|
||||
offset: Int?,
|
||||
statuses: [AnyCodable]?,
|
||||
status: AnyCodable?,
|
||||
deliverystatuses: [AnyCodable]?,
|
||||
deliverystatus: AnyCodable?,
|
||||
query: String?,
|
||||
sortdir: AnyCodable?)
|
||||
{
|
||||
self.scope = scope
|
||||
self.id = id
|
||||
self.jobid = jobid
|
||||
self.limit = limit
|
||||
self.offset = offset
|
||||
self.statuses = statuses
|
||||
self.status = status
|
||||
self.deliverystatuses = deliverystatuses
|
||||
self.deliverystatus = deliverystatus
|
||||
self.query = query
|
||||
self.sortdir = sortdir
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case scope
|
||||
case id
|
||||
case jobid = "jobId"
|
||||
case limit
|
||||
case offset
|
||||
case statuses
|
||||
case status
|
||||
case deliverystatuses = "deliveryStatuses"
|
||||
case deliverystatus = "deliveryStatus"
|
||||
case query
|
||||
case sortdir = "sortDir"
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronRunLogEntry: Codable, Sendable {
|
||||
public let ts: Int
|
||||
public let jobid: String
|
||||
@@ -2459,18 +2381,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
public let status: AnyCodable?
|
||||
public let error: String?
|
||||
public let summary: String?
|
||||
public let delivered: Bool?
|
||||
public let deliverystatus: AnyCodable?
|
||||
public let deliveryerror: String?
|
||||
public let sessionid: String?
|
||||
public let sessionkey: String?
|
||||
public let runatms: Int?
|
||||
public let durationms: Int?
|
||||
public let nextrunatms: Int?
|
||||
public let model: String?
|
||||
public let provider: String?
|
||||
public let usage: [String: AnyCodable]?
|
||||
public let jobname: String?
|
||||
|
||||
public init(
|
||||
ts: Int,
|
||||
@@ -2479,18 +2394,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
status: AnyCodable?,
|
||||
error: String?,
|
||||
summary: String?,
|
||||
delivered: Bool?,
|
||||
deliverystatus: AnyCodable?,
|
||||
deliveryerror: String?,
|
||||
sessionid: String?,
|
||||
sessionkey: String?,
|
||||
runatms: Int?,
|
||||
durationms: Int?,
|
||||
nextrunatms: Int?,
|
||||
model: String?,
|
||||
provider: String?,
|
||||
usage: [String: AnyCodable]?,
|
||||
jobname: String?)
|
||||
nextrunatms: Int?)
|
||||
{
|
||||
self.ts = ts
|
||||
self.jobid = jobid
|
||||
@@ -2498,18 +2406,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
self.status = status
|
||||
self.error = error
|
||||
self.summary = summary
|
||||
self.delivered = delivered
|
||||
self.deliverystatus = deliverystatus
|
||||
self.deliveryerror = deliveryerror
|
||||
self.sessionid = sessionid
|
||||
self.sessionkey = sessionkey
|
||||
self.runatms = runatms
|
||||
self.durationms = durationms
|
||||
self.nextrunatms = nextrunatms
|
||||
self.model = model
|
||||
self.provider = provider
|
||||
self.usage = usage
|
||||
self.jobname = jobname
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
@@ -2519,18 +2420,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
case status
|
||||
case error
|
||||
case summary
|
||||
case delivered
|
||||
case deliverystatus = "deliveryStatus"
|
||||
case deliveryerror = "deliveryError"
|
||||
case sessionid = "sessionId"
|
||||
case sessionkey = "sessionKey"
|
||||
case runatms = "runAtMs"
|
||||
case durationms = "durationMs"
|
||||
case nextrunatms = "nextRunAtMs"
|
||||
case model
|
||||
case provider
|
||||
case usage
|
||||
case jobname = "jobName"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import Testing
|
||||
@testable import OpenClaw
|
||||
|
||||
struct HostEnvSanitizerTests {
|
||||
@Test func sanitizeBlocksShellTraceVariables() {
|
||||
let env = HostEnvSanitizer.sanitize(overrides: [
|
||||
"SHELLOPTS": "xtrace",
|
||||
"PS4": "$(touch /tmp/pwned)",
|
||||
"OPENCLAW_TEST": "1",
|
||||
])
|
||||
#expect(env["SHELLOPTS"] == nil)
|
||||
#expect(env["PS4"] == nil)
|
||||
#expect(env["OPENCLAW_TEST"] == "1")
|
||||
}
|
||||
|
||||
@Test func sanitizeShellWrapperAllowsOnlyExplicitOverrideKeys() {
|
||||
let env = HostEnvSanitizer.sanitize(
|
||||
overrides: [
|
||||
"LANG": "C",
|
||||
"LC_ALL": "C",
|
||||
"OPENCLAW_TOKEN": "secret",
|
||||
"PS4": "$(touch /tmp/pwned)",
|
||||
],
|
||||
shellWrapper: true)
|
||||
|
||||
#expect(env["LANG"] == "C")
|
||||
#expect(env["LC_ALL"] == "C")
|
||||
#expect(env["OPENCLAW_TOKEN"] == nil)
|
||||
#expect(env["PS4"] == nil)
|
||||
}
|
||||
|
||||
@Test func sanitizeNonShellWrapperKeepsRegularOverrides() {
|
||||
let env = HostEnvSanitizer.sanitize(overrides: ["OPENCLAW_TOKEN": "secret"])
|
||||
#expect(env["OPENCLAW_TOKEN"] == "secret")
|
||||
}
|
||||
}
|
||||
@@ -127,14 +127,6 @@ private enum ConnectChallengeError: Error {
|
||||
case timeout
|
||||
}
|
||||
|
||||
private let defaultOperatorConnectScopes: [String] = [
|
||||
"operator.admin",
|
||||
"operator.read",
|
||||
"operator.write",
|
||||
"operator.approvals",
|
||||
"operator.pairing",
|
||||
]
|
||||
|
||||
public actor GatewayChannelActor {
|
||||
private let logger = Logger(subsystem: "ai.openclaw", category: "gateway")
|
||||
private var task: WebSocketTaskBox?
|
||||
@@ -326,7 +318,7 @@ public actor GatewayChannelActor {
|
||||
let primaryLocale = Locale.preferredLanguages.first ?? Locale.current.identifier
|
||||
let options = self.connectOptions ?? GatewayConnectOptions(
|
||||
role: "operator",
|
||||
scopes: defaultOperatorConnectScopes,
|
||||
scopes: ["operator.admin", "operator.approvals", "operator.pairing"],
|
||||
caps: [],
|
||||
commands: [],
|
||||
permissions: [:],
|
||||
|
||||
@@ -2306,39 +2306,15 @@ public struct CronJob: Codable, Sendable {
|
||||
|
||||
public struct CronListParams: Codable, Sendable {
|
||||
public let includedisabled: Bool?
|
||||
public let limit: Int?
|
||||
public let offset: Int?
|
||||
public let query: String?
|
||||
public let enabled: AnyCodable?
|
||||
public let sortby: AnyCodable?
|
||||
public let sortdir: AnyCodable?
|
||||
|
||||
public init(
|
||||
includedisabled: Bool?,
|
||||
limit: Int?,
|
||||
offset: Int?,
|
||||
query: String?,
|
||||
enabled: AnyCodable?,
|
||||
sortby: AnyCodable?,
|
||||
sortdir: AnyCodable?)
|
||||
includedisabled: Bool?)
|
||||
{
|
||||
self.includedisabled = includedisabled
|
||||
self.limit = limit
|
||||
self.offset = offset
|
||||
self.query = query
|
||||
self.enabled = enabled
|
||||
self.sortby = sortby
|
||||
self.sortdir = sortdir
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case includedisabled = "includeDisabled"
|
||||
case limit
|
||||
case offset
|
||||
case query
|
||||
case enabled
|
||||
case sortby = "sortBy"
|
||||
case sortdir = "sortDir"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2398,60 +2374,6 @@ public struct CronAddParams: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronRunsParams: Codable, Sendable {
|
||||
public let scope: AnyCodable?
|
||||
public let id: String?
|
||||
public let jobid: String?
|
||||
public let limit: Int?
|
||||
public let offset: Int?
|
||||
public let statuses: [AnyCodable]?
|
||||
public let status: AnyCodable?
|
||||
public let deliverystatuses: [AnyCodable]?
|
||||
public let deliverystatus: AnyCodable?
|
||||
public let query: String?
|
||||
public let sortdir: AnyCodable?
|
||||
|
||||
public init(
|
||||
scope: AnyCodable?,
|
||||
id: String?,
|
||||
jobid: String?,
|
||||
limit: Int?,
|
||||
offset: Int?,
|
||||
statuses: [AnyCodable]?,
|
||||
status: AnyCodable?,
|
||||
deliverystatuses: [AnyCodable]?,
|
||||
deliverystatus: AnyCodable?,
|
||||
query: String?,
|
||||
sortdir: AnyCodable?)
|
||||
{
|
||||
self.scope = scope
|
||||
self.id = id
|
||||
self.jobid = jobid
|
||||
self.limit = limit
|
||||
self.offset = offset
|
||||
self.statuses = statuses
|
||||
self.status = status
|
||||
self.deliverystatuses = deliverystatuses
|
||||
self.deliverystatus = deliverystatus
|
||||
self.query = query
|
||||
self.sortdir = sortdir
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case scope
|
||||
case id
|
||||
case jobid = "jobId"
|
||||
case limit
|
||||
case offset
|
||||
case statuses
|
||||
case status
|
||||
case deliverystatuses = "deliveryStatuses"
|
||||
case deliverystatus = "deliveryStatus"
|
||||
case query
|
||||
case sortdir = "sortDir"
|
||||
}
|
||||
}
|
||||
|
||||
public struct CronRunLogEntry: Codable, Sendable {
|
||||
public let ts: Int
|
||||
public let jobid: String
|
||||
@@ -2459,18 +2381,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
public let status: AnyCodable?
|
||||
public let error: String?
|
||||
public let summary: String?
|
||||
public let delivered: Bool?
|
||||
public let deliverystatus: AnyCodable?
|
||||
public let deliveryerror: String?
|
||||
public let sessionid: String?
|
||||
public let sessionkey: String?
|
||||
public let runatms: Int?
|
||||
public let durationms: Int?
|
||||
public let nextrunatms: Int?
|
||||
public let model: String?
|
||||
public let provider: String?
|
||||
public let usage: [String: AnyCodable]?
|
||||
public let jobname: String?
|
||||
|
||||
public init(
|
||||
ts: Int,
|
||||
@@ -2479,18 +2394,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
status: AnyCodable?,
|
||||
error: String?,
|
||||
summary: String?,
|
||||
delivered: Bool?,
|
||||
deliverystatus: AnyCodable?,
|
||||
deliveryerror: String?,
|
||||
sessionid: String?,
|
||||
sessionkey: String?,
|
||||
runatms: Int?,
|
||||
durationms: Int?,
|
||||
nextrunatms: Int?,
|
||||
model: String?,
|
||||
provider: String?,
|
||||
usage: [String: AnyCodable]?,
|
||||
jobname: String?)
|
||||
nextrunatms: Int?)
|
||||
{
|
||||
self.ts = ts
|
||||
self.jobid = jobid
|
||||
@@ -2498,18 +2406,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
self.status = status
|
||||
self.error = error
|
||||
self.summary = summary
|
||||
self.delivered = delivered
|
||||
self.deliverystatus = deliverystatus
|
||||
self.deliveryerror = deliveryerror
|
||||
self.sessionid = sessionid
|
||||
self.sessionkey = sessionkey
|
||||
self.runatms = runatms
|
||||
self.durationms = durationms
|
||||
self.nextrunatms = nextrunatms
|
||||
self.model = model
|
||||
self.provider = provider
|
||||
self.usage = usage
|
||||
self.jobname = jobname
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
@@ -2519,18 +2420,11 @@ public struct CronRunLogEntry: Codable, Sendable {
|
||||
case status
|
||||
case error
|
||||
case summary
|
||||
case delivered
|
||||
case deliverystatus = "deliveryStatus"
|
||||
case deliveryerror = "deliveryError"
|
||||
case sessionid = "sessionId"
|
||||
case sessionkey = "sessionKey"
|
||||
case runatms = "runAtMs"
|
||||
case durationms = "durationMs"
|
||||
case nextrunatms = "nextRunAtMs"
|
||||
case model
|
||||
case provider
|
||||
case usage
|
||||
case jobname = "jobName"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
export function reconnectDelayMs(
|
||||
attempt,
|
||||
opts = { baseMs: 1000, maxMs: 30000, jitterMs: 1000, random: Math.random },
|
||||
) {
|
||||
const baseMs = Number.isFinite(opts.baseMs) ? opts.baseMs : 1000;
|
||||
const maxMs = Number.isFinite(opts.maxMs) ? opts.maxMs : 30000;
|
||||
const jitterMs = Number.isFinite(opts.jitterMs) ? opts.jitterMs : 1000;
|
||||
const random = typeof opts.random === "function" ? opts.random : Math.random;
|
||||
const safeAttempt = Math.max(0, Number.isFinite(attempt) ? attempt : 0);
|
||||
const backoff = Math.min(baseMs * 2 ** safeAttempt, maxMs);
|
||||
return backoff + Math.max(0, jitterMs) * random();
|
||||
}
|
||||
|
||||
export function buildRelayWsUrl(port, gatewayToken) {
|
||||
const token = String(gatewayToken || "").trim();
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
"Missing gatewayToken in extension settings (chrome.storage.local.gatewayToken)",
|
||||
);
|
||||
}
|
||||
return `ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(token)}`;
|
||||
}
|
||||
|
||||
export function isRetryableReconnectError(err) {
|
||||
const message = err instanceof Error ? err.message : String(err || "");
|
||||
if (message.includes("Missing gatewayToken")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { buildRelayWsUrl, isRetryableReconnectError, reconnectDelayMs } from './background-utils.js'
|
||||
|
||||
const DEFAULT_PORT = 18792
|
||||
|
||||
const BADGE = {
|
||||
@@ -14,6 +12,8 @@ let relayWs = null
|
||||
/** @type {Promise<void>|null} */
|
||||
let relayConnectPromise = null
|
||||
|
||||
let debuggerListenersInstalled = false
|
||||
|
||||
let nextSession = 1
|
||||
|
||||
/** @type {Map<number, {state:'connecting'|'connected', sessionId?:string, targetId?:string, attachOrder?:number}>} */
|
||||
@@ -26,14 +26,6 @@ const childSessionToTab = new Map()
|
||||
/** @type {Map<number, {resolve:(v:any)=>void, reject:(e:Error)=>void}>} */
|
||||
const pending = new Map()
|
||||
|
||||
// Per-tab operation locks prevent double-attach races.
|
||||
/** @type {Set<number>} */
|
||||
const tabOperationLocks = new Set()
|
||||
|
||||
// Reconnect state for exponential backoff.
|
||||
let reconnectAttempt = 0
|
||||
let reconnectTimer = null
|
||||
|
||||
function nowStack() {
|
||||
try {
|
||||
return new Error().stack || ''
|
||||
@@ -63,63 +55,6 @@ function setBadge(tabId, kind) {
|
||||
void chrome.action.setBadgeTextColor({ tabId, color: '#FFFFFF' }).catch(() => {})
|
||||
}
|
||||
|
||||
// Persist attached tab state to survive MV3 service worker restarts.
|
||||
async function persistState() {
|
||||
try {
|
||||
const tabEntries = []
|
||||
for (const [tabId, tab] of tabs.entries()) {
|
||||
if (tab.state === 'connected' && tab.sessionId && tab.targetId) {
|
||||
tabEntries.push({ tabId, sessionId: tab.sessionId, targetId: tab.targetId, attachOrder: tab.attachOrder })
|
||||
}
|
||||
}
|
||||
await chrome.storage.session.set({
|
||||
persistedTabs: tabEntries,
|
||||
nextSession,
|
||||
})
|
||||
} catch {
|
||||
// chrome.storage.session may not be available in all contexts.
|
||||
}
|
||||
}
|
||||
|
||||
// Rehydrate tab state on service worker startup. Fast path — just restores
|
||||
// maps and badges. Relay reconnect happens separately in background.
|
||||
async function rehydrateState() {
|
||||
try {
|
||||
const stored = await chrome.storage.session.get(['persistedTabs', 'nextSession'])
|
||||
if (stored.nextSession) {
|
||||
nextSession = Math.max(nextSession, stored.nextSession)
|
||||
}
|
||||
const entries = stored.persistedTabs || []
|
||||
// Phase 1: optimistically restore state and badges.
|
||||
for (const entry of entries) {
|
||||
tabs.set(entry.tabId, {
|
||||
state: 'connected',
|
||||
sessionId: entry.sessionId,
|
||||
targetId: entry.targetId,
|
||||
attachOrder: entry.attachOrder,
|
||||
})
|
||||
tabBySession.set(entry.sessionId, entry.tabId)
|
||||
setBadge(entry.tabId, 'on')
|
||||
}
|
||||
// Phase 2: validate asynchronously, remove dead tabs.
|
||||
for (const entry of entries) {
|
||||
try {
|
||||
await chrome.tabs.get(entry.tabId)
|
||||
await chrome.debugger.sendCommand({ tabId: entry.tabId }, 'Runtime.evaluate', {
|
||||
expression: '1',
|
||||
returnByValue: true,
|
||||
})
|
||||
} catch {
|
||||
tabs.delete(entry.tabId)
|
||||
tabBySession.delete(entry.sessionId)
|
||||
setBadge(entry.tabId, 'off')
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore rehydration errors.
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureRelayConnection() {
|
||||
if (relayWs && relayWs.readyState === WebSocket.OPEN) return
|
||||
if (relayConnectPromise) return await relayConnectPromise
|
||||
@@ -128,7 +63,9 @@ async function ensureRelayConnection() {
|
||||
const port = await getRelayPort()
|
||||
const gatewayToken = await getGatewayToken()
|
||||
const httpBase = `http://127.0.0.1:${port}`
|
||||
const wsUrl = buildRelayWsUrl(port, gatewayToken)
|
||||
const wsUrl = gatewayToken
|
||||
? `ws://127.0.0.1:${port}/extension?token=${encodeURIComponent(gatewayToken)}`
|
||||
: `ws://127.0.0.1:${port}/extension`
|
||||
|
||||
// Fast preflight: is the relay server up?
|
||||
try {
|
||||
@@ -137,6 +74,12 @@ async function ensureRelayConnection() {
|
||||
throw new Error(`Relay server not reachable at ${httpBase} (${String(err)})`)
|
||||
}
|
||||
|
||||
if (!gatewayToken) {
|
||||
throw new Error(
|
||||
'Missing gatewayToken in extension settings (chrome.storage.local.gatewayToken)',
|
||||
)
|
||||
}
|
||||
|
||||
const ws = new WebSocket(wsUrl)
|
||||
relayWs = ws
|
||||
|
||||
@@ -156,142 +99,42 @@ async function ensureRelayConnection() {
|
||||
}
|
||||
})
|
||||
|
||||
// Bind permanent handlers. Guard against stale socket: if this WS was
|
||||
// replaced before its close fires, the handler is a no-op.
|
||||
ws.onmessage = (event) => {
|
||||
if (ws !== relayWs) return
|
||||
void whenReady(() => onRelayMessage(String(event.data || '')))
|
||||
}
|
||||
ws.onclose = () => {
|
||||
if (ws !== relayWs) return
|
||||
onRelayClosed('closed')
|
||||
}
|
||||
ws.onerror = () => {
|
||||
if (ws !== relayWs) return
|
||||
onRelayClosed('error')
|
||||
ws.onmessage = (event) => void onRelayMessage(String(event.data || ''))
|
||||
ws.onclose = () => onRelayClosed('closed')
|
||||
ws.onerror = () => onRelayClosed('error')
|
||||
|
||||
if (!debuggerListenersInstalled) {
|
||||
debuggerListenersInstalled = true
|
||||
chrome.debugger.onEvent.addListener(onDebuggerEvent)
|
||||
chrome.debugger.onDetach.addListener(onDebuggerDetach)
|
||||
}
|
||||
})()
|
||||
|
||||
try {
|
||||
await relayConnectPromise
|
||||
reconnectAttempt = 0
|
||||
} finally {
|
||||
relayConnectPromise = null
|
||||
}
|
||||
}
|
||||
|
||||
// Relay closed — update badges, reject pending requests, auto-reconnect.
|
||||
// Debugger sessions are kept alive so they survive transient WS drops.
|
||||
function onRelayClosed(reason) {
|
||||
relayWs = null
|
||||
|
||||
for (const [id, p] of pending.entries()) {
|
||||
pending.delete(id)
|
||||
p.reject(new Error(`Relay disconnected (${reason})`))
|
||||
}
|
||||
|
||||
for (const [tabId, tab] of tabs.entries()) {
|
||||
if (tab.state === 'connected') {
|
||||
setBadge(tabId, 'connecting')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: relay reconnecting…',
|
||||
})
|
||||
}
|
||||
for (const tabId of tabs.keys()) {
|
||||
void chrome.debugger.detach({ tabId }).catch(() => {})
|
||||
setBadge(tabId, 'connecting')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: disconnected (click to re-attach)',
|
||||
})
|
||||
}
|
||||
|
||||
scheduleReconnect()
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
|
||||
const delay = reconnectDelayMs(reconnectAttempt)
|
||||
reconnectAttempt++
|
||||
|
||||
console.log(`Scheduling reconnect attempt ${reconnectAttempt} in ${Math.round(delay)}ms`)
|
||||
|
||||
reconnectTimer = setTimeout(async () => {
|
||||
reconnectTimer = null
|
||||
try {
|
||||
await ensureRelayConnection()
|
||||
reconnectAttempt = 0
|
||||
console.log('Reconnected successfully')
|
||||
await reannounceAttachedTabs()
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
console.warn(`Reconnect attempt ${reconnectAttempt} failed: ${message}`)
|
||||
if (!isRetryableReconnectError(err)) {
|
||||
return
|
||||
}
|
||||
scheduleReconnect()
|
||||
}
|
||||
}, delay)
|
||||
}
|
||||
|
||||
function cancelReconnect() {
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer)
|
||||
reconnectTimer = null
|
||||
}
|
||||
reconnectAttempt = 0
|
||||
}
|
||||
|
||||
// Re-announce all attached tabs to the relay after reconnect.
|
||||
async function reannounceAttachedTabs() {
|
||||
for (const [tabId, tab] of tabs.entries()) {
|
||||
if (tab.state !== 'connected' || !tab.sessionId || !tab.targetId) continue
|
||||
|
||||
// Verify debugger is still attached.
|
||||
try {
|
||||
await chrome.debugger.sendCommand({ tabId }, 'Runtime.evaluate', {
|
||||
expression: '1',
|
||||
returnByValue: true,
|
||||
})
|
||||
} catch {
|
||||
tabs.delete(tabId)
|
||||
if (tab.sessionId) tabBySession.delete(tab.sessionId)
|
||||
setBadge(tabId, 'off')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay (click to attach/detach)',
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
// Send fresh attach event to relay.
|
||||
try {
|
||||
const info = /** @type {any} */ (
|
||||
await chrome.debugger.sendCommand({ tabId }, 'Target.getTargetInfo')
|
||||
)
|
||||
const targetInfo = info?.targetInfo
|
||||
|
||||
sendToRelay({
|
||||
method: 'forwardCDPEvent',
|
||||
params: {
|
||||
method: 'Target.attachedToTarget',
|
||||
params: {
|
||||
sessionId: tab.sessionId,
|
||||
targetInfo: { ...targetInfo, attached: true },
|
||||
waitingForDebugger: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
setBadge(tabId, 'on')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: attached (click to detach)',
|
||||
})
|
||||
} catch {
|
||||
setBadge(tabId, 'on')
|
||||
}
|
||||
}
|
||||
|
||||
await persistState()
|
||||
tabs.clear()
|
||||
tabBySession.clear()
|
||||
childSessionToTab.clear()
|
||||
}
|
||||
|
||||
function sendToRelay(payload) {
|
||||
@@ -316,18 +159,10 @@ async function maybeOpenHelpOnce() {
|
||||
function requestFromRelay(command) {
|
||||
const id = command.id
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
pending.delete(id)
|
||||
reject(new Error('Relay request timeout (30s)'))
|
||||
}, 30000)
|
||||
pending.set(id, {
|
||||
resolve: (v) => { clearTimeout(timer); resolve(v) },
|
||||
reject: (e) => { clearTimeout(timer); reject(e) },
|
||||
})
|
||||
pending.set(id, { resolve, reject })
|
||||
try {
|
||||
sendToRelay(command)
|
||||
} catch (err) {
|
||||
clearTimeout(timer)
|
||||
pending.delete(id)
|
||||
reject(err instanceof Error ? err : new Error(String(err)))
|
||||
}
|
||||
@@ -398,9 +233,8 @@ async function attachTab(tabId, opts = {}) {
|
||||
throw new Error('Target.getTargetInfo returned no targetId')
|
||||
}
|
||||
|
||||
const sid = nextSession++
|
||||
const sessionId = `cb-tab-${sid}`
|
||||
const attachOrder = sid
|
||||
const sessionId = `cb-tab-${nextSession++}`
|
||||
const attachOrder = nextSession
|
||||
|
||||
tabs.set(tabId, { state: 'connected', sessionId, targetId, attachOrder })
|
||||
tabBySession.set(sessionId, tabId)
|
||||
@@ -424,33 +258,11 @@ async function attachTab(tabId, opts = {}) {
|
||||
}
|
||||
|
||||
setBadge(tabId, 'on')
|
||||
await persistState()
|
||||
|
||||
return { sessionId, targetId }
|
||||
}
|
||||
|
||||
async function detachTab(tabId, reason) {
|
||||
const tab = tabs.get(tabId)
|
||||
|
||||
// Send detach events for child sessions first.
|
||||
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
||||
if (parentTabId === tabId) {
|
||||
try {
|
||||
sendToRelay({
|
||||
method: 'forwardCDPEvent',
|
||||
params: {
|
||||
method: 'Target.detachedFromTarget',
|
||||
params: { sessionId: childSessionId, reason: 'parent_detached' },
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Relay may be down.
|
||||
}
|
||||
childSessionToTab.delete(childSessionId)
|
||||
}
|
||||
}
|
||||
|
||||
// Send detach event for main session.
|
||||
if (tab?.sessionId && tab?.targetId) {
|
||||
try {
|
||||
sendToRelay({
|
||||
@@ -461,17 +273,21 @@ async function detachTab(tabId, reason) {
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Relay may be down.
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (tab?.sessionId) tabBySession.delete(tab.sessionId)
|
||||
tabs.delete(tabId)
|
||||
|
||||
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
||||
if (parentTabId === tabId) childSessionToTab.delete(childSessionId)
|
||||
}
|
||||
|
||||
try {
|
||||
await chrome.debugger.detach({ tabId })
|
||||
} catch {
|
||||
// May already be detached.
|
||||
// ignore
|
||||
}
|
||||
|
||||
setBadge(tabId, 'off')
|
||||
@@ -479,8 +295,6 @@ async function detachTab(tabId, reason) {
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay (click to attach/detach)',
|
||||
})
|
||||
|
||||
await persistState()
|
||||
}
|
||||
|
||||
async function connectOrToggleForActiveTab() {
|
||||
@@ -488,43 +302,33 @@ async function connectOrToggleForActiveTab() {
|
||||
const tabId = active?.id
|
||||
if (!tabId) return
|
||||
|
||||
// Prevent concurrent operations on the same tab.
|
||||
if (tabOperationLocks.has(tabId)) return
|
||||
tabOperationLocks.add(tabId)
|
||||
const existing = tabs.get(tabId)
|
||||
if (existing?.state === 'connected') {
|
||||
await detachTab(tabId, 'toggle')
|
||||
return
|
||||
}
|
||||
|
||||
tabs.set(tabId, { state: 'connecting' })
|
||||
setBadge(tabId, 'connecting')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: connecting to local relay…',
|
||||
})
|
||||
|
||||
try {
|
||||
const existing = tabs.get(tabId)
|
||||
if (existing?.state === 'connected') {
|
||||
await detachTab(tabId, 'toggle')
|
||||
return
|
||||
}
|
||||
|
||||
// User is manually connecting — cancel any pending reconnect.
|
||||
cancelReconnect()
|
||||
|
||||
tabs.set(tabId, { state: 'connecting' })
|
||||
setBadge(tabId, 'connecting')
|
||||
await ensureRelayConnection()
|
||||
await attachTab(tabId)
|
||||
} catch (err) {
|
||||
tabs.delete(tabId)
|
||||
setBadge(tabId, 'error')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: connecting to local relay…',
|
||||
title: 'OpenClaw Browser Relay: relay not running (open options for setup)',
|
||||
})
|
||||
|
||||
try {
|
||||
await ensureRelayConnection()
|
||||
await attachTab(tabId)
|
||||
} catch (err) {
|
||||
tabs.delete(tabId)
|
||||
setBadge(tabId, 'error')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: relay not running (open options for setup)',
|
||||
})
|
||||
void maybeOpenHelpOnce()
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
console.warn('attach failed', message, nowStack())
|
||||
}
|
||||
} finally {
|
||||
tabOperationLocks.delete(tabId)
|
||||
void maybeOpenHelpOnce()
|
||||
// Extra breadcrumbs in chrome://extensions service worker logs.
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
console.warn('attach failed', message, nowStack())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -533,12 +337,14 @@ async function handleForwardCdpCommand(msg) {
|
||||
const params = msg?.params?.params || undefined
|
||||
const sessionId = typeof msg?.params?.sessionId === 'string' ? msg.params.sessionId : undefined
|
||||
|
||||
// Map command to tab
|
||||
const bySession = sessionId ? getTabBySessionId(sessionId) : null
|
||||
const targetId = typeof params?.targetId === 'string' ? params.targetId : undefined
|
||||
const tabId =
|
||||
bySession?.tabId ||
|
||||
(targetId ? getTabByTargetId(targetId) : null) ||
|
||||
(() => {
|
||||
// No sessionId: pick the first connected tab (stable-ish).
|
||||
for (const [id, tab] of tabs.entries()) {
|
||||
if (tab.state === 'connected') return id
|
||||
}
|
||||
@@ -628,173 +434,20 @@ function onDebuggerEvent(source, method, params) {
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Relay may be down.
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation/reload fires target_closed but the tab is still alive — Chrome
|
||||
// just swaps the renderer process. Suppress the detach event to the relay and
|
||||
// seamlessly re-attach after a short grace period.
|
||||
function onDebuggerDetach(source, reason) {
|
||||
const tabId = source.tabId
|
||||
if (!tabId) return
|
||||
if (!tabs.has(tabId)) return
|
||||
|
||||
if (reason === 'target_closed') {
|
||||
const oldState = tabs.get(tabId)
|
||||
setBadge(tabId, 'connecting')
|
||||
void chrome.action.setTitle({
|
||||
tabId,
|
||||
title: 'OpenClaw Browser Relay: re-attaching after navigation…',
|
||||
})
|
||||
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// If user manually detached during the grace period, bail out.
|
||||
if (!tabs.has(tabId)) return
|
||||
const tab = await chrome.tabs.get(tabId)
|
||||
if (tab && relayWs?.readyState === WebSocket.OPEN) {
|
||||
console.log(`Re-attaching tab ${tabId} after navigation`)
|
||||
if (oldState?.sessionId) tabBySession.delete(oldState.sessionId)
|
||||
tabs.delete(tabId)
|
||||
await attachTab(tabId, { skipAttachedEvent: false })
|
||||
} else {
|
||||
// Tab gone or relay down — full cleanup.
|
||||
void detachTab(tabId, reason)
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Failed to re-attach tab ${tabId} after navigation:`, err.message)
|
||||
void detachTab(tabId, reason)
|
||||
}
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
|
||||
// Non-navigation detach (user action, crash, etc.) — full cleanup.
|
||||
void detachTab(tabId, reason)
|
||||
}
|
||||
|
||||
// Tab lifecycle listeners — clean up stale entries.
|
||||
chrome.tabs.onRemoved.addListener((tabId) => void whenReady(() => {
|
||||
if (!tabs.has(tabId)) return
|
||||
const tab = tabs.get(tabId)
|
||||
if (tab?.sessionId) tabBySession.delete(tab.sessionId)
|
||||
tabs.delete(tabId)
|
||||
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
||||
if (parentTabId === tabId) childSessionToTab.delete(childSessionId)
|
||||
}
|
||||
if (tab?.sessionId && tab?.targetId) {
|
||||
try {
|
||||
sendToRelay({
|
||||
method: 'forwardCDPEvent',
|
||||
params: {
|
||||
method: 'Target.detachedFromTarget',
|
||||
params: { sessionId: tab.sessionId, targetId: tab.targetId, reason: 'tab_closed' },
|
||||
},
|
||||
})
|
||||
} catch {
|
||||
// Relay may be down.
|
||||
}
|
||||
}
|
||||
void persistState()
|
||||
}))
|
||||
|
||||
chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => void whenReady(() => {
|
||||
const tab = tabs.get(removedTabId)
|
||||
if (!tab) return
|
||||
tabs.delete(removedTabId)
|
||||
tabs.set(addedTabId, tab)
|
||||
if (tab.sessionId) {
|
||||
tabBySession.set(tab.sessionId, addedTabId)
|
||||
}
|
||||
for (const [childSessionId, parentTabId] of childSessionToTab.entries()) {
|
||||
if (parentTabId === removedTabId) {
|
||||
childSessionToTab.set(childSessionId, addedTabId)
|
||||
}
|
||||
}
|
||||
setBadge(addedTabId, 'on')
|
||||
void persistState()
|
||||
}))
|
||||
|
||||
// Register debugger listeners at module scope so detach/event handling works
|
||||
// even when the relay WebSocket is down.
|
||||
chrome.debugger.onEvent.addListener((...args) => void whenReady(() => onDebuggerEvent(...args)))
|
||||
chrome.debugger.onDetach.addListener((...args) => void whenReady(() => onDebuggerDetach(...args)))
|
||||
|
||||
chrome.action.onClicked.addListener(() => void whenReady(() => connectOrToggleForActiveTab()))
|
||||
|
||||
// Refresh badge after navigation completes — service worker may have restarted
|
||||
// during navigation, losing ephemeral badge state.
|
||||
chrome.webNavigation.onCompleted.addListener(({ tabId, frameId }) => void whenReady(() => {
|
||||
if (frameId !== 0) return
|
||||
const tab = tabs.get(tabId)
|
||||
if (tab?.state === 'connected') {
|
||||
setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting')
|
||||
}
|
||||
}))
|
||||
|
||||
// Refresh badge when user switches to an attached tab.
|
||||
chrome.tabs.onActivated.addListener(({ tabId }) => void whenReady(() => {
|
||||
const tab = tabs.get(tabId)
|
||||
if (tab?.state === 'connected') {
|
||||
setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting')
|
||||
}
|
||||
}))
|
||||
chrome.action.onClicked.addListener(() => void connectOrToggleForActiveTab())
|
||||
|
||||
chrome.runtime.onInstalled.addListener(() => {
|
||||
// Useful: first-time instructions.
|
||||
void chrome.runtime.openOptionsPage()
|
||||
})
|
||||
|
||||
// MV3 keepalive via chrome.alarms — more reliable than setInterval across
|
||||
// service worker restarts. Checks relay health and refreshes badges.
|
||||
chrome.alarms.create('relay-keepalive', { periodInMinutes: 0.5 })
|
||||
|
||||
chrome.alarms.onAlarm.addListener(async (alarm) => {
|
||||
if (alarm.name !== 'relay-keepalive') return
|
||||
await initPromise
|
||||
|
||||
if (tabs.size === 0) return
|
||||
|
||||
// Refresh badges (ephemeral in MV3).
|
||||
for (const [tabId, tab] of tabs.entries()) {
|
||||
if (tab.state === 'connected') {
|
||||
setBadge(tabId, relayWs && relayWs.readyState === WebSocket.OPEN ? 'on' : 'connecting')
|
||||
}
|
||||
}
|
||||
|
||||
// If relay is down and no reconnect is in progress, trigger one.
|
||||
if (!relayWs || relayWs.readyState !== WebSocket.OPEN) {
|
||||
if (!relayConnectPromise && !reconnectTimer) {
|
||||
console.log('Keepalive: WebSocket unhealthy, triggering reconnect')
|
||||
await ensureRelayConnection().catch(() => {
|
||||
// ensureRelayConnection may throw without triggering onRelayClosed
|
||||
// (e.g. preflight fetch fails before WS is created), so ensure
|
||||
// reconnect is always scheduled on failure.
|
||||
if (!reconnectTimer) {
|
||||
scheduleReconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Rehydrate state on service worker startup. Split: rehydration is the gate
|
||||
// (fast), relay reconnect runs in background (slow, non-blocking).
|
||||
const initPromise = rehydrateState()
|
||||
|
||||
initPromise.then(() => {
|
||||
if (tabs.size > 0) {
|
||||
ensureRelayConnection().then(() => {
|
||||
reconnectAttempt = 0
|
||||
return reannounceAttachedTabs()
|
||||
}).catch(() => {
|
||||
scheduleReconnect()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Shared gate: all state-dependent handlers await this before accessing maps.
|
||||
async function whenReady(fn) {
|
||||
await initPromise
|
||||
return fn()
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"48": "icons/icon48.png",
|
||||
"128": "icons/icon128.png"
|
||||
},
|
||||
"permissions": ["debugger", "tabs", "activeTab", "storage", "alarms", "webNavigation"],
|
||||
"permissions": ["debugger", "tabs", "activeTab", "storage"],
|
||||
"host_permissions": ["http://127.0.0.1/*", "http://localhost/*"],
|
||||
"background": { "service_worker": "background.js", "type": "module" },
|
||||
"action": {
|
||||
|
||||
@@ -82,9 +82,6 @@ fi
|
||||
|
||||
mkdir -p "$OPENCLAW_CONFIG_DIR"
|
||||
mkdir -p "$OPENCLAW_WORKSPACE_DIR"
|
||||
# Seed device-identity parent eagerly for Docker Desktop/Windows bind mounts
|
||||
# that reject creating new subdirectories from inside the container.
|
||||
mkdir -p "$OPENCLAW_CONFIG_DIR/identity"
|
||||
|
||||
export OPENCLAW_CONFIG_DIR
|
||||
export OPENCLAW_WORKSPACE_DIR
|
||||
|
||||
@@ -182,7 +182,9 @@ The `metadata.openclaw` object supports:
|
||||
The `handler.ts` file exports a `HookHandler` function:
|
||||
|
||||
```typescript
|
||||
const myHandler = async (event) => {
|
||||
import type { HookHandler } from "../../src/hooks/hooks.js";
|
||||
|
||||
const myHandler: HookHandler = async (event) => {
|
||||
// Only trigger on 'new' command
|
||||
if (event.type !== "command" || event.action !== "new") {
|
||||
return;
|
||||
@@ -303,15 +305,13 @@ Message events include rich context about the message:
|
||||
#### Example: Message Logger Hook
|
||||
|
||||
```typescript
|
||||
const isMessageReceivedEvent = (event: { type: string; action: string }) =>
|
||||
event.type === "message" && event.action === "received";
|
||||
const isMessageSentEvent = (event: { type: string; action: string }) =>
|
||||
event.type === "message" && event.action === "sent";
|
||||
import type { HookHandler } from "../../src/hooks/hooks.js";
|
||||
import { isMessageReceivedEvent, isMessageSentEvent } from "../../src/hooks/internal-hooks.js";
|
||||
|
||||
const handler = async (event) => {
|
||||
if (isMessageReceivedEvent(event as { type: string; action: string })) {
|
||||
const handler: HookHandler = async (event) => {
|
||||
if (isMessageReceivedEvent(event)) {
|
||||
console.log(`[message-logger] Received from ${event.context.from}: ${event.context.content}`);
|
||||
} else if (isMessageSentEvent(event as { type: string; action: string })) {
|
||||
} else if (isMessageSentEvent(event)) {
|
||||
console.log(`[message-logger] Sent to ${event.context.to}: ${event.context.content}`);
|
||||
}
|
||||
};
|
||||
@@ -364,7 +364,9 @@ This hook does something useful when you issue `/new`.
|
||||
### 4. Create handler.ts
|
||||
|
||||
```typescript
|
||||
const handler = async (event) => {
|
||||
import type { HookHandler } from "../../src/hooks/hooks.js";
|
||||
|
||||
const handler: HookHandler = async (event) => {
|
||||
if (event.type !== "command" || event.action !== "new") {
|
||||
return;
|
||||
}
|
||||
@@ -791,17 +793,13 @@ Test your handlers in isolation:
|
||||
|
||||
```typescript
|
||||
import { test } from "vitest";
|
||||
import { createHookEvent } from "./src/hooks/hooks.js";
|
||||
import myHandler from "./hooks/my-hook/handler.js";
|
||||
|
||||
test("my handler works", async () => {
|
||||
const event = {
|
||||
type: "command",
|
||||
action: "new",
|
||||
sessionKey: "test-session",
|
||||
timestamp: new Date(),
|
||||
messages: [],
|
||||
context: { foo: "bar" },
|
||||
};
|
||||
const event = createHookEvent("command", "new", "test-session", {
|
||||
foo: "bar",
|
||||
});
|
||||
|
||||
await myHandler(event);
|
||||
|
||||
|
||||
@@ -425,7 +425,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="allowlist"` (with a warning in logs), even if `channels.defaults.groupPolicy` is `open`.
|
||||
If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="open"` (with a warning in logs).
|
||||
|
||||
</Tab>
|
||||
|
||||
|
||||
@@ -190,7 +190,6 @@ Notes:
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
|
||||
@@ -254,10 +253,7 @@ Notes:
|
||||
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
||||
|
||||
- `tools`: allow/deny tools for the whole group.
|
||||
- `toolsBySender`: per-sender overrides within the group.
|
||||
Use explicit key prefixes:
|
||||
`id:<senderId>`, `e164:<phone>`, `username:<handle>`, `name:<displayName>`, and `"*"` wildcard.
|
||||
Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
- `toolsBySender`: per-sender overrides within the group (keys are sender IDs/usernames/emails/phone numbers depending on the channel). Use `"*"` as a wildcard.
|
||||
|
||||
Resolution order (most specific wins):
|
||||
|
||||
@@ -277,7 +273,7 @@ Example (Telegram):
|
||||
"-1001234567890": {
|
||||
tools: { deny: ["exec", "read", "write"] },
|
||||
toolsBySender: {
|
||||
"id:123456789": { alsoAllow: ["exec"] },
|
||||
"123456789": { alsoAllow: ["exec"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -158,7 +158,6 @@ imsg send <handle> "test"
|
||||
Group sender allowlist: `channels.imessage.groupAllowFrom`.
|
||||
|
||||
Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available.
|
||||
Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Mention gating for groups:
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Text is supported everywhere; media and reactions vary by channel.
|
||||
- [BlueBubbles](/channels/bluebubbles) — **Recommended for iMessage**; uses the BlueBubbles macOS server REST API with full feature support (edit, unsend, effects, reactions, group management — edit currently broken on macOS 26 Tahoe).
|
||||
- [iMessage (legacy)](/channels/imessage) — Legacy macOS integration via imsg CLI (deprecated, use BlueBubbles for new setups).
|
||||
- [Microsoft Teams](/channels/msteams) — Bot Framework; enterprise support (plugin, installed separately).
|
||||
- [Synology Chat](/channels/synology-chat) — Synology NAS Chat via outgoing+incoming webhooks (plugin, installed separately).
|
||||
- [LINE](/channels/line) — LINE Messaging API bot (plugin, installed separately).
|
||||
- [Nextcloud Talk](/channels/nextcloud-talk) — Self-hosted chat via Nextcloud Talk (plugin, installed separately).
|
||||
- [Matrix](/channels/matrix) — Matrix protocol (plugin, installed separately).
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
title: IRC
|
||||
description: Connect OpenClaw to IRC channels and direct messages.
|
||||
summary: "IRC plugin setup, access controls, and troubleshooting"
|
||||
read_when:
|
||||
- You want to connect OpenClaw to IRC channels or DMs
|
||||
- You are configuring IRC allowlists, group policy, or mention gating
|
||||
---
|
||||
|
||||
Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages.
|
||||
@@ -163,7 +159,7 @@ Use `toolsBySender` to apply a stricter policy to `"*"` and a looser one to your
|
||||
"*": {
|
||||
deny: ["group:runtime", "group:fs", "gateway", "nodes", "cron", "browser"],
|
||||
},
|
||||
"id:eigen": {
|
||||
eigen: {
|
||||
deny: ["gateway", "nodes", "cron"],
|
||||
},
|
||||
},
|
||||
@@ -176,9 +172,7 @@ Use `toolsBySender` to apply a stricter policy to `"*"` and a looser one to your
|
||||
|
||||
Notes:
|
||||
|
||||
- `toolsBySender` keys should use `id:` for IRC sender identity values:
|
||||
`id:eigen` or `id:eigen!~eigen@174.127.248.171` for stronger matching.
|
||||
- Legacy unprefixed keys are still accepted and matched as `id:` only.
|
||||
- `toolsBySender` keys can be a nick (e.g. `"eigen"`) or a full hostmask (`"eigen!~eigen@174.127.248.171"`) for stronger identity matching.
|
||||
- The first matching sender policy wins; `"*"` is the wildcard fallback.
|
||||
|
||||
For more on group access vs mention-gating (and how they interact), see: [/channels/groups](/channels/groups).
|
||||
|
||||
@@ -118,7 +118,6 @@ Allowlists and policies:
|
||||
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||
- Runtime note: if `channels.line` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
LINE IDs are case-sensitive. Valid IDs look like:
|
||||
|
||||
|
||||
@@ -195,7 +195,6 @@ Notes:
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Runtime note: if `channels.matrix` is completely missing, runtime falls back to `groupPolicy="allowlist"` for room checks (even if `channels.defaults.groupPolicy` is set).
|
||||
- Allowlist rooms with `channels.matrix.groups` (room IDs or aliases; names are resolved to IDs when directory search finds a single exact match):
|
||||
|
||||
```json5
|
||||
|
||||
@@ -103,7 +103,6 @@ Notes:
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
- Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## Targets for outbound delivery
|
||||
|
||||
|
||||
@@ -469,8 +469,6 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention`: per-channel override.
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.tools`: per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
||||
- `channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender`: per-channel per-sender tool policy overrides (`"*"` wildcard supported).
|
||||
- `toolsBySender` keys should use explicit prefixes:
|
||||
`id:`, `e164:`, `username:`, `name:` (legacy unprefixed keys still map to `id:` only).
|
||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||
|
||||
## Routing & Sessions
|
||||
|
||||
@@ -195,7 +195,6 @@ Groups:
|
||||
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ For actions/directory reads, user token can be preferred when configured. For wr
|
||||
|
||||
Channel allowlist lives under `channels.slack.channels`.
|
||||
|
||||
Runtime note: if `channels.slack` is completely missing (env-only setup), runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
Runtime note: if `channels.slack` is completely missing (env-only setup) and `channels.defaults.groupPolicy` is unset, runtime falls back to `groupPolicy="open"` and logs a warning.
|
||||
|
||||
Name/ID resolution:
|
||||
|
||||
@@ -191,8 +191,6 @@ For actions/directory reads, user token can be preferred when configured. For wr
|
||||
- `skills`
|
||||
- `systemPrompt`
|
||||
- `tools`, `toolsBySender`
|
||||
- `toolsBySender` key format: `id:`, `e164:`, `username:`, `name:`, or `"*"` wildcard
|
||||
(legacy unprefixed keys still map to `id:` only)
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
@@ -243,7 +241,7 @@ Manual reply tags are supported:
|
||||
- `[[reply_to_current]]`
|
||||
- `[[reply_to:<id>]]`
|
||||
|
||||
Note: `replyToMode="off"` disables **all** reply threading in Slack, including explicit `[[reply_to_*]]` tags. This differs from Telegram, where explicit tags are still honored in `"off"` mode. The difference reflects the platform threading models: Slack threads hide messages from the channel, while Telegram replies remain visible in the main chat flow.
|
||||
Note: `replyToMode="off"` disables implicit reply threading. Explicit `[[reply_to_*]]` tags are still honored.
|
||||
|
||||
## Media, chunking, and delivery
|
||||
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
---
|
||||
summary: "Synology Chat webhook setup and OpenClaw config"
|
||||
read_when:
|
||||
- Setting up Synology Chat with OpenClaw
|
||||
- Debugging Synology Chat webhook routing
|
||||
title: "Synology Chat"
|
||||
---
|
||||
|
||||
# Synology Chat (plugin)
|
||||
|
||||
Status: supported via plugin as a direct-message channel using Synology Chat webhooks.
|
||||
The plugin accepts inbound messages from Synology Chat outgoing webhooks and sends replies
|
||||
through a Synology Chat incoming webhook.
|
||||
|
||||
## Plugin required
|
||||
|
||||
Synology Chat is plugin-based and not part of the default core channel install.
|
||||
|
||||
Install from a local checkout:
|
||||
|
||||
```bash
|
||||
openclaw plugins install ./extensions/synology-chat
|
||||
```
|
||||
|
||||
Details: [Plugins](/tools/plugin)
|
||||
|
||||
## Quick setup
|
||||
|
||||
1. Install and enable the Synology Chat plugin.
|
||||
2. In Synology Chat integrations:
|
||||
- Create an incoming webhook and copy its URL.
|
||||
- Create an outgoing webhook with your secret token.
|
||||
3. Point the outgoing webhook URL to your OpenClaw gateway:
|
||||
- `https://gateway-host/webhook/synology` by default.
|
||||
- Or your custom `channels.synology-chat.webhookPath`.
|
||||
4. Configure `channels.synology-chat` in OpenClaw.
|
||||
5. Restart gateway and send a DM to the Synology Chat bot.
|
||||
|
||||
Minimal config:
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
token: "synology-outgoing-token",
|
||||
incomingUrl: "https://nas.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=incoming&version=2&token=...",
|
||||
webhookPath: "/webhook/synology",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["123456"],
|
||||
rateLimitPerMinute: 30,
|
||||
allowInsecureSsl: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Environment variables
|
||||
|
||||
For the default account, you can use env vars:
|
||||
|
||||
- `SYNOLOGY_CHAT_TOKEN`
|
||||
- `SYNOLOGY_CHAT_INCOMING_URL`
|
||||
- `SYNOLOGY_NAS_HOST`
|
||||
- `SYNOLOGY_ALLOWED_USER_IDS` (comma-separated)
|
||||
- `SYNOLOGY_RATE_LIMIT`
|
||||
- `OPENCLAW_BOT_NAME`
|
||||
|
||||
Config values override env vars.
|
||||
|
||||
## DM policy and access control
|
||||
|
||||
- `dmPolicy: "allowlist"` is the recommended default.
|
||||
- `allowedUserIds` accepts a list (or comma-separated string) of Synology user IDs.
|
||||
- `dmPolicy: "open"` allows any sender.
|
||||
- `dmPolicy: "disabled"` blocks DMs.
|
||||
- Pairing approvals work with:
|
||||
- `openclaw pairing list synology-chat`
|
||||
- `openclaw pairing approve synology-chat <CODE>`
|
||||
|
||||
## Outbound delivery
|
||||
|
||||
Use numeric Synology Chat user IDs as targets.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
openclaw message send --channel synology-chat --target 123456 --text "Hello from OpenClaw"
|
||||
openclaw message send --channel synology-chat --target synology-chat:123456 --text "Hello again"
|
||||
```
|
||||
|
||||
Media sends are supported by URL-based file delivery.
|
||||
|
||||
## Multi-account
|
||||
|
||||
Multiple Synology Chat accounts are supported under `channels.synology-chat.accounts`.
|
||||
Each account can override token, incoming URL, webhook path, DM policy, and limits.
|
||||
|
||||
```json5
|
||||
{
|
||||
channels: {
|
||||
"synology-chat": {
|
||||
enabled: true,
|
||||
accounts: {
|
||||
default: {
|
||||
token: "token-a",
|
||||
incomingUrl: "https://nas-a.example.com/...token=...",
|
||||
},
|
||||
alerts: {
|
||||
token: "token-b",
|
||||
incomingUrl: "https://nas-b.example.com/...token=...",
|
||||
webhookPath: "/webhook/synology-alerts",
|
||||
dmPolicy: "allowlist",
|
||||
allowedUserIds: ["987654"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Security notes
|
||||
|
||||
- Keep `token` secret and rotate it if leaked.
|
||||
- Keep `allowInsecureSsl: false` unless you explicitly trust a self-signed local NAS cert.
|
||||
- Inbound webhook requests are token-verified and rate-limited per sender.
|
||||
- Prefer `dmPolicy: "allowlist"` for production.
|
||||
@@ -47,7 +47,6 @@ Status: production-ready for bot DMs + groups via grammY. Long polling is the de
|
||||
```
|
||||
|
||||
Env fallback: `TELEGRAM_BOT_TOKEN=...` (default account only).
|
||||
Telegram does **not** use `openclaw channels login telegram`; configure token in config/env, then start gateway.
|
||||
|
||||
</Step>
|
||||
|
||||
@@ -149,7 +148,6 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
|
||||
`groupAllowFrom` is used for group sender filtering. If not set, Telegram falls back to `allowFrom`.
|
||||
`groupAllowFrom` entries must be numeric Telegram user IDs.
|
||||
Runtime note: if `channels.telegram` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group policy evaluation (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Example: allow any member in one specific group:
|
||||
|
||||
@@ -672,29 +670,6 @@ openclaw message send --channel telegram --target @name --message "hi"
|
||||
|
||||
- Node 22+ + custom fetch/proxy can trigger immediate abort behavior if AbortSignal types mismatch.
|
||||
- Some hosts resolve `api.telegram.org` to IPv6 first; broken IPv6 egress can cause intermittent Telegram API failures.
|
||||
- If logs include `TypeError: fetch failed` or `Network request for 'getUpdates' failed!`, OpenClaw now retries these as recoverable network errors.
|
||||
- On VPS hosts with unstable direct egress/TLS, route Telegram API calls through `channels.telegram.proxy`:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
proxy: socks5://user:pass@proxy-host:1080
|
||||
```
|
||||
|
||||
- Node 22+ defaults to `autoSelectFamily=true` (except WSL2) and `dnsResultOrder=ipv4first`.
|
||||
- If your host is WSL2 or explicitly works better with IPv4-only behavior, force family selection:
|
||||
|
||||
```yaml
|
||||
channels:
|
||||
telegram:
|
||||
network:
|
||||
autoSelectFamily: false
|
||||
```
|
||||
|
||||
- Environment overrides (temporary):
|
||||
- `OPENCLAW_TELEGRAM_DISABLE_AUTO_SELECT_FAMILY=1`
|
||||
- `OPENCLAW_TELEGRAM_ENABLE_AUTO_SELECT_FAMILY=1`
|
||||
- `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER=ipv4first`
|
||||
- Validate DNS answers:
|
||||
|
||||
```bash
|
||||
@@ -737,8 +712,7 @@ Primary reference:
|
||||
- `channels.telegram.streaming`: `off | partial | block | progress` (live stream preview; default: `off`; `progress` maps to `partial`).
|
||||
- `channels.telegram.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to enabled on Node 22+, with WSL2 defaulting to disabled.
|
||||
- `channels.telegram.network.dnsResultOrder`: override DNS result order (`ipv4first` or `verbatim`). Defaults to `ipv4first` on Node 22+.
|
||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts.
|
||||
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
||||
- `channels.telegram.webhookUrl`: enable webhook mode (requires `channels.telegram.webhookSecret`).
|
||||
- `channels.telegram.webhookSecret`: webhook secret (required when webhookUrl is set).
|
||||
|
||||
@@ -171,7 +171,7 @@ OpenClaw recommends running WhatsApp on a separate number when possible. (The ch
|
||||
- if `groupAllowFrom` is unset, runtime falls back to `allowFrom` when available
|
||||
- sender allowlists are evaluated before mention/reply activation
|
||||
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is `allowlist` (with a warning log), even if `channels.defaults.groupPolicy` is set.
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is effectively `open`.
|
||||
|
||||
</Tab>
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
title: CI Pipeline
|
||||
description: How the OpenClaw CI pipeline works
|
||||
summary: "CI job graph, scope gates, and local command equivalents"
|
||||
read_when:
|
||||
- You need to understand why a CI job did or did not run
|
||||
- You are debugging failing GitHub Actions checks
|
||||
---
|
||||
|
||||
# CI Pipeline
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw clawbot` (legacy alias namespace)"
|
||||
read_when:
|
||||
- You maintain older scripts using `openclaw clawbot ...`
|
||||
- You need migration guidance to current commands
|
||||
title: "clawbot"
|
||||
---
|
||||
|
||||
# `openclaw clawbot`
|
||||
|
||||
Legacy alias namespace kept for backwards compatibility.
|
||||
|
||||
Current supported alias:
|
||||
|
||||
- `openclaw clawbot qr` (same behavior as [`openclaw qr`](/cli/qr))
|
||||
|
||||
## Migration
|
||||
|
||||
Prefer modern top-level commands directly:
|
||||
|
||||
- `openclaw clawbot qr` -> `openclaw qr`
|
||||
@@ -1,35 +0,0 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw completion` (generate/install shell completion scripts)"
|
||||
read_when:
|
||||
- You want shell completions for zsh/bash/fish/PowerShell
|
||||
- You need to cache completion scripts under OpenClaw state
|
||||
title: "completion"
|
||||
---
|
||||
|
||||
# `openclaw completion`
|
||||
|
||||
Generate shell completion scripts and optionally install them into your shell profile.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw completion
|
||||
openclaw completion --shell zsh
|
||||
openclaw completion --install
|
||||
openclaw completion --shell fish --install
|
||||
openclaw completion --write-state
|
||||
openclaw completion --shell bash --write-state
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `-s, --shell <shell>`: shell target (`zsh`, `bash`, `powershell`, `fish`; default: `zsh`)
|
||||
- `-i, --install`: install completion by adding a source line to your shell profile
|
||||
- `--write-state`: write completion script(s) to `$OPENCLAW_STATE_DIR/completions` without printing to stdout
|
||||
- `-y, --yes`: skip install confirmation prompts
|
||||
|
||||
## Notes
|
||||
|
||||
- `--install` writes a small "OpenClaw Completion" block into your shell profile and points it at the cached script.
|
||||
- Without `--install` or `--write-state`, the command prints the script to stdout.
|
||||
- Completion generation eagerly loads command trees so nested subcommands are included.
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw daemon` (legacy alias for gateway service management)"
|
||||
read_when:
|
||||
- You still use `openclaw daemon ...` in scripts
|
||||
- You need service lifecycle commands (install/start/stop/restart/status)
|
||||
title: "daemon"
|
||||
---
|
||||
|
||||
# `openclaw daemon`
|
||||
|
||||
Legacy alias for Gateway service management commands.
|
||||
|
||||
`openclaw daemon ...` maps to the same service control surface as `openclaw gateway ...` service commands.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw daemon status
|
||||
openclaw daemon install
|
||||
openclaw daemon start
|
||||
openclaw daemon stop
|
||||
openclaw daemon restart
|
||||
openclaw daemon uninstall
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
- `status`: show service install state and probe Gateway health
|
||||
- `install`: install service (`launchd`/`systemd`/`schtasks`)
|
||||
- `uninstall`: remove service
|
||||
- `start`: start service
|
||||
- `stop`: stop service
|
||||
- `restart`: restart service
|
||||
|
||||
## Common options
|
||||
|
||||
- `status`: `--url`, `--token`, `--password`, `--timeout`, `--no-probe`, `--deep`, `--json`
|
||||
- `install`: `--port`, `--runtime <node|bun>`, `--token`, `--force`, `--json`
|
||||
- lifecycle (`uninstall|start|stop|restart`): `--json`
|
||||
|
||||
## Prefer
|
||||
|
||||
Use [`openclaw gateway`](/cli/gateway) for current docs and examples.
|
||||
@@ -16,7 +16,6 @@ This page describes the current CLI behavior. If commands change, update this do
|
||||
- [`onboard`](/cli/onboard)
|
||||
- [`configure`](/cli/configure)
|
||||
- [`config`](/cli/config)
|
||||
- [`completion`](/cli/completion)
|
||||
- [`doctor`](/cli/doctor)
|
||||
- [`dashboard`](/cli/dashboard)
|
||||
- [`reset`](/cli/reset)
|
||||
@@ -34,7 +33,6 @@ This page describes the current CLI behavior. If commands change, update this do
|
||||
- [`system`](/cli/system)
|
||||
- [`models`](/cli/models)
|
||||
- [`memory`](/cli/memory)
|
||||
- [`directory`](/cli/directory)
|
||||
- [`nodes`](/cli/nodes)
|
||||
- [`devices`](/cli/devices)
|
||||
- [`node`](/cli/node)
|
||||
@@ -48,13 +46,10 @@ This page describes the current CLI behavior. If commands change, update this do
|
||||
- [`hooks`](/cli/hooks)
|
||||
- [`webhooks`](/cli/webhooks)
|
||||
- [`pairing`](/cli/pairing)
|
||||
- [`qr`](/cli/qr)
|
||||
- [`plugins`](/cli/plugins) (plugin commands)
|
||||
- [`channels`](/cli/channels)
|
||||
- [`security`](/cli/security)
|
||||
- [`skills`](/cli/skills)
|
||||
- [`daemon`](/cli/daemon) (legacy alias for gateway service commands)
|
||||
- [`clawbot`](/cli/clawbot) (legacy alias namespace)
|
||||
- [`voicecall`](/cli/voicecall) (plugin; if installed)
|
||||
|
||||
## Global flags
|
||||
@@ -99,9 +94,7 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
get
|
||||
set
|
||||
unset
|
||||
completion
|
||||
doctor
|
||||
dashboard
|
||||
security
|
||||
audit
|
||||
reset
|
||||
@@ -115,7 +108,6 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
remove
|
||||
login
|
||||
logout
|
||||
directory
|
||||
skills
|
||||
list
|
||||
info
|
||||
@@ -153,13 +145,6 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
stop
|
||||
restart
|
||||
run
|
||||
daemon
|
||||
status
|
||||
install
|
||||
uninstall
|
||||
start
|
||||
stop
|
||||
restart
|
||||
logs
|
||||
system
|
||||
event
|
||||
@@ -246,9 +231,6 @@ openclaw [--dev] [--profile <name>] <command>
|
||||
pairing
|
||||
list
|
||||
approve
|
||||
qr
|
||||
clawbot
|
||||
qr
|
||||
docs
|
||||
dns
|
||||
setup
|
||||
@@ -321,14 +303,13 @@ Options:
|
||||
- `--non-interactive`
|
||||
- `--mode <local|remote>`
|
||||
- `--flow <quickstart|advanced|manual>` (manual is an alias for advanced)
|
||||
- `--auth-choice <setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|moonshot-api-key-cn|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|mistral-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|custom-api-key|skip>`
|
||||
- `--auth-choice <setup-token|token|chutes|openai-codex|openai-api-key|openrouter-api-key|ai-gateway-api-key|moonshot-api-key|moonshot-api-key-cn|kimi-code-api-key|synthetic-api-key|venice-api-key|gemini-api-key|zai-api-key|apiKey|minimax-api|minimax-api-lightning|opencode-zen|custom-api-key|skip>`
|
||||
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token <token>` (non-interactive; used with `--auth-choice token`)
|
||||
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
|
||||
- `--token-expires-in <duration>` (non-interactive; e.g. `365d`, `12h`)
|
||||
- `--anthropic-api-key <key>`
|
||||
- `--openai-api-key <key>`
|
||||
- `--mistral-api-key <key>`
|
||||
- `--openrouter-api-key <key>`
|
||||
- `--ai-gateway-api-key <key>`
|
||||
- `--moonshot-api-key <key>`
|
||||
|
||||
@@ -69,7 +69,5 @@ Flags:
|
||||
- `--invoke-timeout <ms>`: node invoke timeout (default `30000`).
|
||||
- `--needs-screen-recording`: require screen recording permission.
|
||||
- `--raw <command>`: run a shell string (`/bin/sh -lc` or `cmd.exe /c`).
|
||||
In allowlist mode on Windows node hosts, `cmd.exe /c` shell-wrapper runs require approval
|
||||
(allowlist entry alone does not auto-allow the wrapper form).
|
||||
- `--agent <id>`: agent-scoped approvals/allowlists (defaults to configured agent).
|
||||
- `--ask <off|on-miss|always>`, `--security <deny|allowlist|full>`: overrides.
|
||||
|
||||
@@ -56,19 +56,10 @@ openclaw onboard --non-interactive \
|
||||
# --auth-choice zai-cn
|
||||
```
|
||||
|
||||
Non-interactive Mistral example:
|
||||
|
||||
```bash
|
||||
openclaw onboard --non-interactive \
|
||||
--auth-choice mistral-api-key \
|
||||
--mistral-api-key "$MISTRAL_API_KEY"
|
||||
```
|
||||
|
||||
Flow notes:
|
||||
|
||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||
- Local onboarding DM scope behavior: [CLI Onboarding Reference](/start/wizard-cli-reference#outputs-and-internals).
|
||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||
- Custom Provider: connect any OpenAI or Anthropic compatible endpoint,
|
||||
including hosted providers not listed. Use Unknown to auto-detect.
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
summary: "CLI reference for `openclaw qr` (generate iOS pairing QR + setup code)"
|
||||
read_when:
|
||||
- You want to pair the iOS app with a gateway quickly
|
||||
- You need setup-code output for remote/manual sharing
|
||||
title: "qr"
|
||||
---
|
||||
|
||||
# `openclaw qr`
|
||||
|
||||
Generate an iOS pairing QR and setup code from your current Gateway configuration.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
openclaw qr
|
||||
openclaw qr --setup-code-only
|
||||
openclaw qr --json
|
||||
openclaw qr --remote
|
||||
openclaw qr --url wss://gateway.example/ws --token '<token>'
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
- `--remote`: use `gateway.remote.url` plus remote token/password from config
|
||||
- `--url <url>`: override gateway URL used in payload
|
||||
- `--public-url <url>`: override public URL used in payload
|
||||
- `--token <token>`: override gateway token for payload
|
||||
- `--password <password>`: override gateway password for payload
|
||||
- `--setup-code-only`: print only setup code
|
||||
- `--no-ascii`: skip ASCII QR rendering
|
||||
- `--json`: emit JSON (`setupCode`, `gatewayUrl`, `auth`, `urlSource`)
|
||||
|
||||
## Notes
|
||||
|
||||
- `--token` and `--password` are mutually exclusive.
|
||||
- After scanning, approve device pairing with:
|
||||
- `openclaw devices list`
|
||||
- `openclaw devices approve <requestId>`
|
||||
@@ -28,13 +28,66 @@ This is for cooperative/shared inbox hardening. A single Gateway shared by mutua
|
||||
It also warns when small models (`<=300B`) are used without sandboxing and with web/browser tools enabled.
|
||||
For webhook ingress, it warns when `hooks.defaultSessionKey` is unset, when request `sessionKey` overrides are enabled, and when overrides are enabled without `hooks.allowedSessionKeyPrefixes`.
|
||||
It also warns when sandbox Docker settings are configured while sandbox mode is off, when `gateway.nodes.denyCommands` uses ineffective pattern-like/unknown entries, when `gateway.nodes.allowCommands` explicitly enables dangerous node commands, when global `tools.profile="minimal"` is overridden by agent tool profiles, when open groups expose runtime/filesystem tools without sandbox/workspace guards, and when installed extension plugin tools may be reachable under permissive tool policy.
|
||||
It also flags `gateway.allowRealIpFallback=true` (header-spoofing risk if proxies are misconfigured) and `discovery.mdns.mode="full"` (metadata leakage via mDNS TXT records).
|
||||
It also warns when sandbox browser uses Docker `bridge` network without `sandbox.browser.cdpSourceRange`.
|
||||
It also warns when existing sandbox browser Docker containers have missing/stale hash labels (for example pre-migration containers missing `openclaw.browserConfigEpoch`) and recommends `openclaw sandbox recreate --browser --all`.
|
||||
It also warns when npm-based plugin/hook install records are unpinned, missing integrity metadata, or drift from currently installed package versions.
|
||||
It warns when Discord allowlists (`channels.discord.allowFrom`, `channels.discord.guilds.*.users`, pairing store) use name or tag entries instead of stable IDs.
|
||||
It warns when `gateway.auth.mode="none"` leaves Gateway HTTP APIs reachable without a shared secret (`/tools/invoke` plus any enabled `/v1/*` endpoint).
|
||||
|
||||
## Skill security
|
||||
|
||||
Community skills (installed from ClawHub) are subject to additional security enforcement:
|
||||
|
||||
- **SKILL.md scanning**: content is scanned for prompt injection patterns, capability inflation, and boundary spoofing before entering the system prompt. Skills with critical findings are blocked from loading.
|
||||
- **Capability enforcement**: community skills must declare `capabilities` (e.g., `shell`, `network`) in frontmatter. Undeclared dangerous tool usage is blocked at runtime by the before-tool-call hook — a hard code gate that prompt injection cannot bypass.
|
||||
- **Command dispatch gating**: community skills using `command-dispatch: tool` can't dispatch to dangerous tools without the matching capability.
|
||||
- **Audit logging**: all security events are tagged with `category: "security"` and include session context for forensics. View in the web UI Logs tab using the Security filter.
|
||||
|
||||
See `openclaw skills check` for a runtime security overview, `openclaw skills info <name>` for per-skill details, and [Skills — Tool enforcement matrix](/tools/skills#tool-enforcement-matrix) for the complete tool-by-tool breakdown.
|
||||
|
||||
### Tool enforcement matrix
|
||||
|
||||
Every tool falls into one of three tiers when community skills are loaded:
|
||||
|
||||
**Always denied** — blocked unconditionally, no capability can override:
|
||||
|
||||
| Tool | Reason |
|
||||
|------|--------|
|
||||
| `gateway` | Control-plane reconfiguration (restart, shutdown, auth changes) |
|
||||
| `nodes` | Cluster node management (add/remove compute, redirect traffic) |
|
||||
|
||||
**Capability-gated** — blocked by default, allowed if the skill declares the matching capability:
|
||||
|
||||
| Capability | Tools | What it unlocks |
|
||||
|------------|-------|-----------------|
|
||||
| `shell` | `exec`, `process`, `lobster` | Run shell commands and manage processes |
|
||||
| `filesystem` | `write`, `edit`, `apply_patch` | File mutations (read is always allowed) |
|
||||
| `network` | `web_fetch`, `web_search` | Outbound HTTP requests |
|
||||
| `browser` | `browser` | Browser automation |
|
||||
| `sessions` | `sessions_spawn`, `sessions_send`, `subagents` | Cross-session orchestration |
|
||||
| `messaging` | `message` | Send messages to configured channels |
|
||||
| `scheduling` | `cron` | Schedule recurring jobs |
|
||||
|
||||
**Always allowed** — safe read-only or output-only tools, no capability required:
|
||||
|
||||
| Tool | Why safe |
|
||||
|------|---------|
|
||||
| `read` | Read-only file access |
|
||||
| `memory_search`, `memory_get` | Read-only memory access |
|
||||
| `agents_list` | List agents (read-only) |
|
||||
| `sessions_list`, `sessions_history`, `session_status` | Session introspection (read-only) |
|
||||
| `canvas` | UI rendering (output-only) |
|
||||
| `image` | Image generation (output-only) |
|
||||
| `tts` | Text-to-speech (output-only) |
|
||||
|
||||
A community skill with no capabilities declared gets access only to the always-allowed tier. Declare capabilities in SKILL.md frontmatter:
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
openclaw:
|
||||
capabilities: [shell, filesystem, network]
|
||||
```
|
||||
|
||||
## JSON output
|
||||
|
||||
Use `--json` for CI/policy checks:
|
||||
|
||||
@@ -18,9 +18,163 @@ Related:
|
||||
|
||||
## Commands
|
||||
|
||||
### `openclaw skills list`
|
||||
|
||||
List all skills with status, capabilities, and source.
|
||||
|
||||
```bash
|
||||
openclaw skills list
|
||||
openclaw skills list --eligible
|
||||
openclaw skills info <name>
|
||||
openclaw skills check
|
||||
openclaw skills list # all skills
|
||||
openclaw skills list --eligible # only ready-to-use skills
|
||||
openclaw skills list --json # JSON output
|
||||
openclaw skills list -v # verbose (show missing requirements)
|
||||
```
|
||||
|
||||
Output columns: **Status** (`+ ready`, `x missing`, `x blocked`), **Skill** (name + capability icons), **Description**, **Source**.
|
||||
|
||||
Capability icons displayed next to skill names:
|
||||
|
||||
| Icon | Capability |
|
||||
|------|-----------|
|
||||
| `>_` | `shell` — run shell commands |
|
||||
| `📂` | `filesystem` — read/write files |
|
||||
| `🌐` | `network` — outbound HTTP |
|
||||
| `🔍` | `browser` — browser automation |
|
||||
| `⚡` | `sessions` — cross-session orchestration |
|
||||
|
||||
Skills blocked by security scanning show `x blocked` instead of `x missing`.
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
Skills (10/12 ready)
|
||||
|
||||
Status Skill Description Source
|
||||
+ ready git-autopush >_ 🌐 Automate git workflows openclaw-managed
|
||||
+ ready think Extended thinking bundled
|
||||
+ ready peekaboo 🔍 ⚡ Browser peek and screenshot bundled
|
||||
x missing summarize >_ Summarize with CLI tool bundled
|
||||
x blocked evil-injector >_ Totally harmless skill openclaw-managed
|
||||
- disabled old-skill Deprecated skill workspace
|
||||
```
|
||||
|
||||
With `-v` (verbose), two extra columns appear — **Scan** and **Missing**:
|
||||
|
||||
```
|
||||
Status Skill Description Source Scan Missing
|
||||
+ ready git-autopush >_ 🌐 Automate git wor... openclaw-managed
|
||||
x missing summarize >_ Summarize with... bundled bins: summarize
|
||||
x blocked evil-injector >_ Totally harmless... openclaw-managed [blocked]
|
||||
+ ready sketch-tool 🌐 >_ Generate sketches openclaw-managed [warn]
|
||||
```
|
||||
|
||||
### `openclaw skills info <name>`
|
||||
|
||||
Show detailed information about a single skill including security status.
|
||||
|
||||
```bash
|
||||
openclaw skills info git-helper
|
||||
openclaw skills info git-helper --json
|
||||
```
|
||||
|
||||
Displays: description, source, file path, capabilities (with descriptions), security scan results, requirements (met/unmet), and install options.
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
git-autopush + Ready
|
||||
|
||||
Automate git commit, push, and PR workflows.
|
||||
|
||||
Source openclaw-managed
|
||||
Path ~/.openclaw/skills/git-autopush/SKILL.md
|
||||
Homepage https://github.com/example/git-autopush
|
||||
Primary env GH_TOKEN
|
||||
|
||||
Capabilities
|
||||
>_ shell Run shell commands
|
||||
🌐 network Make outbound HTTP requests
|
||||
|
||||
Security
|
||||
Scan + clean
|
||||
|
||||
Requirements
|
||||
bin git + ok
|
||||
bin gh + ok
|
||||
env GH_TOKEN + ok
|
||||
```
|
||||
|
||||
For a skill with missing requirements:
|
||||
|
||||
```
|
||||
summarize x Missing requirements
|
||||
|
||||
Summarize URLs and files using the summarize CLI.
|
||||
|
||||
Source bundled
|
||||
Path /opt/openclaw/skills/summarize/SKILL.md
|
||||
|
||||
Capabilities
|
||||
>_ shell Run shell commands
|
||||
|
||||
Security
|
||||
Scan + clean
|
||||
|
||||
Requirements
|
||||
bin summarize x missing
|
||||
|
||||
Install options
|
||||
brew Install summarize (brew install summarize)
|
||||
```
|
||||
|
||||
For a skill blocked by scanning:
|
||||
|
||||
```
|
||||
evil-injector x Blocked (security)
|
||||
|
||||
Totally harmless skill.
|
||||
|
||||
Source openclaw-managed
|
||||
Path ~/.openclaw/skills/evil-injector/SKILL.md
|
||||
|
||||
Capabilities
|
||||
>_ shell Run shell commands
|
||||
|
||||
Security
|
||||
Scan [blocked] prompt injection detected
|
||||
```
|
||||
|
||||
### `openclaw skills check`
|
||||
|
||||
Security-focused overview of all skills.
|
||||
|
||||
```bash
|
||||
openclaw skills check
|
||||
openclaw skills check --json
|
||||
```
|
||||
|
||||
Shows: total/eligible/disabled/blocked/missing counts, capabilities requested by community skills, runtime policy restrictions, and scan result summary.
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
Skills Status Check
|
||||
|
||||
Status Count
|
||||
Total 12
|
||||
Eligible 10
|
||||
Disabled 1
|
||||
Blocked (allowlist) 0
|
||||
Missing requirements 1
|
||||
|
||||
Community skill capabilities
|
||||
Icon Capability # Skills
|
||||
>_ shell 3 git-autopush, deploy-helper, node-runner
|
||||
📂 filesystem 2 git-autopush, file-editor
|
||||
🌐 network 2 git-autopush, sketch-tool
|
||||
|
||||
Scan results
|
||||
Result #
|
||||
Clean 11
|
||||
Warning 1
|
||||
Blocked 0
|
||||
```
|
||||
|
||||
@@ -21,7 +21,6 @@ openclaw update wizard
|
||||
openclaw update --channel beta
|
||||
openclaw update --channel dev
|
||||
openclaw update --tag beta
|
||||
openclaw update --dry-run
|
||||
openclaw update --no-restart
|
||||
openclaw update --json
|
||||
openclaw --update
|
||||
@@ -32,7 +31,6 @@ openclaw --update
|
||||
- `--no-restart`: skip restarting the Gateway service after a successful update.
|
||||
- `--channel <stable|beta|dev>`: set the update channel (git + npm; persisted in config).
|
||||
- `--tag <dist-tag|version>`: override the npm dist-tag or version for this update only.
|
||||
- `--dry-run`: preview planned update actions (channel/tag/target/restart flow) without writing config, installing, syncing plugins, or restarting.
|
||||
- `--json`: print machine-readable `UpdateRunResult` JSON.
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1200s).
|
||||
|
||||
@@ -68,8 +66,6 @@ install method aligned:
|
||||
updates it, and installs the global CLI from that checkout.
|
||||
- `stable`/`beta` → installs from npm using the matching dist-tag.
|
||||
|
||||
The Gateway core auto-updater (when enabled via config) reuses this same update path.
|
||||
|
||||
## Git checkout flow
|
||||
|
||||
Channels:
|
||||
|
||||
@@ -28,7 +28,7 @@ openclaw voicecall end --call-id <id>
|
||||
```bash
|
||||
openclaw voicecall expose --mode serve
|
||||
openclaw voicecall expose --mode funnel
|
||||
openclaw voicecall expose --mode off
|
||||
openclaw voicecall unexpose
|
||||
```
|
||||
|
||||
Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.
|
||||
|
||||
@@ -105,8 +105,7 @@ Defaults:
|
||||
2. `openai` if an OpenAI key can be resolved.
|
||||
3. `gemini` if a Gemini key can be resolved.
|
||||
4. `voyage` if a Voyage key can be resolved.
|
||||
5. `mistral` if a Mistral key can be resolved.
|
||||
6. Otherwise memory search stays disabled until configured.
|
||||
5. Otherwise memory search stays disabled until configured.
|
||||
- Local mode uses node-llama-cpp and may require `pnpm approve-builds`.
|
||||
- Uses sqlite-vec (when available) to accelerate vector search inside SQLite.
|
||||
|
||||
@@ -115,9 +114,7 @@ resolves keys from auth profiles, `models.providers.*.apiKey`, or environment
|
||||
variables. Codex OAuth only covers chat/completions and does **not** satisfy
|
||||
embeddings for memory search. For Gemini, use `GEMINI_API_KEY` or
|
||||
`models.providers.google.apiKey`. For Voyage, use `VOYAGE_API_KEY` or
|
||||
`models.providers.voyage.apiKey`. For Mistral, use `MISTRAL_API_KEY` or
|
||||
`models.providers.mistral.apiKey`.
|
||||
When using a custom OpenAI-compatible endpoint,
|
||||
`models.providers.voyage.apiKey`. When using a custom OpenAI-compatible endpoint,
|
||||
set `memorySearch.remote.apiKey` (and optional `memorySearch.remote.headers`).
|
||||
|
||||
### QMD backend (experimental)
|
||||
@@ -331,7 +328,7 @@ If you don't want to set an API key, use `memorySearch.provider = "local"` or se
|
||||
|
||||
Fallbacks:
|
||||
|
||||
- `memorySearch.fallback` can be `openai`, `gemini`, `voyage`, `mistral`, `local`, or `none`.
|
||||
- `memorySearch.fallback` can be `openai`, `gemini`, `local`, or `none`.
|
||||
- The fallback provider is only used when the primary embedding provider fails.
|
||||
|
||||
Batch indexing (OpenAI + Gemini + Voyage):
|
||||
|
||||
@@ -131,13 +131,11 @@ OpenClaw ships with the pi‑ai catalog. These providers require **no**
|
||||
- OpenRouter: `openrouter` (`OPENROUTER_API_KEY`)
|
||||
- Example model: `openrouter/anthropic/claude-sonnet-4-5`
|
||||
- xAI: `xai` (`XAI_API_KEY`)
|
||||
- Mistral: `mistral` (`MISTRAL_API_KEY`)
|
||||
- Example model: `mistral/mistral-large-latest`
|
||||
- CLI: `openclaw onboard --auth-choice mistral-api-key`
|
||||
- Groq: `groq` (`GROQ_API_KEY`)
|
||||
- Cerebras: `cerebras` (`CEREBRAS_API_KEY`)
|
||||
- GLM models on Cerebras use ids `zai-glm-4.7` and `zai-glm-4.6`.
|
||||
- OpenAI-compatible base URL: `https://api.cerebras.ai/v1`.
|
||||
- Mistral: `mistral` (`MISTRAL_API_KEY`)
|
||||
- GitHub Copilot: `github-copilot` (`COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN`)
|
||||
- Hugging Face Inference: `huggingface` (`HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN`) — OpenAI-compatible router; example model: `huggingface/deepseek-ai/DeepSeek-R1`; CLI: `openclaw onboard --auth-choice huggingface-api-key`. See [Hugging Face (Inference)](/providers/huggingface).
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ Use `session.dmScope` to control how **direct messages** are grouped:
|
||||
Notes:
|
||||
|
||||
- Default is `dmScope: "main"` for continuity (all DMs share the main session). This is fine for single-user setups.
|
||||
- Local CLI onboarding writes `session.dmScope: "per-channel-peer"` by default when unset (existing explicit values are preserved).
|
||||
- For multi-account inboxes on the same channel, prefer `per-account-channel-peer`.
|
||||
- If the same person contacts you on multiple channels, use `session.identityLinks` to collapse their DM sessions into one canonical identity.
|
||||
- You can verify your DM settings with `openclaw security audit` (see [security](/cli/security)).
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
summary: "Alias for session management docs"
|
||||
read_when:
|
||||
- You looked for docs/concepts/sessions.md; canonical doc lives in docs/concepts/session.md
|
||||
- You looked for docs/sessions.md; canonical doc lives in docs/session.md
|
||||
title: "Sessions"
|
||||
---
|
||||
|
||||
|
||||
@@ -91,10 +91,6 @@
|
||||
"source": "/moonshot",
|
||||
"destination": "/providers/moonshot"
|
||||
},
|
||||
{
|
||||
"source": "/mistral",
|
||||
"destination": "/providers/mistral"
|
||||
},
|
||||
{
|
||||
"source": "/openrouter",
|
||||
"destination": "/providers/openrouter"
|
||||
@@ -275,10 +271,6 @@
|
||||
"source": "/start/clawd/",
|
||||
"destination": "/start/openclaw"
|
||||
},
|
||||
{
|
||||
"source": "/start/pairing",
|
||||
"destination": "/channels/pairing"
|
||||
},
|
||||
{
|
||||
"source": "/clawhub",
|
||||
"destination": "/tools/clawhub"
|
||||
@@ -532,12 +524,12 @@
|
||||
"destination": "/channels/pairing"
|
||||
},
|
||||
{
|
||||
"source": "/experiments/plans/cron-add-hardening",
|
||||
"destination": "/automation/cron-jobs"
|
||||
"source": "/plans/cron-add-hardening",
|
||||
"destination": "/experiments/plans/cron-add-hardening"
|
||||
},
|
||||
{
|
||||
"source": "/experiments/plans/group-policy-hardening",
|
||||
"destination": "/channels/groups"
|
||||
"source": "/plans/group-policy-hardening",
|
||||
"destination": "/experiments/plans/group-policy-hardening"
|
||||
},
|
||||
{
|
||||
"source": "/poll",
|
||||
@@ -899,15 +891,9 @@
|
||||
"channels/mattermost",
|
||||
"channels/signal",
|
||||
"channels/imessage",
|
||||
"channels/bluebubbles",
|
||||
"channels/msteams",
|
||||
"channels/synology-chat",
|
||||
"channels/line",
|
||||
"channels/matrix",
|
||||
"channels/nextcloud-talk",
|
||||
"channels/nostr",
|
||||
"channels/tlon",
|
||||
"channels/twitch",
|
||||
"channels/zalo",
|
||||
"channels/zalouser"
|
||||
]
|
||||
@@ -1071,7 +1057,6 @@
|
||||
"providers/bedrock",
|
||||
"providers/vercel-ai-gateway",
|
||||
"providers/moonshot",
|
||||
"providers/mistral",
|
||||
"providers/minimax",
|
||||
"providers/opencode",
|
||||
"providers/glm",
|
||||
@@ -1197,20 +1182,14 @@
|
||||
"group": "CLI commands",
|
||||
"pages": [
|
||||
"cli/index",
|
||||
"cli/acp",
|
||||
"cli/agent",
|
||||
"cli/agents",
|
||||
"cli/approvals",
|
||||
"cli/browser",
|
||||
"cli/channels",
|
||||
"cli/clawbot",
|
||||
"cli/completion",
|
||||
"cli/config",
|
||||
"cli/configure",
|
||||
"cli/cron",
|
||||
"cli/daemon",
|
||||
"cli/dashboard",
|
||||
"cli/devices",
|
||||
"cli/directory",
|
||||
"cli/dns",
|
||||
"cli/docs",
|
||||
@@ -1222,12 +1201,10 @@
|
||||
"cli/memory",
|
||||
"cli/message",
|
||||
"cli/models",
|
||||
"cli/node",
|
||||
"cli/nodes",
|
||||
"cli/onboard",
|
||||
"cli/pairing",
|
||||
"cli/plugins",
|
||||
"cli/qr",
|
||||
"cli/reset",
|
||||
"cli/sandbox",
|
||||
"cli/security",
|
||||
@@ -1239,8 +1216,7 @@
|
||||
"cli/tui",
|
||||
"cli/uninstall",
|
||||
"cli/update",
|
||||
"cli/voicecall",
|
||||
"cli/webhooks"
|
||||
"cli/voicecall"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -1287,6 +1263,8 @@
|
||||
"group": "Experiments",
|
||||
"pages": [
|
||||
"experiments/onboarding-config-protocol",
|
||||
"experiments/plans/cron-add-hardening",
|
||||
"experiments/plans/group-policy-hardening",
|
||||
"experiments/research/memory",
|
||||
"experiments/proposals/model-config"
|
||||
]
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
summary: "Plan: isolate browser act:evaluate from Playwright queue using CDP, with end-to-end deadlines and safer ref resolution"
|
||||
read_when:
|
||||
- Working on browser `act:evaluate` timeout, abort, or queue blocking issues
|
||||
- Planning CDP based isolation for evaluate execution
|
||||
owner: "openclaw"
|
||||
status: "draft"
|
||||
last_updated: "2026-02-10"
|
||||
|
||||
63
docs/experiments/plans/cron-add-hardening.md
Normal file
63
docs/experiments/plans/cron-add-hardening.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
summary: "Harden cron.add input handling, align schemas, and improve cron UI/agent tooling"
|
||||
owner: "openclaw"
|
||||
status: "complete"
|
||||
last_updated: "2026-01-05"
|
||||
title: "Cron Add Hardening"
|
||||
---
|
||||
|
||||
# Cron Add Hardening & Schema Alignment
|
||||
|
||||
## Context
|
||||
|
||||
Recent gateway logs show repeated `cron.add` failures with invalid parameters (missing `sessionTarget`, `wakeMode`, `payload`, and malformed `schedule`). This indicates that at least one client (likely the agent tool call path) is sending wrapped or partially specified job payloads. Separately, there is drift between cron provider enums in TypeScript, gateway schema, CLI flags, and UI form types, plus a UI mismatch for `cron.status` (expects `jobCount` while gateway returns `jobs`).
|
||||
|
||||
## Goals
|
||||
|
||||
- Stop `cron.add` INVALID_REQUEST spam by normalizing common wrapper payloads and inferring missing `kind` fields.
|
||||
- Align cron provider lists across gateway schema, cron types, CLI docs, and UI forms.
|
||||
- Make agent cron tool schema explicit so the LLM produces correct job payloads.
|
||||
- Fix the Control UI cron status job count display.
|
||||
- Add tests to cover normalization and tool behavior.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Change cron scheduling semantics or job execution behavior.
|
||||
- Add new schedule kinds or cron expression parsing.
|
||||
- Overhaul the UI/UX for cron beyond the necessary field fixes.
|
||||
|
||||
## Findings (current gaps)
|
||||
|
||||
- `CronPayloadSchema` in gateway excludes `signal` + `imessage`, while TS types include them.
|
||||
- Control UI CronStatus expects `jobCount`, but gateway returns `jobs`.
|
||||
- Agent cron tool schema allows arbitrary `job` objects, enabling malformed inputs.
|
||||
- Gateway strictly validates `cron.add` with no normalization, so wrapped payloads fail.
|
||||
|
||||
## What changed
|
||||
|
||||
- `cron.add` and `cron.update` now normalize common wrapper shapes and infer missing `kind` fields.
|
||||
- Agent cron tool schema matches the gateway schema, which reduces invalid payloads.
|
||||
- Provider enums are aligned across gateway, CLI, UI, and macOS picker.
|
||||
- Control UI uses the gateway’s `jobs` count field for status.
|
||||
|
||||
## Current behavior
|
||||
|
||||
- **Normalization:** wrapped `data`/`job` payloads are unwrapped; `schedule.kind` and `payload.kind` are inferred when safe.
|
||||
- **Defaults:** safe defaults are applied for `wakeMode` and `sessionTarget` when missing.
|
||||
- **Providers:** Discord/Slack/Signal/iMessage are now consistently surfaced across CLI/UI.
|
||||
|
||||
See [Cron jobs](/automation/cron-jobs) for the normalized shape and examples.
|
||||
|
||||
## Verification
|
||||
|
||||
- Watch gateway logs for reduced `cron.add` INVALID_REQUEST errors.
|
||||
- Confirm Control UI cron status shows job count after refresh.
|
||||
|
||||
## Optional Follow-ups
|
||||
|
||||
- Manual Control UI smoke: add a cron job per provider + verify status job count.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Should `cron.add` accept explicit `state` from clients (currently disallowed by schema)?
|
||||
- Should we allow `webchat` as an explicit delivery provider (currently filtered in delivery resolution)?
|
||||
40
docs/experiments/plans/group-policy-hardening.md
Normal file
40
docs/experiments/plans/group-policy-hardening.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
summary: "Telegram allowlist hardening: prefix + whitespace normalization"
|
||||
read_when:
|
||||
- Reviewing historical Telegram allowlist changes
|
||||
title: "Telegram Allowlist Hardening"
|
||||
---
|
||||
|
||||
# Telegram Allowlist Hardening
|
||||
|
||||
**Date**: 2026-01-05
|
||||
**Status**: Complete
|
||||
**PR**: #216
|
||||
|
||||
## Summary
|
||||
|
||||
Telegram allowlists now accept `telegram:` and `tg:` prefixes case-insensitively, and tolerate
|
||||
accidental whitespace. This aligns inbound allowlist checks with outbound send normalization.
|
||||
|
||||
## What changed
|
||||
|
||||
- Prefixes `telegram:` and `tg:` are treated the same (case-insensitive).
|
||||
- Allowlist entries are trimmed; empty entries are ignored.
|
||||
|
||||
## Examples
|
||||
|
||||
All of these are accepted for the same ID:
|
||||
|
||||
- `telegram:123456`
|
||||
- `TG:123456`
|
||||
- `tg:123456`
|
||||
|
||||
## Why it matters
|
||||
|
||||
Copy/paste from logs or chat IDs often includes prefixes and whitespace. Normalizing avoids
|
||||
false negatives when deciding whether to respond in DMs or groups.
|
||||
|
||||
## Related docs
|
||||
|
||||
- [Group Chats](/channels/groups)
|
||||
- [Telegram Provider](/channels/telegram)
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
summary: "Plan: Add OpenResponses /v1/responses endpoint and deprecate chat completions cleanly"
|
||||
read_when:
|
||||
- Designing or implementing `/v1/responses` gateway support
|
||||
- Planning migration from Chat Completions compatibility
|
||||
owner: "openclaw"
|
||||
status: "draft"
|
||||
last_updated: "2026-01-19"
|
||||
@@ -116,7 +113,7 @@ Key points extracted:
|
||||
- Non-stream response shape
|
||||
- Stream event ordering and `[DONE]`
|
||||
- Session routing with headers and `user`
|
||||
- Keep `src/gateway/openai-http.test.ts` unchanged.
|
||||
- Keep `src/gateway/openai-http.e2e.test.ts` unchanged.
|
||||
- Manual: curl to `/v1/responses` with `stream: true` and verify event ordering and terminal
|
||||
`[DONE]`.
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
summary: "Production plan for reliable interactive process supervision (PTY + non-PTY) with explicit ownership, unified lifecycle, and deterministic cleanup"
|
||||
read_when:
|
||||
- Working on exec/process lifecycle ownership and cleanup
|
||||
- Debugging PTY and non-PTY supervision behavior
|
||||
owner: "openclaw"
|
||||
status: "in-progress"
|
||||
last_updated: "2026-02-15"
|
||||
@@ -159,12 +156,12 @@ Unit tests:
|
||||
|
||||
E2E targets:
|
||||
|
||||
- `pnpm vitest src/agents/cli-runner.test.ts`
|
||||
- `pnpm vitest run src/agents/bash-tools.exec.pty-fallback.test.ts src/agents/bash-tools.exec.background-abort.test.ts src/agents/bash-tools.process.send-keys.test.ts`
|
||||
- `pnpm test:e2e src/agents/cli-runner.e2e.test.ts`
|
||||
- `pnpm test:e2e src/agents/bash-tools.exec.pty-fallback.e2e.test.ts src/agents/bash-tools.exec.background-abort.e2e.test.ts src/agents/bash-tools.process.send-keys.e2e.test.ts`
|
||||
|
||||
Typecheck note:
|
||||
|
||||
- Use `pnpm build` (and `pnpm check` for full lint/docs gate) in this repo. Older notes that mention `pnpm tsgo` are obsolete.
|
||||
- `pnpm tsgo` currently fails in this repo due to a pre-existing UI typing dependency issue (`@vitest/browser-playwright` resolution), unrelated to this process supervision work.
|
||||
|
||||
## 8. Operational guarantees preserved
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
summary: "Channel agnostic session binding architecture and iteration 1 delivery scope"
|
||||
read_when:
|
||||
- Refactoring channel-agnostic session routing and bindings
|
||||
- Investigating duplicate, stale, or missing session delivery across channels
|
||||
owner: "onutc"
|
||||
status: "in-progress"
|
||||
last_updated: "2026-02-21"
|
||||
@@ -215,7 +212,7 @@ Tests:
|
||||
|
||||
- `src/discord/monitor/provider*.test.ts`
|
||||
- `src/discord/monitor/reply-delivery.test.ts`
|
||||
- `src/agents/subagent-announce.format.test.ts`
|
||||
- `src/agents/subagent-announce.format.e2e.test.ts`
|
||||
|
||||
## Done criteria for iteration 1
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
title: "Configuration Reference"
|
||||
description: "Complete field-by-field reference for ~/.openclaw/openclaw.json"
|
||||
summary: "Complete reference for every OpenClaw config key, defaults, and channel settings"
|
||||
read_when:
|
||||
- You need exact field-level config semantics or defaults
|
||||
- You are validating channel, model, gateway, or tool config blocks
|
||||
---
|
||||
|
||||
# Configuration Reference
|
||||
@@ -39,7 +35,7 @@ All channels support DM policies and group policies:
|
||||
<Note>
|
||||
`channels.defaults.groupPolicy` sets the default when a provider's `groupPolicy` is unset.
|
||||
Pairing codes expire after 1 hour. Pending DM pairing requests are capped at **3 per channel**.
|
||||
If a provider block is missing entirely (`channels.<provider>` absent), runtime group policy falls back to `allowlist` (fail-closed) with a startup warning.
|
||||
Slack/Discord have a special fallback: if their provider section is missing entirely, runtime group policy can resolve to `open` (with a startup warning).
|
||||
</Note>
|
||||
|
||||
### Channel model overrides
|
||||
@@ -165,10 +161,7 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
|
||||
maxDelayMs: 30000,
|
||||
jitter: 0.1,
|
||||
},
|
||||
network: {
|
||||
autoSelectFamily: true,
|
||||
dnsResultOrder: "ipv4first",
|
||||
},
|
||||
network: { autoSelectFamily: false },
|
||||
proxy: "socks5://localhost:9050",
|
||||
webhookUrl: "https://example.com/telegram-webhook",
|
||||
webhookSecret: "secret",
|
||||
|
||||
@@ -101,20 +101,6 @@ You can persist a remote target so CLI commands use it by default:
|
||||
|
||||
When the gateway is loopback-only, keep the URL at `ws://127.0.0.1:18789` and open the SSH tunnel first.
|
||||
|
||||
## Credential precedence
|
||||
|
||||
Gateway call/probe credential resolution now follows one shared contract:
|
||||
|
||||
- Explicit credentials (`--token`, `--password`, or tool `gatewayToken`) always win.
|
||||
- Local mode defaults:
|
||||
- token: `OPENCLAW_GATEWAY_TOKEN` -> `gateway.auth.token`
|
||||
- password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway.auth.password`
|
||||
- Remote mode defaults:
|
||||
- token: `gateway.remote.token` -> `OPENCLAW_GATEWAY_TOKEN` -> `gateway.auth.token`
|
||||
- password: `OPENCLAW_GATEWAY_PASSWORD` -> `gateway.remote.password` -> `gateway.auth.password`
|
||||
- Remote probe/status token checks are strict by default: they use `gateway.remote.token` only (no local token fallback) when targeting remote mode.
|
||||
- Legacy `CLAWDBOT_GATEWAY_*` env vars are only used by compatibility call paths; probe/status/auth resolution uses `OPENCLAW_GATEWAY_*` only.
|
||||
|
||||
## Chat UI over SSH
|
||||
|
||||
WebChat no longer uses a separate HTTP port. The SwiftUI chat UI connects directly to the Gateway WebSocket.
|
||||
|
||||
@@ -117,34 +117,31 @@ When the audit prints findings, treat this as a priority order:
|
||||
|
||||
High-signal `checkId` values you will most likely see in real deployments (not exhaustive):
|
||||
|
||||
| `checkId` | Severity | Why it matters | Primary fix key/path | Auto-fix |
|
||||
| -------------------------------------------------- | ------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `fs.state_dir.perms_world_writable` | critical | Other users/processes can modify full OpenClaw state | filesystem perms on `~/.openclaw` | yes |
|
||||
| `fs.config.perms_writable` | critical | Others can change auth/tool policy/config | filesystem perms on `~/.openclaw/openclaw.json` | yes |
|
||||
| `fs.config.perms_world_readable` | critical | Config can expose tokens/settings | filesystem perms on config file | yes |
|
||||
| `gateway.bind_no_auth` | critical | Remote bind without shared secret | `gateway.bind`, `gateway.auth.*` | no |
|
||||
| `gateway.loopback_no_auth` | critical | Reverse-proxied loopback may become unauthenticated | `gateway.auth.*`, proxy setup | no |
|
||||
| `gateway.http.no_auth` | warn/critical | Gateway HTTP APIs reachable with `auth.mode="none"` | `gateway.auth.mode`, `gateway.http.endpoints.*` | no |
|
||||
| `gateway.tools_invoke_http.dangerous_allow` | warn/critical | Re-enables dangerous tools over HTTP API | `gateway.tools.allow` | no |
|
||||
| `gateway.nodes.allow_commands_dangerous` | warn/critical | Enables high-impact node commands (camera/screen/contacts/calendar/SMS) | `gateway.nodes.allowCommands` | no |
|
||||
| `gateway.tailscale_funnel` | critical | Public internet exposure | `gateway.tailscale.mode` | no |
|
||||
| `gateway.control_ui.insecure_auth` | warn | Insecure-auth compatibility toggle enabled | `gateway.controlUi.allowInsecureAuth` | no |
|
||||
| `gateway.control_ui.device_auth_disabled` | critical | Disables device identity check | `gateway.controlUi.dangerouslyDisableDeviceAuth` | no |
|
||||
| `gateway.real_ip_fallback_enabled` | warn/critical | Trusting `X-Real-IP` fallback can enable source-IP spoofing via proxy misconfig | `gateway.allowRealIpFallback`, `gateway.trustedProxies` | no |
|
||||
| `discovery.mdns_full_mode` | warn/critical | mDNS full mode advertises `cliPath`/`sshPort` metadata on local network | `discovery.mdns.mode`, `gateway.bind` | no |
|
||||
| `config.insecure_or_dangerous_flags` | warn | Any insecure/dangerous debug flags enabled | multiple keys (see finding detail) | no |
|
||||
| `hooks.token_too_short` | warn | Easier brute force on hook ingress | `hooks.token` | no |
|
||||
| `hooks.request_session_key_enabled` | warn/critical | External caller can choose sessionKey | `hooks.allowRequestSessionKey` | no |
|
||||
| `hooks.request_session_key_prefixes_missing` | warn/critical | No bound on external session key shapes | `hooks.allowedSessionKeyPrefixes` | no |
|
||||
| `logging.redact_off` | warn | Sensitive values leak to logs/status | `logging.redactSensitive` | yes |
|
||||
| `sandbox.docker_config_mode_off` | warn | Sandbox Docker config present but inactive | `agents.*.sandbox.mode` | no |
|
||||
| `tools.exec.host_sandbox_no_sandbox_defaults` | warn | `exec host=sandbox` resolves to host exec when sandbox is off | `tools.exec.host`, `agents.defaults.sandbox.mode` | no |
|
||||
| `tools.exec.host_sandbox_no_sandbox_agents` | warn | Per-agent `exec host=sandbox` resolves to host exec when sandbox is off | `agents.list[].tools.exec.host`, `agents.list[].sandbox.mode` | no |
|
||||
| `tools.exec.safe_bins_interpreter_unprofiled` | warn | Interpreter/runtime bins in `safeBins` without explicit profiles broaden exec risk | `tools.exec.safeBins`, `tools.exec.safeBinProfiles`, `agents.list[].tools.exec.*` | no |
|
||||
| `security.exposure.open_groups_with_runtime_or_fs` | critical/warn | Open groups can reach command/file tools without sandbox/workspace guards | `channels.*.groupPolicy`, `tools.profile/deny`, `tools.fs.workspaceOnly`, `agents.*.sandbox.mode` | no |
|
||||
| `tools.profile_minimal_overridden` | warn | Agent overrides bypass global minimal profile | `agents.list[].tools.profile` | no |
|
||||
| `plugins.tools_reachable_permissive_policy` | warn | Extension tools reachable in permissive contexts | `tools.profile` + tool allow/deny | no |
|
||||
| `models.small_params` | critical/info | Small models + unsafe tool surfaces raise injection risk | model choice + sandbox/tool policy | no |
|
||||
| `checkId` | Severity | Why it matters | Primary fix key/path | Auto-fix |
|
||||
| -------------------------------------------------- | ------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | -------- |
|
||||
| `fs.state_dir.perms_world_writable` | critical | Other users/processes can modify full OpenClaw state | filesystem perms on `~/.openclaw` | yes |
|
||||
| `fs.config.perms_writable` | critical | Others can change auth/tool policy/config | filesystem perms on `~/.openclaw/openclaw.json` | yes |
|
||||
| `fs.config.perms_world_readable` | critical | Config can expose tokens/settings | filesystem perms on config file | yes |
|
||||
| `gateway.bind_no_auth` | critical | Remote bind without shared secret | `gateway.bind`, `gateway.auth.*` | no |
|
||||
| `gateway.loopback_no_auth` | critical | Reverse-proxied loopback may become unauthenticated | `gateway.auth.*`, proxy setup | no |
|
||||
| `gateway.http.no_auth` | warn/critical | Gateway HTTP APIs reachable with `auth.mode="none"` | `gateway.auth.mode`, `gateway.http.endpoints.*` | no |
|
||||
| `gateway.tools_invoke_http.dangerous_allow` | warn/critical | Re-enables dangerous tools over HTTP API | `gateway.tools.allow` | no |
|
||||
| `gateway.nodes.allow_commands_dangerous` | warn/critical | Enables high-impact node commands (camera/screen/contacts/calendar/SMS) | `gateway.nodes.allowCommands` | no |
|
||||
| `gateway.tailscale_funnel` | critical | Public internet exposure | `gateway.tailscale.mode` | no |
|
||||
| `gateway.control_ui.insecure_auth` | warn | Insecure-auth compatibility toggle enabled | `gateway.controlUi.allowInsecureAuth` | no |
|
||||
| `gateway.control_ui.device_auth_disabled` | critical | Disables device identity check | `gateway.controlUi.dangerouslyDisableDeviceAuth` | no |
|
||||
| `config.insecure_or_dangerous_flags` | warn | Any insecure/dangerous debug flags enabled | multiple keys (see finding detail) | no |
|
||||
| `hooks.token_too_short` | warn | Easier brute force on hook ingress | `hooks.token` | no |
|
||||
| `hooks.request_session_key_enabled` | warn/critical | External caller can choose sessionKey | `hooks.allowRequestSessionKey` | no |
|
||||
| `hooks.request_session_key_prefixes_missing` | warn/critical | No bound on external session key shapes | `hooks.allowedSessionKeyPrefixes` | no |
|
||||
| `logging.redact_off` | warn | Sensitive values leak to logs/status | `logging.redactSensitive` | yes |
|
||||
| `sandbox.docker_config_mode_off` | warn | Sandbox Docker config present but inactive | `agents.*.sandbox.mode` | no |
|
||||
| `tools.exec.host_sandbox_no_sandbox_defaults` | warn | `exec host=sandbox` resolves to host exec when sandbox is off | `tools.exec.host`, `agents.defaults.sandbox.mode` | no |
|
||||
| `tools.exec.host_sandbox_no_sandbox_agents` | warn | Per-agent `exec host=sandbox` resolves to host exec when sandbox is off | `agents.list[].tools.exec.host`, `agents.list[].sandbox.mode` | no |
|
||||
| `security.exposure.open_groups_with_runtime_or_fs` | critical/warn | Open groups can reach command/file tools without sandbox/workspace guards | `channels.*.groupPolicy`, `tools.profile/deny`, `tools.fs.workspaceOnly`, `agents.*.sandbox.mode` | no |
|
||||
| `tools.profile_minimal_overridden` | warn | Agent overrides bypass global minimal profile | `agents.list[].tools.profile` | no |
|
||||
| `plugins.tools_reachable_permissive_policy` | warn | Extension tools reachable in permissive contexts | `tools.profile` + tool allow/deny | no |
|
||||
| `models.small_params` | critical/info | Small models + unsafe tool surfaces raise injection risk | model choice + sandbox/tool policy | no |
|
||||
|
||||
## Control UI over HTTP
|
||||
|
||||
@@ -218,6 +215,18 @@ If a macOS node is paired, the Gateway can invoke `system.run` on that node. Thi
|
||||
- Controlled on the Mac via **Settings → Exec approvals** (security + ask + allowlist).
|
||||
- If you don’t want remote execution, set security to **deny** and remove node pairing for that Mac.
|
||||
|
||||
## Skill security
|
||||
|
||||
Community skills (installed from ClawHub) are subject to runtime security enforcement:
|
||||
|
||||
- **Capabilities**: Skills declare what system access they need (`shell`, `filesystem`, `network`, `browser`, `sessions`) in `metadata.openclaw.capabilities`. No capabilities = read-only. Community skills that use tools without declaring the matching capability are blocked at runtime.
|
||||
- **SKILL.md scanning**: Content is scanned for prompt injection patterns, capability inflation, and boundary spoofing before entering the system prompt. Skills with critical findings are blocked from loading.
|
||||
- **Trust tiers**: Skills are classified as `builtin`, `community`, or `local`. Only `community` skills (installed from ClawHub) are subject to enforcement — builtin and local skills are exempt. Author verification may be introduced in a future release to provide an additional trust signal.
|
||||
- **Command dispatch gating**: Community skills using `command-dispatch: tool` can't dispatch to dangerous tools without declaring the matching capability.
|
||||
- **Audit logging**: All security events are tagged with `category: "security"` and include session context.
|
||||
|
||||
Use `openclaw skills check` for a security overview and `openclaw skills info <name>` for per-skill details. See [Skills CLI](/cli/skills) for full command reference.
|
||||
|
||||
## Dynamic skills (watcher / remote nodes)
|
||||
|
||||
OpenClaw can refresh the skills list mid-session:
|
||||
@@ -225,7 +234,7 @@ OpenClaw can refresh the skills list mid-session:
|
||||
- **Skills watcher**: changes to `SKILL.md` can update the skills snapshot on the next agent turn.
|
||||
- **Remote nodes**: connecting a macOS node can make macOS-only skills eligible (based on bin probing).
|
||||
|
||||
Treat skill folders as **trusted code** and restrict who can modify them.
|
||||
Restrict who can modify skill folders. Community skills are subject to scanning and capability enforcement (see above), but local and workspace skills are treated as trusted — if someone can write to your skill folders, they can inject instructions into the system prompt.
|
||||
|
||||
## The Threat Model
|
||||
|
||||
@@ -333,7 +342,6 @@ This is a messaging-context boundary, not a host-admin boundary. If users are mu
|
||||
Treat the snippet above as **secure DM mode**:
|
||||
|
||||
- Default: `session.dmScope: "main"` (all DMs share one session for continuity).
|
||||
- Local CLI onboarding default: writes `session.dmScope: "per-channel-peer"` when unset (keeps existing explicit values).
|
||||
- Secure DM mode: `session.dmScope: "per-channel-peer"` (each channel+sender pair gets an isolated DM context).
|
||||
|
||||
If you run multiple accounts on the same channel, use `per-account-channel-peer` instead. If the same person contacts you on multiple channels, use `session.identityLinks` to collapse those DM sessions into one canonical identity. See [Session Management](/concepts/session) and [Configuration](/gateway/configuration).
|
||||
|
||||
@@ -82,12 +82,6 @@ See [Configuration: Env var substitution](/gateway/configuration#env-var-substit
|
||||
| `OPENCLAW_STATE_DIR` | Override the state directory (default `~/.openclaw`). |
|
||||
| `OPENCLAW_CONFIG_PATH` | Override the config file path (default `~/.openclaw/openclaw.json`). |
|
||||
|
||||
## Logging
|
||||
|
||||
| Variable | Purpose |
|
||||
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `OPENCLAW_LOG_LEVEL` | Override log level for both file and console (e.g. `debug`, `trace`). Takes precedence over `logging.level` and `logging.consoleLevel` in config. Invalid values are ignored with a warning. |
|
||||
|
||||
### `OPENCLAW_HOME`
|
||||
|
||||
When set, `OPENCLAW_HOME` replaces the system home directory (`$HOME` / `os.homedir()`) for all internal path resolution. This enables full filesystem isolation for headless service accounts.
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
---
|
||||
summary: "Frequently asked questions about OpenClaw setup, configuration, and usage"
|
||||
read_when:
|
||||
- Answering common setup, install, onboarding, or runtime support questions
|
||||
- Triaging user-reported issues before deeper debugging
|
||||
title: "FAQ"
|
||||
---
|
||||
|
||||
@@ -1251,15 +1248,14 @@ still need a real API key (`OPENAI_API_KEY` or `models.providers.openai.apiKey`)
|
||||
If you don't set a provider explicitly, OpenClaw auto-selects a provider when it
|
||||
can resolve an API key (auth profiles, `models.providers.*.apiKey`, or env vars).
|
||||
It prefers OpenAI if an OpenAI key resolves, otherwise Gemini if a Gemini key
|
||||
resolves, then Voyage, then Mistral. If no remote key is available, memory
|
||||
search stays disabled until you configure it. If you have a local model path
|
||||
configured and present, OpenClaw
|
||||
resolves. If neither key is available, memory search stays disabled until you
|
||||
configure it. If you have a local model path configured and present, OpenClaw
|
||||
prefers `local`.
|
||||
|
||||
If you'd rather stay local, set `memorySearch.provider = "local"` (and optionally
|
||||
`memorySearch.fallback = "none"`). If you want Gemini embeddings, set
|
||||
`memorySearch.provider = "gemini"` and provide `GEMINI_API_KEY` (or
|
||||
`memorySearch.remote.apiKey`). We support **OpenAI, Gemini, Voyage, Mistral, or local** embedding
|
||||
`memorySearch.remote.apiKey`). We support **OpenAI, Gemini, or local** embedding
|
||||
models - see [Memory](/concepts/memory) for the setup details.
|
||||
|
||||
### Does memory persist forever What are the limits
|
||||
|
||||
@@ -352,15 +352,15 @@ Run docs checks after doc edits: `pnpm docs:list`.
|
||||
|
||||
These are “real pipeline” regressions without real providers:
|
||||
|
||||
- Gateway tool calling (mock OpenAI, real gateway + agent loop): `src/gateway/gateway.test.ts` (case: "runs a mock OpenAI tool call end-to-end via gateway agent loop")
|
||||
- Gateway wizard (WS `wizard.start`/`wizard.next`, writes config + auth enforced): `src/gateway/gateway.test.ts` (case: "runs wizard over ws and writes auth token config")
|
||||
- Gateway tool calling (mock OpenAI, real gateway + agent loop): `src/gateway/gateway.tool-calling.mock-openai.test.ts`
|
||||
- Gateway wizard (WS `wizard.start`/`wizard.next`, writes config + auth enforced): `src/gateway/gateway.wizard.e2e.test.ts`
|
||||
|
||||
## Agent reliability evals (skills)
|
||||
|
||||
We already have a few CI-safe tests that behave like “agent reliability evals”:
|
||||
|
||||
- Mock tool-calling through the real gateway + agent loop (`src/gateway/gateway.test.ts`).
|
||||
- End-to-end wizard flows that validate session wiring and config effects (`src/gateway/gateway.test.ts`).
|
||||
- Mock tool-calling through the real gateway + agent loop (`src/gateway/gateway.tool-calling.mock-openai.test.ts`).
|
||||
- End-to-end wizard flows that validate session wiring and config effects (`src/gateway/gateway.wizard.e2e.test.ts`).
|
||||
|
||||
What’s still missing for skills (see [Skills](/tools/skills)):
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Shelley, [exe.dev](https://exe.dev)'s agent, can install OpenClaw instantly with
|
||||
prompt. The prompt used is as below:
|
||||
|
||||
```
|
||||
Set up OpenClaw (https://docs.openclaw.ai/install) on this VM. Use the non-interactive and accept-risk flags for openclaw onboarding. Add the supplied auth or token as needed. Configure nginx to forward from the default port 18789 to the root location on the default enabled site config, making sure to enable Websocket support. Pairing is done by "openclaw devices list" and "openclaw devices approve <request id>". Make sure the dashboard shows that OpenClaw's health is OK. exe.dev handles forwarding from port 8000 to port 80/443 and HTTPS for us, so the final "reachable" should be <vm-name>.exe.xyz, without port specification.
|
||||
Set up OpenClaw (https://docs.openclaw.ai/install) on this VM. Use the non-interactive and accept-risk flags for openclaw onboarding. Add the supplied auth or token as needed. Configure nginx to forward from the default port 18789 to the root location on the default enabled site config, making sure to enable Websocket support. Pairing is done by "openclaw devices list" and "openclaw device approve <request id>". Make sure the dashboard shows that OpenClaw's health is OK. exe.dev handles forwarding from port 8000 to port 80/443 and HTTPS for us, so the final "reachable" should be <vm-name>.exe.xyz, without port specification.
|
||||
```
|
||||
|
||||
## Manual installation
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
title: Fly.io
|
||||
description: Deploy OpenClaw on Fly.io
|
||||
summary: "Step-by-step Fly.io deployment for OpenClaw with persistent storage and HTTPS"
|
||||
read_when:
|
||||
- Deploying OpenClaw on Fly.io
|
||||
- Setting up Fly volumes, secrets, and first-run config
|
||||
---
|
||||
|
||||
# Fly.io Deployment
|
||||
|
||||
@@ -71,32 +71,6 @@ See [Development channels](/install/development-channels) for channel semantics
|
||||
|
||||
Note: on npm installs, the gateway logs an update hint on startup (checks the current channel tag). Disable via `update.checkOnStart: false`.
|
||||
|
||||
### Core auto-updater (optional)
|
||||
|
||||
Auto-updater is **off by default** and is a core Gateway feature (not a plugin).
|
||||
|
||||
```json
|
||||
{
|
||||
"update": {
|
||||
"channel": "stable",
|
||||
"auto": {
|
||||
"enabled": true,
|
||||
"stableDelayHours": 6,
|
||||
"stableJitterHours": 12,
|
||||
"betaCheckIntervalHours": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Behavior:
|
||||
|
||||
- `stable`: when a new version is seen, OpenClaw waits `stableDelayHours` and then applies a deterministic per-install jitter in `stableJitterHours` (spread rollout).
|
||||
- `beta`: checks on `betaCheckIntervalHours` cadence (default: hourly) and applies when an update is available.
|
||||
- `dev`: no automatic apply; use manual `openclaw update`.
|
||||
|
||||
Use `openclaw update --dry-run` to preview update actions before enabling automation.
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -118,8 +118,6 @@ All logging configuration lives under `logging` in `~/.openclaw/openclaw.json`.
|
||||
- `logging.level`: **file logs** (JSONL) level.
|
||||
- `logging.consoleLevel`: **console** verbosity level.
|
||||
|
||||
You can override both via the **`OPENCLAW_LOG_LEVEL`** environment variable (e.g. `OPENCLAW_LOG_LEVEL=debug`). The env var takes precedence over the config file, so you can raise verbosity for a single run without editing `openclaw.json`. You can also pass the global CLI option **`--log-level <level>`** (for example, `openclaw --log-level debug gateway run`), which overrides the environment variable for that command.
|
||||
|
||||
`--verbose` only affects console output; it does not change file log levels.
|
||||
|
||||
### Console styles
|
||||
|
||||
@@ -94,27 +94,11 @@ Note: Binary detection is best-effort across macOS/Linux/Windows; ensure the CLI
|
||||
}
|
||||
```
|
||||
|
||||
### Provider-only (Mistral Voxtral)
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
media: {
|
||||
audio: {
|
||||
enabled: true,
|
||||
models: [{ provider: "mistral", model: "voxtral-mini-latest" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes & limits
|
||||
|
||||
- Provider auth follows the standard model auth order (auth profiles, env vars, `models.providers.*.apiKey`).
|
||||
- Deepgram picks up `DEEPGRAM_API_KEY` when `provider: "deepgram"` is used.
|
||||
- Deepgram setup details: [Deepgram (audio transcription)](/providers/deepgram).
|
||||
- Mistral setup details: [Mistral](/providers/mistral).
|
||||
- Audio providers can override `baseUrl`, `headers`, and `providerOptions` via `tools.media.audio`.
|
||||
- Default size cap is 20MB (`tools.media.audio.maxBytes`). Oversize audio is skipped for that model and the next entry is tried.
|
||||
- Default `maxChars` for audio is **unset** (full transcript). Set `tools.media.audio.maxChars` or per-entry `maxChars` to trim output.
|
||||
|
||||
@@ -278,11 +278,8 @@ Notes:
|
||||
- `system.run` returns stdout/stderr/exit code in the payload.
|
||||
- `system.notify` respects notification permission state on the macOS app.
|
||||
- `system.run` supports `--cwd`, `--env KEY=VAL`, `--command-timeout`, and `--needs-screen-recording`.
|
||||
- For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped `--env` values are reduced to an explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`).
|
||||
- For allow-always decisions in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.
|
||||
- On Windows node hosts in allowlist mode, shell-wrapper runs via `cmd.exe /c` require approval (allowlist entry alone does not auto-allow the wrapper form).
|
||||
- `system.notify` supports `--priority <passive|active|timeSensitive>` and `--delivery <system|overlay|auto>`.
|
||||
- Node hosts ignore `PATH` overrides and strip dangerous startup/shell keys (`DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`). If you need extra PATH entries, configure the node host service environment (or install tools in standard locations) instead of passing `PATH` via `--env`.
|
||||
- Node hosts ignore `PATH` overrides. If you need extra PATH entries, configure the node host service environment (or install tools in standard locations) instead of passing `PATH` via `--env`.
|
||||
- On macOS node mode, `system.run` is gated by exec approvals in the macOS app (Settings → Exec approvals).
|
||||
Ask/allowlist/full behave the same as the headless node host; denied prompts return `SYSTEM_RUN_DENIED`.
|
||||
- On headless node host, `system.run` is gated by exec approvals (`~/.openclaw/exec-approvals.json`).
|
||||
@@ -334,9 +331,9 @@ Notes:
|
||||
- The node host stores its node id, token, display name, and gateway connection info in `~/.openclaw/node.json`.
|
||||
- Exec approvals are enforced locally via `~/.openclaw/exec-approvals.json`
|
||||
(see [Exec approvals](/tools/exec-approvals)).
|
||||
- On macOS, the headless node host executes `system.run` locally by default. Set
|
||||
`OPENCLAW_NODE_EXEC_HOST=app` to route `system.run` through the companion app exec host; add
|
||||
`OPENCLAW_NODE_EXEC_FALLBACK=0` to require the app host and fail closed if it is unavailable.
|
||||
- On macOS, the headless node host prefers the companion app exec host when reachable and falls
|
||||
back to local execution if the app is unavailable. Set `OPENCLAW_NODE_EXEC_HOST=app` to require
|
||||
the app, or `OPENCLAW_NODE_EXEC_FALLBACK=0` to disable fallback.
|
||||
- Add `--tls` / `--tls-fingerprint` when the Gateway WS uses TLS.
|
||||
|
||||
## Mac node mode
|
||||
|
||||
@@ -175,11 +175,11 @@ If you omit `capabilities`, the entry is eligible for the list it appears in.
|
||||
|
||||
## Provider support matrix (OpenClaw integrations)
|
||||
|
||||
| Capability | Provider integration | Notes |
|
||||
| ---------- | ------------------------------------------------ | --------------------------------------------------------- |
|
||||
| Image | OpenAI / Anthropic / Google / others via `pi-ai` | Any image-capable model in the registry works. |
|
||||
| Audio | OpenAI, Groq, Deepgram, Google, Mistral | Provider transcription (Whisper/Deepgram/Gemini/Voxtral). |
|
||||
| Video | Google (Gemini API) | Provider video understanding. |
|
||||
| Capability | Provider integration | Notes |
|
||||
| ---------- | ------------------------------------------------ | ------------------------------------------------- |
|
||||
| Image | OpenAI / Anthropic / Google / others via `pi-ai` | Any image-capable model in the registry works. |
|
||||
| Audio | OpenAI, Groq, Deepgram, Google | Provider transcription (Whisper/Deepgram/Gemini). |
|
||||
| Video | Google (Gemini API) | Provider video understanding. |
|
||||
|
||||
## Recommended providers
|
||||
|
||||
@@ -190,7 +190,7 @@ If you omit `capabilities`, the entry is eligible for the list it appears in.
|
||||
|
||||
**Audio**
|
||||
|
||||
- `openai/gpt-4o-mini-transcribe`, `groq/whisper-large-v3-turbo`, `deepgram/nova-3`, or `mistral/voxtral-mini-latest`.
|
||||
- `openai/gpt-4o-mini-transcribe`, `groq/whisper-large-v3-turbo`, or `deepgram/nova-3`.
|
||||
- CLI fallback: `whisper-cli` (whisper-cpp) or `whisper`.
|
||||
- Deepgram setup: [Deepgram (audio transcription)](/providers/deepgram).
|
||||
|
||||
|
||||
@@ -86,8 +86,6 @@ If pairing is fine but `system.run` fails, fix exec approvals/allowlist.
|
||||
- `LOCATION_BACKGROUND_UNAVAILABLE` → app is backgrounded but only While Using permission exists.
|
||||
- `SYSTEM_RUN_DENIED: approval required` → exec request needs explicit approval.
|
||||
- `SYSTEM_RUN_DENIED: allowlist miss` → command blocked by allowlist mode.
|
||||
On Windows node hosts, shell-wrapper forms like `cmd.exe /c ...` are treated as allowlist misses in
|
||||
allowlist mode unless approved via ask flow.
|
||||
|
||||
## Fast recovery loop
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
---
|
||||
title: "Pi Development Workflow"
|
||||
summary: "Developer workflow for Pi integration: build, test, and live validation"
|
||||
read_when:
|
||||
- Working on Pi integration code or tests
|
||||
- Running Pi-specific lint, typecheck, and live test flows
|
||||
---
|
||||
|
||||
# Pi Development Workflow
|
||||
@@ -19,25 +15,19 @@ This guide summarizes a sane workflow for working on the pi integration in OpenC
|
||||
|
||||
## Running Pi Tests
|
||||
|
||||
Run the Pi-focused test set directly with Vitest:
|
||||
Use the dedicated script for the pi integration test set:
|
||||
|
||||
```bash
|
||||
pnpm test -- \
|
||||
"src/agents/pi-*.test.ts" \
|
||||
"src/agents/pi-embedded-*.test.ts" \
|
||||
"src/agents/pi-tools*.test.ts" \
|
||||
"src/agents/pi-settings.test.ts" \
|
||||
"src/agents/pi-tool-definition-adapter*.test.ts" \
|
||||
"src/agents/pi-extensions/**/*.test.ts"
|
||||
scripts/pi/run-tests.sh
|
||||
```
|
||||
|
||||
To include the live provider exercise:
|
||||
To include the live test that exercises real provider behavior:
|
||||
|
||||
```bash
|
||||
OPENCLAW_LIVE_TEST=1 pnpm test -- src/agents/pi-embedded-runner-extraparams.live.test.ts
|
||||
scripts/pi/run-tests.sh --live
|
||||
```
|
||||
|
||||
This covers the main Pi unit suites:
|
||||
The script runs all pi related unit tests via these globs:
|
||||
|
||||
- `src/agents/pi-*.test.ts`
|
||||
- `src/agents/pi-embedded-*.test.ts`
|
||||
|
||||
100
docs/pi.md
100
docs/pi.md
@@ -1,9 +1,5 @@
|
||||
---
|
||||
title: "Pi Integration Architecture"
|
||||
summary: "Architecture of OpenClaw's embedded Pi agent integration and session lifecycle"
|
||||
read_when:
|
||||
- Understanding Pi SDK integration design in OpenClaw
|
||||
- Modifying agent session lifecycle, tooling, or provider wiring for Pi
|
||||
---
|
||||
|
||||
# Pi Integration Architecture
|
||||
@@ -381,7 +377,7 @@ OpenClaw loads custom pi extensions for specialized behavior:
|
||||
|
||||
### Compaction Safeguard
|
||||
|
||||
`src/agents/pi-extensions/compaction-safeguard.ts` adds guardrails to compaction, including adaptive token budgeting plus tool failure and file operation summaries:
|
||||
`pi-extensions/compaction-safeguard.ts` adds guardrails to compaction, including adaptive token budgeting plus tool failure and file operation summaries:
|
||||
|
||||
```typescript
|
||||
if (resolveCompactionMode(params.cfg) === "safeguard") {
|
||||
@@ -392,7 +388,7 @@ if (resolveCompactionMode(params.cfg) === "safeguard") {
|
||||
|
||||
### Context Pruning
|
||||
|
||||
`src/agents/pi-extensions/context-pruning.ts` implements cache-TTL based context pruning:
|
||||
`pi-extensions/context-pruning.ts` implements cache-TTL based context pruning:
|
||||
|
||||
```typescript
|
||||
if (cfg?.agents?.defaults?.contextPruning?.mode === "cache-ttl") {
|
||||
@@ -537,22 +533,80 @@ Areas for potential rework:
|
||||
|
||||
## Tests
|
||||
|
||||
Pi integration coverage spans these suites:
|
||||
All existing tests that cover the pi integration and its extensions:
|
||||
|
||||
- `src/agents/pi-*.test.ts`
|
||||
- `src/agents/pi-auth-json.test.ts`
|
||||
- `src/agents/pi-embedded-*.test.ts`
|
||||
- `src/agents/pi-embedded-helpers*.test.ts`
|
||||
- `src/agents/pi-embedded-runner*.test.ts`
|
||||
- `src/agents/pi-embedded-runner/**/*.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe*.test.ts`
|
||||
- `src/agents/pi-tools*.test.ts`
|
||||
- `src/agents/pi-tool-definition-adapter*.test.ts`
|
||||
- `src/agents/pi-embedded-block-chunker.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.buildbootstrapcontextfiles.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.classifyfailoverreason.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.downgradeopenai-reasoning.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.formatassistanterrortext.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.formatrawassistanterrorforui.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.image-dimension-error.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.image-size-error.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.isautherrormessage.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.isbillingerrormessage.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.iscloudcodeassistformaterror.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.iscompactionfailureerror.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.iscontextoverflowerror.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.isfailovererrormessage.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.islikelycontextoverflowerror.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.ismessagingtoolduplicate.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.messaging-duplicate.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.normalizetextforcomparison.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.resolvebootstrapmaxchars.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitize-session-messages-images.keeps-tool-call-tool-result-ids-unchanged.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitize-session-messages-images.removes-empty-assistant-text-blocks-but-preserves.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitizegoogleturnordering.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitizesessionmessagesimages-thought-signature-stripping.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitizetoolcallid.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.sanitizeuserfacingtext.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.stripthoughtsignatures.test.ts`
|
||||
- `src/agents/pi-embedded-helpers.validate-turns.test.ts`
|
||||
- `src/agents/pi-embedded-runner-extraparams.live.test.ts` (live)
|
||||
- `src/agents/pi-embedded-runner-extraparams.test.ts`
|
||||
- `src/agents/pi-embedded-runner.applygoogleturnorderingfix.test.ts`
|
||||
- `src/agents/pi-embedded-runner.buildembeddedsandboxinfo.test.ts`
|
||||
- `src/agents/pi-embedded-runner.createsystempromptoverride.test.ts`
|
||||
- `src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.falls-back-provider-default-per-dm-not.test.ts`
|
||||
- `src/agents/pi-embedded-runner.get-dm-history-limit-from-session-key.returns-undefined-sessionkey-is-undefined.test.ts`
|
||||
- `src/agents/pi-embedded-runner.google-sanitize-thinking.test.ts`
|
||||
- `src/agents/pi-embedded-runner.guard.test.ts`
|
||||
- `src/agents/pi-embedded-runner.limithistoryturns.test.ts`
|
||||
- `src/agents/pi-embedded-runner.resolvesessionagentids.test.ts`
|
||||
- `src/agents/pi-embedded-runner.run-embedded-pi-agent.auth-profile-rotation.test.ts`
|
||||
- `src/agents/pi-embedded-runner.sanitize-session-history.test.ts`
|
||||
- `src/agents/pi-embedded-runner.splitsdktools.test.ts`
|
||||
- `src/agents/pi-embedded-runner.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.code-span-awareness.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.reply-tags.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.calls-onblockreplyflush-before-tool-execution-start-preserve.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.does-not-append-text-end-content-is.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.does-not-call-onblockreplyflush-callback-is-not.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.does-not-duplicate-text-end-repeats-full.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.does-not-emit-duplicate-block-replies-text.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.emits-block-replies-text-end-does-not.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.emits-reasoning-as-separate-message-enabled.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.filters-final-suppresses-output-without-start-tag.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.includes-canvas-action-metadata-tool-summaries.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.keeps-assistanttexts-final-answer-block-replies-are.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.keeps-indented-fenced-blocks-intact.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.reopens-fenced-blocks-splitting-inside-them.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.splits-long-single-line-fenced-blocks-reopen.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.streams-soft-chunks-paragraph-preference.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.subscribeembeddedpisession.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.suppresses-message-end-block-replies-message-tool.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.subscribe-embedded-pi-session.waits-multiple-compaction-retries-before-resolving.test.ts`
|
||||
- `src/agents/pi-embedded-subscribe.tools.test.ts`
|
||||
- `src/agents/pi-embedded-utils.test.ts`
|
||||
- `src/agents/pi-extensions/compaction-safeguard.test.ts`
|
||||
- `src/agents/pi-extensions/context-pruning.test.ts`
|
||||
- `src/agents/pi-settings.test.ts`
|
||||
- `src/agents/pi-extensions/**/*.test.ts`
|
||||
|
||||
Live/opt-in:
|
||||
|
||||
- `src/agents/pi-embedded-runner-extraparams.live.test.ts` (enable `OPENCLAW_LIVE_TEST=1`)
|
||||
|
||||
For current run commands, see [Pi Development Workflow](/pi-dev).
|
||||
- `src/agents/pi-tool-definition-adapter.test.ts`
|
||||
- `src/agents/pi-tools-agent-config.test.ts`
|
||||
- `src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-b.test.ts`
|
||||
- `src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-d.test.ts`
|
||||
- `src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping-f.test.ts`
|
||||
- `src/agents/pi-tools.create-openclaw-coding-tools.adds-claude-style-aliases-schemas-without-dropping.test.ts`
|
||||
- `src/agents/pi-tools.policy.test.ts`
|
||||
- `src/agents/pi-tools.safe-bins.test.ts`
|
||||
- `src/agents/pi-tools.workspace-paths.test.ts`
|
||||
|
||||
@@ -34,17 +34,17 @@ Notes:
|
||||
# From repo root; set release IDs so Sparkle feed is enabled.
|
||||
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
||||
BUNDLE_ID=bot.molt.mac \
|
||||
APP_VERSION=2026.2.23 \
|
||||
APP_VERSION=2026.2.21 \
|
||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||
BUILD_CONFIG=release \
|
||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||
scripts/package-mac-app.sh
|
||||
|
||||
# Zip for distribution (includes resource forks for Sparkle delta support)
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.23.zip
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/OpenClaw.app dist/OpenClaw-2026.2.21.zip
|
||||
|
||||
# Optional: also build a styled DMG for humans (drag to /Applications)
|
||||
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.23.dmg
|
||||
scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.21.dmg
|
||||
|
||||
# Recommended: build + notarize/staple zip + DMG
|
||||
# First, create a keychain profile once:
|
||||
@@ -52,14 +52,14 @@ scripts/create-dmg.sh dist/OpenClaw.app dist/OpenClaw-2026.2.23.dmg
|
||||
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
||||
NOTARIZE=1 NOTARYTOOL_PROFILE=openclaw-notary \
|
||||
BUNDLE_ID=bot.molt.mac \
|
||||
APP_VERSION=2026.2.23 \
|
||||
APP_VERSION=2026.2.21 \
|
||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||
BUILD_CONFIG=release \
|
||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||
scripts/package-mac-dist.sh
|
||||
|
||||
# Optional: ship dSYM alongside the release
|
||||
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.23.dSYM.zip
|
||||
ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenClaw-2026.2.21.dSYM.zip
|
||||
```
|
||||
|
||||
## Appcast entry
|
||||
@@ -67,7 +67,7 @@ ditto -c -k --keepParent apps/macos/.build/release/OpenClaw.app.dSYM dist/OpenCl
|
||||
Use the release note generator so Sparkle renders formatted HTML notes:
|
||||
|
||||
```bash
|
||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.23.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
|
||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/OpenClaw-2026.2.21.zip https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml
|
||||
```
|
||||
|
||||
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/openclaw/openclaw/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
||||
@@ -75,7 +75,7 @@ Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when
|
||||
|
||||
## Publish & verify
|
||||
|
||||
- Upload `OpenClaw-2026.2.23.zip` (and `OpenClaw-2026.2.23.dSYM.zip`) to the GitHub release for tag `v2026.2.23`.
|
||||
- Upload `OpenClaw-2026.2.21.zip` (and `OpenClaw-2026.2.21.dSYM.zip`) to the GitHub release for tag `v2026.2.21`.
|
||||
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml`.
|
||||
- Sanity checks:
|
||||
- `curl -I https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml` returns 200.
|
||||
|
||||
@@ -105,9 +105,7 @@ Notes:
|
||||
- `allowlist` entries are glob patterns for resolved binary paths.
|
||||
- Raw shell command text that contains shell control or expansion syntax (`&&`, `||`, `;`, `|`, `` ` ``, `$`, `<`, `>`, `(`, `)`) is treated as an allowlist miss and requires explicit approval (or allowlisting the shell binary).
|
||||
- Choosing “Always Allow” in the prompt adds that command to the allowlist.
|
||||
- `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`, `SHELLOPTS`, `PS4`) and then merged with the app’s environment.
|
||||
- For shell wrappers (`bash|sh|zsh ... -c/-lc`), request-scoped environment overrides are reduced to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`).
|
||||
- For allow-always decisions in allowlist mode, known dispatch wrappers (`env`, `nice`, `nohup`, `stdbuf`, `timeout`) persist inner executable paths instead of wrapper paths. If unwrapping is not safe, no allowlist entry is persisted automatically.
|
||||
- `system.run` environment overrides are filtered (drops `PATH`, `DYLD_*`, `LD_*`, `NODE_OPTIONS`, `PYTHON*`, `PERL*`, `RUBYOPT`) and then merged with the app’s environment.
|
||||
|
||||
## Deep links
|
||||
|
||||
|
||||
@@ -107,10 +107,6 @@ Set config under `plugins.entries.voice-call.config`:
|
||||
streaming: {
|
||||
enabled: true,
|
||||
streamPath: "/voice/stream",
|
||||
preStartTimeoutMs: 5000,
|
||||
maxPendingConnections: 32,
|
||||
maxPendingConnectionsPerIp: 4,
|
||||
maxConnections: 128,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -129,11 +125,6 @@ Notes:
|
||||
- If you use ngrok free tier, set `publicUrl` to the exact ngrok URL; signature verification is always enforced.
|
||||
- `tunnel.allowNgrokFreeTierLoopbackBypass: true` allows Twilio webhooks with invalid signatures **only** when `tunnel.provider="ngrok"` and `serve.bind` is loopback (ngrok local agent). Use for local dev only.
|
||||
- Ngrok free tier URLs can change or add interstitial behavior; if `publicUrl` drifts, Twilio signatures will fail. For production, prefer a stable domain or Tailscale funnel.
|
||||
- Streaming security defaults:
|
||||
- `streaming.preStartTimeoutMs` closes sockets that never send a valid `start` frame.
|
||||
- `streaming.maxPendingConnections` caps total unauthenticated pre-start sockets.
|
||||
- `streaming.maxPendingConnectionsPerIp` caps unauthenticated pre-start sockets per source IP.
|
||||
- `streaming.maxConnections` caps total open media stream sockets (pending + active).
|
||||
|
||||
## Stale call reaper
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@ See [Venice AI](/providers/venice).
|
||||
- [Together AI](/providers/together)
|
||||
- [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [Mistral](/providers/mistral)
|
||||
- [OpenCode Zen](/providers/opencode)
|
||||
- [Amazon Bedrock](/providers/bedrock)
|
||||
- [Z.AI](/providers/zai)
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
summary: "Use Mistral models and Voxtral transcription with OpenClaw"
|
||||
read_when:
|
||||
- You want to use Mistral models in OpenClaw
|
||||
- You need Mistral API key onboarding and model refs
|
||||
title: "Mistral"
|
||||
---
|
||||
|
||||
# Mistral
|
||||
|
||||
OpenClaw supports Mistral for both text/image model routing (`mistral/...`) and
|
||||
audio transcription via Voxtral in media understanding.
|
||||
Mistral can also be used for memory embeddings (`memorySearch.provider = "mistral"`).
|
||||
|
||||
## CLI setup
|
||||
|
||||
```bash
|
||||
openclaw onboard --auth-choice mistral-api-key
|
||||
# or non-interactive
|
||||
openclaw onboard --mistral-api-key "$MISTRAL_API_KEY"
|
||||
```
|
||||
|
||||
## Config snippet (LLM provider)
|
||||
|
||||
```json5
|
||||
{
|
||||
env: { MISTRAL_API_KEY: "sk-..." },
|
||||
agents: { defaults: { model: { primary: "mistral/mistral-large-latest" } } },
|
||||
}
|
||||
```
|
||||
|
||||
## Config snippet (audio transcription with Voxtral)
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
media: {
|
||||
audio: {
|
||||
enabled: true,
|
||||
models: [{ provider: "mistral", model: "voxtral-mini-latest" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- Mistral auth uses `MISTRAL_API_KEY`.
|
||||
- Provider base URL defaults to `https://api.mistral.ai/v1`.
|
||||
- Onboarding default model is `mistral/mistral-large-latest`.
|
||||
- Media-understanding default audio model for Mistral is `voxtral-mini-latest`.
|
||||
- Media transcription path uses `/v1/audio/transcriptions`.
|
||||
- Memory embeddings path uses `/v1/embeddings` (default model: `mistral-embed`).
|
||||
@@ -39,7 +39,6 @@ See [Venice AI](/providers/venice).
|
||||
- [Vercel AI Gateway](/providers/vercel-ai-gateway)
|
||||
- [Cloudflare AI Gateway](/providers/cloudflare-ai-gateway)
|
||||
- [Moonshot AI (Kimi + Kimi Coding)](/providers/moonshot)
|
||||
- [Mistral](/providers/mistral)
|
||||
- [Synthetic](/providers/synthetic)
|
||||
- [OpenCode Zen](/providers/opencode)
|
||||
- [Z.AI](/providers/zai)
|
||||
|
||||
@@ -47,7 +47,7 @@ This will set `together/moonshotai/Kimi-K2.5` as the default model.
|
||||
## Environment note
|
||||
|
||||
If the Gateway runs as a daemon (launchd/systemd), make sure `TOGETHER_API_KEY`
|
||||
is available to that process (for example, in `~/.openclaw/.env` or via
|
||||
is available to that process (for example, in `~/.clawdbot/.env` or via
|
||||
`env.shellEnv`).
|
||||
|
||||
## Available models
|
||||
|
||||
@@ -79,7 +79,7 @@ openclaw onboard --non-interactive \
|
||||
### 3. Verify Setup
|
||||
|
||||
```bash
|
||||
openclaw agent --model venice/llama-3.3-70b --message "Hello, are you working?"
|
||||
openclaw chat --model venice/llama-3.3-70b "Hello, are you working?"
|
||||
```
|
||||
|
||||
## Model Selection
|
||||
@@ -195,19 +195,19 @@ Venice uses a credit-based system. Check [venice.ai/pricing](https://venice.ai/p
|
||||
|
||||
```bash
|
||||
# Use default private model
|
||||
openclaw agent --model venice/llama-3.3-70b --message "Quick health check"
|
||||
openclaw chat --model venice/llama-3.3-70b
|
||||
|
||||
# Use Claude via Venice (anonymized)
|
||||
openclaw agent --model venice/claude-opus-45 --message "Summarize this task"
|
||||
openclaw chat --model venice/claude-opus-45
|
||||
|
||||
# Use uncensored model
|
||||
openclaw agent --model venice/venice-uncensored --message "Draft options"
|
||||
openclaw chat --model venice/venice-uncensored
|
||||
|
||||
# Use vision model with image
|
||||
openclaw agent --model venice/qwen3-vl-235b-a22b --message "Review attached image"
|
||||
openclaw chat --model venice/qwen3-vl-235b-a22b
|
||||
|
||||
# Use coding model
|
||||
openclaw agent --model venice/qwen3-coder-480b-a35b-instruct --message "Refactor this function"
|
||||
openclaw chat --model venice/qwen3-coder-480b-a35b-instruct
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
---
|
||||
title: Outbound Session Mirroring Refactor (Issue #1520)
|
||||
description: Track outbound session mirroring refactor notes, decisions, tests, and open items.
|
||||
summary: "Refactor notes for mirroring outbound sends into target channel sessions"
|
||||
read_when:
|
||||
- Working on outbound transcript/session mirroring behavior
|
||||
- Debugging sessionKey derivation for send/message tool paths
|
||||
---
|
||||
|
||||
# Outbound Session Mirroring Refactor (Issue #1520)
|
||||
@@ -62,7 +58,7 @@ Outbound sends were mirrored into the _current_ agent session (tool session key)
|
||||
|
||||
## Tests Added/Updated
|
||||
|
||||
- `src/infra/outbound/outbound.test.ts`
|
||||
- `src/infra/outbound/outbound-session.test.ts`
|
||||
- Slack thread session key.
|
||||
- Telegram topic session key.
|
||||
- dmScope identityLinks with Discord.
|
||||
@@ -84,6 +80,6 @@ Outbound sends were mirrored into the _current_ agent session (tool session key)
|
||||
- `src/agents/tools/message-tool.ts`
|
||||
- `src/gateway/server-methods/send.ts`
|
||||
- Tests in:
|
||||
- `src/infra/outbound/outbound.test.ts`
|
||||
- `src/infra/outbound/outbound-session.test.ts`
|
||||
- `src/agents/tools/message-tool.test.ts`
|
||||
- `src/gateway/server-methods/send.test.ts`
|
||||
|
||||
@@ -23,7 +23,7 @@ When the operator says “release”, immediately do this preflight (no extra qu
|
||||
|
||||
- [ ] Bump `package.json` version (e.g., `2026.1.29`).
|
||||
- [ ] Run `pnpm plugins:sync` to align extension package versions + changelogs.
|
||||
- [ ] Update CLI/version strings in [`src/version.ts`](https://github.com/openclaw/openclaw/blob/main/src/version.ts) and the Baileys user agent in [`src/web/session.ts`](https://github.com/openclaw/openclaw/blob/main/src/web/session.ts).
|
||||
- [ ] Update CLI/version strings: [`src/cli/program.ts`](https://github.com/openclaw/openclaw/blob/main/src/cli/program.ts) and the Baileys user agent in [`src/provider-web.ts`](https://github.com/openclaw/openclaw/blob/main/src/provider-web.ts).
|
||||
- [ ] 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.
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@ Semantic memory search uses **embedding APIs** when configured for remote provid
|
||||
- `memorySearch.provider = "openai"` → OpenAI embeddings
|
||||
- `memorySearch.provider = "gemini"` → Gemini embeddings
|
||||
- `memorySearch.provider = "voyage"` → Voyage embeddings
|
||||
- `memorySearch.provider = "mistral"` → Mistral embeddings
|
||||
- Optional fallback to a remote provider if local embeddings fail
|
||||
|
||||
You can keep it local with `memorySearch.provider = "local"` (no API usage).
|
||||
|
||||
@@ -243,7 +243,6 @@ Typical fields in `~/.openclaw/openclaw.json`:
|
||||
- `agents.defaults.workspace`
|
||||
- `agents.defaults.model` / `models.providers` (if Minimax chosen)
|
||||
- `gateway.*` (mode, bind, auth, tailscale)
|
||||
- `session.dmScope` (behavior details: [CLI Onboarding Reference](/start/wizard-cli-reference#outputs-and-internals))
|
||||
- `channels.telegram.botToken`, `channels.discord.token`, `channels.signal.*`, `channels.imessage.*`
|
||||
- Channel allowlists (Slack/Discord/Matrix/Microsoft Teams) when you opt in during the prompts (names resolve to IDs when possible).
|
||||
- `skills.install.nodeManager`
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
---
|
||||
title: Formal Verification (Security Models)
|
||||
summary: Machine-checked security models for OpenClaw’s highest-risk paths.
|
||||
read_when:
|
||||
- Reviewing formal security model guarantees or limits
|
||||
- Reproducing or updating TLA+/TLC security model checks
|
||||
permalink: /security/formal-verification/
|
||||
---
|
||||
|
||||
|
||||
@@ -181,6 +181,8 @@ Use these hubs to discover every page, including deep dives and reference docs t
|
||||
## Experiments (exploratory)
|
||||
|
||||
- [Onboarding config protocol](/experiments/onboarding-config-protocol)
|
||||
- [Cron hardening notes](/experiments/plans/cron-add-hardening)
|
||||
- [Group policy hardening notes](/experiments/plans/group-policy-hardening)
|
||||
- [Research: memory](/experiments/research/memory)
|
||||
- [Model config exploration](/experiments/proposals/model-config)
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
title: "Showcase"
|
||||
description: "Real-world OpenClaw projects from the community"
|
||||
summary: "Community-built projects and integrations powered by OpenClaw"
|
||||
read_when:
|
||||
- Looking for real OpenClaw usage examples
|
||||
- Updating community project highlights
|
||||
---
|
||||
|
||||
# Showcase
|
||||
|
||||
@@ -86,16 +86,6 @@ Add `--json` for a machine-readable summary.
|
||||
--gateway-bind loopback
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Mistral example">
|
||||
```bash
|
||||
openclaw onboard --non-interactive \
|
||||
--mode local \
|
||||
--auth-choice mistral-api-key \
|
||||
--mistral-api-key "$MISTRAL_API_KEY" \
|
||||
--gateway-port 18789 \
|
||||
--gateway-bind loopback
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="Synthetic example">
|
||||
```bash
|
||||
openclaw onboard --non-interactive \
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user