From 945b6ceab794635c77bf083676a65519d563b192 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 12:18:17 -0300 Subject: [PATCH 01/17] docs: add feat/matrix-channel proposal Scopes Matrix messaging integration as an alternative to Telegram. Covers plugin vs. sidecar approaches, Conduit self-hosted option, idempotency, open questions on OpenClaw plugin API and E2E encryption. --- docs/PROPOSAL-MATRIX-CHANNEL.md | 472 ++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 docs/PROPOSAL-MATRIX-CHANNEL.md diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md new file mode 100644 index 0000000..f0fc26b --- /dev/null +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -0,0 +1,472 @@ +# Proposal: feat/matrix-channel — Matrix Channel Integration + +**Status:** Proposed +**Branch:** `feat/matrix-channel` (not yet created) +**Depends on:** None +**Idempotency note:** In this context, "idempotent" means the change can be applied repeatedly without drift and can be introduced either before initial deployment or later on an existing deployment. + +--- + +## Goal + +Add Matrix as a supported messaging channel in `openclaw-stack`, using the existing OpenClaw Matrix plugin documented at: + +`https://docs.openclaw.ai/channels/matrix` + +OpenClaw supports Matrix via the `@openclaw/matrix` plugin. For this stack, the implementation work is plugin installation, channel configuration, persistence, and operator workflows. + +--- + +## What Upstream Already Supports + +Per the current OpenClaw docs, Matrix is: + +- Supported via plugin, not built into the core install +- Installed with `openclaw plugins install @openclaw/matrix` +- Configured under `channels.matrix` +- Compatible with direct messages, rooms, threads, media, reactions, polls, location, and native commands +- Capable of E2EE when `channels.matrix.encryption: true` and the crypto module is available +- Able to run multiple Matrix accounts via `channels.matrix.accounts` + +Important implication: this stack does not need to invent a Matrix transport layer. It needs to make plugin installation and Matrix channel configuration first-class deployment concerns. + +--- + +## Recommended Path + +Implement Matrix in `openclaw-stack` using OpenClaw's native Matrix plugin and config model: + +1. Install `@openclaw/matrix` during provisioning/startup for each OpenClaw instance. +2. Expose Matrix credentials/config through `stack.yml` and `.env`. +3. Render `channels.matrix` into each instance's `openclaw.json`. +4. Persist Matrix runtime state so access tokens, sync state, and crypto state survive restarts. +5. Document DM pairing, room allowlists, and E2EE verification as operator workflows. + +Implement Matrix through the official plugin and stack-managed configuration. + +--- + +## Proposed Stack Design + +### 1. Plugin installation + +Matrix ships as a plugin and is not bundled with OpenClaw core. The stack therefore needs an explicit install step for: + +```bash +openclaw plugins install @openclaw/matrix +``` + +This should happen in a deploy-managed, repeatable way, not as an ad hoc manual command after boot. + +Implementation: + +- Install/check the plugin at runtime during claw startup, against the claw's persisted `.openclaw` directory +- Run the install/check as the claw's runtime user (`node`, uid `1000`) so files under `~/.openclaw/extensions` keep the same ownership model as the rest of the persisted state +- Keep plugin files in the per-instance persisted extensions path +- Make the install step safe to re-run on deploy + +Build-time changes should only be used if Matrix needs extra OS/package dependencies in the image. Plugin presence itself should remain a per-claw runtime concern. + +### 2. Channel configuration + +The stack should render OpenClaw's native Matrix config into `openclaw.json`, for example: + +```jsonc +{ + "channels": { + "matrix": { + "enabled": true, + "homeserver": "$MATRIX_HOMESERVER", + "accessToken": "$MATRIX_ACCESS_TOKEN", + "encryption": false, + "dm": { "policy": "pairing" }, + "groupPolicy": "allowlist" + } + } +} +``` + +Optional E2EE sets `"encryption": true`. + +### 3. Runtime persistence + +The upstream docs state that Matrix stores credentials and sync/crypto state under `~/.openclaw`, including: + +- `~/.openclaw/credentials/matrix/credentials.json` +- `~/.openclaw/matrix/accounts/...` + +That means this proposal must account for persistent storage, especially for: + +- Access-token backed sessions +- Sync state +- E2EE crypto state and device verification + +This stack already persists the relevant `~/.openclaw` paths, so Matrix can reuse them without a new volume design. + +Backup coverage should be extended to include: + +- `~/.openclaw/matrix/` + +E2EE should not be considered production-ready in this stack until backup/restore covers the Matrix crypto state under that tree. + +### 4. Access-control model + +The plugin's default Matrix DM behavior is `dm.policy = "pairing"`, which is a good operational default for this stack. + +For rooms, upstream defaults are stricter than Telegram: + +- `channels.matrix.groupPolicy = "allowlist"` by default +- Room allowlisting is configured via `channels.matrix.groups` +- Group sender allowlisting is configured via `channels.matrix.groupAllowFrom` +- Invites auto-join by default and can be restricted with `autoJoin` and `autoJoinAllowlist` + +Matrix is not just "Telegram but different transport." It has a richer room and allowlist model that should be surfaced in stack config/docs. + +### 5. Mental model: claws vs Matrix accounts + +A claw is an OpenClaw gateway instance with its own domain, config, token, workspace, and runtime state. + +A Matrix account is just one messaging identity that a claw can log in as. + +For v1, the intended model is: + +- one claw = one Matrix bot account +- one Matrix bot account can talk to multiple human Matrix users +- DMs are gated by pairing +- rooms are gated by allowlists and per-room policy + +This is not a "one Matrix account per human user" design. Multiple people can use the same claw through the same bot account, subject to the claw's pairing and room policy. + +Upstream supports multiple Matrix accounts per claw, but that is a phase-2 feature and is not needed for the basic model above. + +--- + +## Required Changes + +### `stack.yml.example` + +Add a top-level per-claw `matrix:` section, parallel to `telegram:`, and map it to upstream `channels.matrix`. Example: + +```yaml +defaults: + matrix: + enabled: false + homeserver: "https://matrix.org" + dm_policy: pairing + dm_allow_from: [] + group_policy: allowlist + encryption: false + groups: + # "!roomid:matrix.org": + # enabled: true + # mention_only: true + group_allow_from: [] + auto_join: always + auto_join_allowlist: [] + +claws: + main: + matrix: + enabled: true + access_token: ${MAIN_CLAW_MATRIX_ACCESS_TOKEN} +``` + +This matches the repo's current config style and deep-merge behavior. The render step can translate it into upstream fields such as `dm.policy`, `groupPolicy`, `groupAllowFrom`, and `autoJoin`. + +Default resolution: + +- `defaults.matrix.homeserver` should provide the normal stack-wide default +- individual claws may override it with `claws..matrix.homeserver` +- per-claw override is only needed when different claws intentionally use different homeservers + +Room identity: + +- stack config should use canonical Matrix room IDs such as `!roomid:matrix.org` +- room aliases such as `#room:matrix.org` may help operators discover rooms, but the stack should render and persist canonical room IDs to avoid alias-resolution ambiguity + +### `.env.example` + +Add per-claw access-token credentials following the stack's existing naming pattern: + +```env +MAIN_CLAW_MATRIX_ACCESS_TOKEN= +# Optional if homeserver differs per claw: +# MAIN_CLAW_MATRIX_HOMESERVER=https://matrix.org +``` + +Use access-token auth only in this stack. The rendered container env can still expose `MATRIX_HOMESERVER` and `MATRIX_ACCESS_TOKEN`, but stack inputs should follow the per-claw naming pattern already used elsewhere in this repo. If multi-account support is added later, it belongs in `stack.yml`, not as a flat env-var scheme. + +`MAIN_CLAW_MATRIX_ACCESS_TOKEN` is only an example. The actual env var name depends on the claw name, such as `ALERTS_CLAW_MATRIX_ACCESS_TOKEN`. + +### `openclaw.jsonc` template + +Render `channels.matrix` only when enabled, and pass through supported options such as: + +- `enabled` +- `homeserver` +- `accessToken` +- `encryption` +- `dm.policy` +- `dm.allowFrom` +- `groupPolicy` +- `groupAllowFrom` +- `groups` +- `autoJoin` +- `autoJoinAllowlist` + +### Deploy/install scripts + +Add a repeatable Matrix plugin install step to the stack's provisioning flow. + +Touchpoints: + +- `deploy/openclaw-stack/entrypoint.sh` for per-instance install/check logic against the persisted extensions directory +- `deploy/host/build-openclaw.sh` only if image/build-time preparation is needed for Matrix runtime dependencies +- The per-instance `~/.openclaw/extensions` path +- Startup validation so a Matrix-enabled config fails clearly if the plugin is missing + +Expected behavior: + +- If `matrix.enabled` is false, no Matrix plugin install/check work is done +- If `matrix.enabled` is true, startup performs a fast existence check for the plugin under the claw's persisted extensions directory and installs it only if missing +- Re-running deploy or restart is safe and does not duplicate plugin state + +The idempotency check should be cheap, such as testing for the installed plugin directory under `~/.openclaw/extensions/`, so normal restarts do not pay repeated install overhead. +The install/check should run as the persisted runtime user so the plugin directory does not become root-owned. + +### `docker-compose.yml.hbs` + +Add Matrix env vars to each claw service so `openclaw.json` can resolve them via env substitution at startup. + +Required env wiring: + +```yaml +{{#if this.matrix.enabled}} +- MATRIX_HOMESERVER={{this.matrix.homeserver}} +- MATRIX_ACCESS_TOKEN={{this.matrix.access_token}} +{{/if}} +``` + +These env vars should only be emitted when Matrix is enabled for that claw. + +Implementation note: + +- Ensure the claw-service Handlebars context exposes `this.matrix` in the same way it already exposes other per-claw settings such as `this.telegram` + +### Docs + +Add operator documentation covering: + +- Creating a Matrix bot account +- Obtaining an access token +- DM pairing approval with `openclaw pairing list matrix` and `openclaw pairing approve matrix ` +- Inviting the bot to a room before room usage +- How `auto_join` and `auto_join_allowlist` affect room invites +- How allowed rooms are represented in stack config +- Whether mention-gating is enabled for room interactions +- Room allowlisting and mention-gating +- E2EE verification flow +- Beeper-specific note: requires E2EE enabled + +### Validation rules + +Add pre-deploy and startup validation for these cases: + +- `matrix.enabled: true` requires `matrix.homeserver` +- `matrix.enabled: true` requires `matrix.access_token` +- `npm run pre-deploy` should fail before rendering deploy artifacts if required Matrix config is missing +- `npm run pre-deploy` should fail if the resolved per-claw secret (for example `MAIN_CLAW_MATRIX_ACCESS_TOKEN`) is unset while Matrix is enabled for that claw +- Matrix-enabled config fails clearly if plugin install/check fails +- `matrix.encryption: true` emits a clear warning or hard failure if crypto support is unavailable + +### Restart behavior + +Matrix install and config should follow the stack's current reload model: + +- First-time Matrix enablement requires a gateway restart because plugin loading is not hot-reloadable +- Subsequent `channels.matrix` config changes may be hot-reloadable if they are treated as ordinary channel config by OpenClaw +- Any `plugins.*` change still requires restart + +--- + +## Operational Notes + +### DM behavior + +Matrix DMs default to pairing approval, which is appropriate for this stack's security posture. + +Relevant upstream commands: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +Initial operator flow: + +1. Deploy the claw with Matrix enabled and valid access token. +2. Send the first DM to the bot account from a Matrix client. +3. Review pending Matrix pairings with `openclaw pairing list matrix`. +4. Approve the intended user/device with `openclaw pairing approve matrix `. + +### Rooms and mention-gating + +Rooms are handled as group sessions. Upstream defaults to `groupPolicy: "allowlist"` and supports per-room config, sender allowlists, and mention gating. This should be surfaced directly in stack configuration and operator docs. + +Initial operator flow: + +1. Invite the bot account to the room. +2. Ensure the room is allowed by the claw's Matrix config. +3. If `auto_join` is restricted, ensure the room or inviter is in the appropriate allowlist. +4. If mention-gating is enabled for that room policy, address the bot explicitly when sending commands. + +### E2EE + +Matrix E2EE is supported, but it introduces operational requirements: + +- Crypto module must be available +- The bot/device must be verified from another Matrix client +- Crypto state must persist across restarts +- Rotating access tokens creates a new device store and may require re-verification +- Token rotation is an operator action followed by redeploy, not a deploy-script feature +- Token rotation should not imply OpenClaw user re-pairing, but it may require Matrix-side device re-verification in encrypted rooms + +Recommendation: support `encryption: false` first as the baseline deployment path, but design persistence correctly so `encryption: true` is a safe follow-up rather than a redesign. + +Release gate for production E2EE: + +- v1 may expose `matrix.encryption` +- production-ready E2EE requires backup/restore coverage for `~/.openclaw/matrix/` +- until that backup coverage is implemented and verified, encrypted Matrix should be treated as supported with operational caution rather than the baseline production mode + +### Multi-account + +Upstream supports multiple Matrix accounts per claw, but that should remain out of scope for v1. + +For this stack, the v1 model is one Matrix bot account per claw. Multi-account support would only be useful later for cases such as separate assistant and alerts bots or account-specific routing/policy. + +--- + +## Idempotency + +Matrix integration should remain fully idempotent in the deployment sense: + +- **Before initial deployment:** enable Matrix in config, provide credentials, deploy once +- **After deployment:** add Matrix config, run the normal deploy flow, and let the stack install/configure the plugin +- **Repeat deploys:** re-running deploy should not duplicate plugin state or require cleanup +- **Disable later:** turning Matrix off should stop channel startup without affecting unrelated OpenClaw state + +Clarification: + +- "Can be added before or after deployment" is one consequence of idempotency here +- The stronger meaning is that the same deploy action can be safely re-applied and converge on the intended state + +--- + +## Implementation Constraints + +### Plugin install location + +Install `@openclaw/matrix` into the standard per-instance OpenClaw extensions path: + +- `~/.openclaw/extensions/` + +Reasoning: + +- This is the upstream plugin install location +- This stack already persists each claw's full `~/.openclaw` tree +- It survives restarts and image rebuilds naturally +- It avoids mixing third-party installed plugins with repo-owned plugins under `/app/openclaw-stack/plugins` + +Version pinning: + +- v1 does not need explicit plugin version pinning in the proposal +- initial implementation may follow the normal install path for `@openclaw/matrix` +- if upgrade churn or regressions appear in practice, the stack can add explicit plugin version pinning later + +### Runtime persistence + +Matrix runtime persistence is already satisfied by the current stack design. + +Reasoning: + +- Each claw bind-mounts `instances//.openclaw` into `/home/node/.openclaw` +- Setup scripts create and persist that directory on the host +- Matrix credentials, sync state, and crypto state live under that persisted tree + +Remaining follow-up: + +- Extend host backups to include Matrix account state under `.openclaw/matrix/` + +### Migration and drift + +The stack should define expected behavior for claws that already have manual Matrix state. + +Cases to handle: + +- Plugin already present under `~/.openclaw/extensions` +- Existing Matrix credentials and sync state already present under `~/.openclaw` +- Existing claw config updated from "no Matrix" to stack-managed Matrix + +Expected behavior: + +- The stack reuses existing persisted plugin/state where valid +- Deploy converges config without deleting valid runtime state +- Validation surfaces mismatches clearly rather than silently overwriting them + +Mismatch handling: + +- Plugin exists and version differs: warn and leave the installed plugin in place unless the stack explicitly adds plugin version pinning later +- Persisted credentials exist and configured access token differs: treat the configured token as source of truth, reuse existing state where possible, and warn that Matrix device/session state may need re-verification +- Persisted sync/crypto state exists and homeserver changes: warn clearly and treat this as a migration-risk change that may require a fresh Matrix session or re-sync +- Persisted plugin/state exists but Matrix is now disabled in stack config: do not delete runtime state automatically; disable startup only + +Reset boundaries: + +- If the homeserver changes, existing Matrix sync/crypto state should be treated as invalid and the implementation should require a fresh Matrix session rather than silently reusing the old state +- If the configured access token changes and the persisted Matrix session cannot authenticate cleanly, the implementation should prompt for a fresh Matrix session reset rather than silently looping on bad state +- If Matrix is disabled, persisted state should be left on disk unless the operator explicitly removes it + +Reset mechanism: + +- v1 does not need a dedicated reset command in the proposal +- implementation should at minimum fail clearly and tell the operator that the persisted Matrix session/state must be reset before proceeding +- a dedicated reset command or helper script can be added later if reset handling becomes common enough to justify it + +### `stack.yml` shape + +Use a top-level per-claw `matrix:` block, parallel to `telegram:`, and render it into upstream `channels.matrix`. + +Reasoning: + +- Matches current repo config conventions +- Works cleanly with deep-merge defaults +- Keeps stack operator config concise +- Still maps cleanly onto upstream Matrix settings + +### Multi-account scope + +Initial implementation should expose only the default Matrix account. Support for multiple Matrix accounts per claw should be deferred. + +Reasoning: + +- One Matrix bot identity per claw matches the stack's current operating model +- Multi-account support adds nested secrets, routing choices, and more operator complexity +- It is useful, but not necessary for initial Matrix support + +### E2EE scope for v1 + +Initial implementation should expose E2EE config, but unencrypted Matrix should remain the baseline supported path. + +Reasoning: + +- Upstream supports both encrypted and unencrypted Matrix +- E2EE adds device verification, recovery, and backup complexity +- The current stack persistence model is sufficient for runtime use, but backup coverage should be extended before encrypted recovery is considered fully operationally mature +- This keeps v1 practical without painting the design into a corner + +--- + +## Conclusion + +The implementation should integrate the official Matrix plugin into `openclaw-stack` as a deploy-managed extension with first-class config and persistence support. From 6be7cee9eaffc65482560d97eded18dac7b0feba Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 19:44:54 -0300 Subject: [PATCH 02/17] feat(matrix): add Matrix channel integration Wires in the @openclaw/matrix plugin as a first-class deploy concern: - stack.yml.example: matrix: defaults block with all config options; commented per-claw example (access_token env var pattern) - docker-compose.yml.hbs: MATRIX_ENABLED always emitted; homeserver and access_token emitted only when enabled (sensitive var guard) - entrypoint.sh: idempotent plugin install at startup (filesystem check before install, runs as node uid 1000 for correct ownership) - openclaw/default/openclaw.jsonc: matrix channel block with env var substitution, disabled by default via MATRIX_ENABLED=false - build/pre-deploy.mjs: validateClaw checks homeserver + access_token when matrix.enabled; matrix_enabled derived boolean for Handlebars; MATRIX_HOMESERVER and MATRIX_ACCESS_TOKEN added to potentiallyEmptyVars so openclaw.jsonc ${VAR} resolves cleanly when Matrix is disabled - docs/MATRIX.md: operator setup guide (account, token, pairing, rooms, E2EE, token rotation, troubleshooting) - docs/PROPOSAL-MATRIX-CHANNEL.md: updated from initial draft to reflect confirmed native plugin support and full implementation scope --- build/pre-deploy.mjs | 18 +++ deploy/openclaw-stack/entrypoint.sh | 17 +++ docker-compose.yml.hbs | 8 ++ docs/MATRIX.md | 216 ++++++++++++++++++++++++++++ docs/PROPOSAL-MATRIX-CHANNEL.md | 18 ++- openclaw/default/openclaw.jsonc | 12 ++ stack.yml.example | 18 +++ 7 files changed, 300 insertions(+), 7 deletions(-) create mode 100644 docs/MATRIX.md diff --git a/build/pre-deploy.mjs b/build/pre-deploy.mjs index 3082952..a05ff5e 100644 --- a/build/pre-deploy.mjs +++ b/build/pre-deploy.mjs @@ -297,6 +297,16 @@ function validateClaw(name, claw) { if (!telegram?.bot_token) { warn(`Claw '${name}' has no telegram.bot_token — Telegram will be disabled`); } + + const matrix = claw.matrix; + if (matrix?.enabled) { + if (!matrix.homeserver) { + fatal(`Claw '${name}' has matrix.enabled: true but matrix.homeserver is not set`); + } + if (!matrix.access_token) { + fatal(`Claw '${name}' has matrix.enabled: true but matrix.access_token is empty — set the access token env var in .env`); + } + } } // ── Step 7: Compute derived values per claw ────────────────────────────────── @@ -356,6 +366,9 @@ function computeDerivedValues(claws, stack, host, previousDeploy) { claw.llemtry_url = logUrl ? logUrl + "/llemtry" : ""; claw.enable_events_logging = stack.logging?.events || false; claw.enable_llemtry_logging = stack.logging?.llemtry || false; + // matrix_enabled is a flat boolean for Handlebars — avoids empty-string output + // when matrix: is absent from stack.yml ({{this.matrix.enabled}} would be empty). + claw.matrix_enabled = claw.matrix?.enabled === true; } return autoTokens; @@ -641,6 +654,11 @@ async function main() { EVENTS_URL: "events_url", LLEMTRY_URL: "llemtry_url", ADMIN_TELEGRAM_ID: "telegram.allow_from", + // Matrix vars: always present in env (openclaw.jsonc references them via ${VAR}). + // When Matrix is disabled, they are empty — pre-resolved here so OpenClaw's + // ${VAR} substitution doesn't throw MissingEnvVarError on the matrix channel block. + MATRIX_HOMESERVER: "matrix.homeserver", + MATRIX_ACCESS_TOKEN: "matrix.access_token", }; // Check against first claw (these vars are stack-wide, same for all claws) const firstClaw = Object.values(claws)[0]; diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 0825c72..38873fd 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -100,6 +100,23 @@ echo "prefix=$npm_global" > /home/node/.npmrc export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" +# ── Matrix plugin install (if enabled) ────────────────────────────── +# Fast filesystem check — if @openclaw/matrix is already installed, skip entirely. +# Only runs when MATRIX_ENABLED=true (controlled by matrix.enabled in stack.yml). +# Runs as node (uid 1000) so plugin files under ~/.openclaw/extensions keep the +# same ownership as the rest of the persisted .openclaw state. +if [ "${MATRIX_ENABLED:-false}" = "true" ]; then + MATRIX_EXT_DIR="/home/node/.openclaw/extensions/@openclaw/matrix" + if [ ! -d "$MATRIX_EXT_DIR" ]; then + echo "[entrypoint] Installing @openclaw/matrix plugin..." + gosu node openclaw plugins install @openclaw/matrix && \ + echo "[entrypoint] @openclaw/matrix installed" || \ + echo "[entrypoint] WARNING: @openclaw/matrix install failed — Matrix channel will not be available" + else + echo "[entrypoint] @openclaw/matrix already installed" + fi +fi + # ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. # Real binaries live in sandbox images — shims are gateway-only (not bind-mounted). diff --git a/docker-compose.yml.hbs b/docker-compose.yml.hbs index 743fca7..990ce9b 100644 --- a/docker-compose.yml.hbs +++ b/docker-compose.yml.hbs @@ -61,6 +61,14 @@ services: # ── Telegram ── - TELEGRAM_BOT_TOKEN={{this.telegram.bot_token}} - ADMIN_TELEGRAM_ID={{this.telegram.allow_from}} + # ── Matrix ── + # MATRIX_ENABLED is always emitted so openclaw.jsonc ${MATRIX_ENABLED} resolves cleanly. + # Sensitive vars (homeserver, token) are only emitted when Matrix is enabled. + - MATRIX_ENABLED={{this.matrix_enabled}} +{{#if this.matrix_enabled}} + - MATRIX_HOMESERVER={{this.matrix.homeserver}} + - MATRIX_ACCESS_TOKEN={{this.matrix.access_token}} +{{/if}} # ── Domain & UI ── - OPENCLAW_DOMAIN={{this.domain}} - OPENCLAW_DOMAIN_PATH={{this.domain_path}} diff --git a/docs/MATRIX.md b/docs/MATRIX.md new file mode 100644 index 0000000..881a3e1 --- /dev/null +++ b/docs/MATRIX.md @@ -0,0 +1,216 @@ +# Matrix Setup + +OpenClaw supports Matrix as a messaging channel via the `@openclaw/matrix` plugin. Matrix is an open, federated, E2E-encrypted messaging protocol. You can use any Matrix account on any homeserver — no phone number required. + +Full upstream config reference: https://docs.openclaw.ai/channels/matrix + +--- + +## 1. Create a Matrix Bot Account + +Create a dedicated Matrix account for your claw to use as its bot identity. + +**Option A: matrix.org (easiest)** + +1. Go to [app.element.io](https://app.element.io) and register a new account +2. Use a username that identifies it as a bot (e.g., `openclaw-yourname`) +3. Note the homeserver: `https://matrix.org` + +**Option B: Any other public homeserver** + +Any Matrix homeserver works. The account only needs to be able to join rooms and receive DMs. + +--- + +## 2. Get an Access Token + +The stack uses access-token auth. Do not use password auth. + +1. Log in to your bot account in Element (or any Matrix client) +2. Go to **Settings** → **Help & About** → scroll to **Access Token** +3. Copy the token — it looks like `syt_...` + +Alternatively, use the Matrix REST API: + +```bash +curl -XPOST 'https://matrix.org/_matrix/client/v3/login' \ + -H 'Content-Type: application/json' \ + -d '{"type":"m.login.password","user":"@yourbot:matrix.org","password":"yourpassword"}' +# → {"access_token": "syt_...", ...} +``` + +--- + +## 3. Configure + +Add to `stack.yml` under the claw that should use Matrix: + +```yaml +claws: + personal-claw: + matrix: + enabled: true + access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} + # homeserver: "https://matrix.org" # optional — overrides the default +``` + +Add the token to `.env`: + +```env +PERSONAL_CLAW_MATRIX_ACCESS_TOKEN=syt_your_token_here +``` + +The claw name prefix (e.g. `PERSONAL_CLAW`) must match the claw key in `stack.yml` with hyphens replaced by underscores and uppercased. + +Then rebuild: + +```bash +npm run pre-deploy +``` + +--- + +## 4. Room Configuration (optional) + +By default: + +- DMs require pairing approval (`dm.policy: pairing`) +- Group rooms require explicit allowlisting (`groupPolicy: allowlist`) +- The bot auto-joins any room it's invited to (`auto_join: always`) + +To allow specific rooms, add them to `stack.yml`: + +```yaml +matrix: + enabled: true + access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} + groups: + "!roomid:matrix.org": + enabled: true + mention_only: false # true = only respond when @mentioned +``` + +Room IDs look like `!abc123:matrix.org`. Find yours in Element under **Room Settings** → **Advanced** → **Internal Room ID**. + +--- + +## 5. Deploy + +After running `npm run pre-deploy`, sync and restart the claw: + +```bash +scripts/sync-deploy.sh +sudo -u openclaw bash -c 'cd /home/openclaw && docker compose up -d personal-claw' +``` + +On first boot, the entrypoint installs `@openclaw/matrix` into the claw's persisted extensions directory (`~/.openclaw/extensions/@openclaw/matrix`). Subsequent restarts skip the install (fast filesystem check). + +--- + +## 6. Pair Your Account + +Matrix DMs default to pairing approval. After the claw starts: + +1. Send any message to your bot account from your personal Matrix client +2. On the VPS, check pending pairings: + +```bash +openclaw pairing list matrix +``` + +3. Approve your device: + +```bash +openclaw pairing approve matrix +``` + +After approval, the bot responds to your DMs normally. + +--- + +## Rooms and Mention Gating + +Rooms use `groupPolicy: allowlist` by default — the bot only responds in rooms explicitly listed in `matrix.groups`. To respond in a room: + +1. Invite the bot account to the room from your Matrix client +2. Add the room ID to `stack.yml` under `matrix.groups` +3. Re-deploy + +To require the bot to be @mentioned before it responds in a room: + +```yaml +groups: + "!roomid:matrix.org": + enabled: true + mention_only: true +``` + +--- + +## E2EE (Optional) + +Matrix E2EE is supported but requires additional setup: + +1. Set `encryption: true` in `openclaw.jsonc` (in the `channels.matrix` block) +2. Verify the bot's Matrix device from another Matrix client (e.g., Element → Security → Verify) +3. Ensure `~/.openclaw/matrix/` is included in your backup (crypto state must survive restarts) + +> **Recommendation:** Start without E2EE (`encryption: false`). Add E2EE only after confirming the Matrix channel works and backup coverage includes `~/.openclaw/matrix/`. Rotating the access token creates a new device identity and requires re-verification in encrypted rooms. + +> **Beeper:** Beeper requires E2EE to be enabled. Configure `encryption: true` and complete device verification before using Beeper as a Matrix client. + +--- + +## Token Rotation + +If you need to rotate the Matrix access token: + +1. Generate a new token for the bot account (via Element Settings or the login API) +2. Update `.env` with the new token +3. Run `npm run pre-deploy` → `scripts/sync-deploy.sh` +4. Restart the claw: `docker compose up -d ` + +Token rotation does not affect OpenClaw device pairings, but may require Matrix-side device re-verification in encrypted rooms. + +--- + +## Troubleshooting + +### Bot not responding to DMs + +Check pending pairings: + +```bash +openclaw pairing list matrix +openclaw pairing approve matrix +``` + +### Bot not responding in rooms + +- Confirm the room ID is in `matrix.groups` in `stack.yml` +- Confirm `auto_join` is not set to `never` +- Check claw logs: `sudo docker logs -openclaw- 2>&1 | grep -i matrix` + +### Plugin install failed + +Check entrypoint logs: + +```bash +sudo docker logs -openclaw- 2>&1 | grep 'matrix' +``` + +If the plugin install failed, you can install it manually: + +```bash +sudo docker exec --user node -openclaw- openclaw plugins install @openclaw/matrix +``` + +### E2EE verification + +If the bot is in encrypted rooms and messages are not decrypted: + +1. Open Element with your personal account +2. Go to the room → click the bot's name → **Verify** +3. Complete the emoji verification flow + +The bot's device must be verified from within each encrypted room. diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md index f0fc26b..1fff2a9 100644 --- a/docs/PROPOSAL-MATRIX-CHANNEL.md +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -77,7 +77,7 @@ The stack should render OpenClaw's native Matrix config into `openclaw.json`, fo "matrix": { "enabled": true, "homeserver": "$MATRIX_HOMESERVER", - "accessToken": "$MATRIX_ACCESS_TOKEN", + "accessToken": "$MATRIX_ACCESS_TOKEN", // Access token for the Matrix account this claw uses as its bot-style identity "encryption": false, "dm": { "policy": "pairing" }, "groupPolicy": "allowlist" @@ -137,7 +137,7 @@ For v1, the intended model is: This is not a "one Matrix account per human user" design. Multiple people can use the same claw through the same bot account, subject to the claw's pairing and room policy. -Upstream supports multiple Matrix accounts per claw, but that is a phase-2 feature and is not needed for the basic model above. +Upstream supports multiple Matrix accounts per claw. In this stack, the normal and intended relationship is one Matrix account per claw, and that is the model used by this proposal. --- @@ -168,7 +168,7 @@ claws: main: matrix: enabled: true - access_token: ${MAIN_CLAW_MATRIX_ACCESS_TOKEN} + access_token: ${MAIN_CLAW_MATRIX_ACCESS_TOKEN} # Access token for the Matrix account this claw logs in as ``` This matches the repo's current config style and deep-merge behavior. The render step can translate it into upstream fields such as `dm.policy`, `groupPolicy`, `groupAllowFrom`, and `autoJoin`. @@ -194,9 +194,12 @@ MAIN_CLAW_MATRIX_ACCESS_TOKEN= # MAIN_CLAW_MATRIX_HOMESERVER=https://matrix.org ``` -Use access-token auth only in this stack. The rendered container env can still expose `MATRIX_HOMESERVER` and `MATRIX_ACCESS_TOKEN`, but stack inputs should follow the per-claw naming pattern already used elsewhere in this repo. If multi-account support is added later, it belongs in `stack.yml`, not as a flat env-var scheme. +Use access-token auth only in this stack. The rendered container env can still expose `MATRIX_HOMESERVER` and `MATRIX_ACCESS_TOKEN`, but stack inputs should follow the per-claw naming pattern already used elsewhere in this repo. Multi-account support is not part of this stack model and does not need a flat env-var scheme. `MAIN_CLAW_MATRIX_ACCESS_TOKEN` is only an example. The actual env var name depends on the claw name, such as `ALERTS_CLAW_MATRIX_ACCESS_TOKEN`. +This token belongs to the Matrix account the claw uses as its bot-style identity, not to the homeserver itself. + +This proposal is about the claw's interactive Matrix identity. If Matrix is later used for host/infrastructure alerts, that should use a separate Matrix sender identity, separate access token, and likely a separate room configuration rather than reusing the claw's chat identity. ### `openclaw.jsonc` template @@ -259,6 +262,7 @@ Add operator documentation covering: - Creating a Matrix bot account - Obtaining an access token +- Understanding that the claw's Matrix access token is for interactive chat, not for a separate infra-alert sender - DM pairing approval with `openclaw pairing list matrix` and `openclaw pairing approve matrix ` - Inviting the bot to a room before room usage - How `auto_join` and `auto_join_allowlist` affect room invites @@ -341,9 +345,9 @@ Release gate for production E2EE: ### Multi-account -Upstream supports multiple Matrix accounts per claw, but that should remain out of scope for v1. +Upstream supports multiple Matrix accounts per claw, but that is not part of this stack's intended model. -For this stack, the v1 model is one Matrix bot account per claw. Multi-account support would only be useful later for cases such as separate assistant and alerts bots or account-specific routing/policy. +For this stack, the normal model is one Matrix account per claw, and that is the model used in this proposal. Multi-account support is optional and remains out of scope for v1. --- @@ -446,7 +450,7 @@ Reasoning: ### Multi-account scope -Initial implementation should expose only the default Matrix account. Support for multiple Matrix accounts per claw should be deferred. +Initial implementation should expose only the default Matrix account. Multiple Matrix accounts per claw are possible upstream, but that is not part of this stack's intended model. Reasoning: diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index dc4a288..618a865 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -64,6 +64,18 @@ "streamMode": "partial" // Stream partial responses to Telegram // botToken read from TELEGRAM_BOT_TOKEN env var (set in docker-compose.yml) // WARNING: Each claw MUST use a unique bot token. Sharing tokens causes getUpdates 409 conflicts. + }, + // Matrix channel — enabled via MATRIX_ENABLED env var (controlled by matrix.enabled in stack.yml). + // Configure matrix: in stack.yml, set access token in .env, then run npm run pre-deploy. + // Full config reference: https://docs.openclaw.ai/channels/matrix + // Setup guide: docs/MATRIX.md + "matrix": { + "enabled": "${MATRIX_ENABLED}", + "homeserver": "${MATRIX_HOMESERVER}", + "accessToken": "${MATRIX_ACCESS_TOKEN}", // Access token for the Matrix bot account + "encryption": false, // Set to true for E2EE (requires crypto module — see docs/MATRIX.md) + "dm": { "policy": "pairing" }, // Require device pairing for DMs + "groupPolicy": "allowlist" // Only respond in explicitly listed rooms } }, "logging": { diff --git a/stack.yml.example b/stack.yml.example index e319ed2..e3cc10d 100644 --- a/stack.yml.example +++ b/stack.yml.example @@ -80,6 +80,20 @@ defaults: dashboard_path: /dashboard telegram: allow_from: ${ADMIN_TELEGRAM_ID} + # Matrix channel (optional — alternative or complement to Telegram) + # Enable per-claw by setting matrix.enabled: true and providing an access token. + # Full config reference: https://docs.openclaw.ai/channels/matrix + matrix: + enabled: false + homeserver: "https://matrix.org" + dm_policy: pairing # pairing | open | closed + dm_allow_from: [] + group_policy: allowlist # allowlist | open | closed + encryption: false # true requires crypto module — see docs/MATRIX.md + groups: {} # "!roomid:matrix.org": { enabled: true, mention_only: false } + group_allow_from: [] + auto_join: always # always | allowlist | never + auto_join_allowlist: [] resources: cpus: 6 memory: 12G @@ -95,6 +109,10 @@ claws: dashboard_port: 6090 telegram: bot_token: ${PERSONAL_CLAW_TELEGRAM_BOT_TOKEN} + # matrix: + # enabled: true + # access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} # Access token for the Matrix bot account + # # homeserver: "https://matrix.org" # Override stack-wide default if needed health_check_cron: true # Enable health check cron on the main claw - not needed on each claw # work-claw: From e030c95a0d2e8119f5868eb9ed1151e66dce1c20 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 19:49:29 -0300 Subject: [PATCH 03/17] fix(backup): include matrix/ in per-claw backup Matrix sync state and E2EE crypto keys live in .openclaw/matrix/. Without this, a restore would lose the bot's device identity and require re-verification in all encrypted rooms. matrix_dir is conditionally populated (ls -d 2>/dev/null) so the tar command is unchanged for claws without Matrix enabled. Also updates the 06-backup.md "What Gets Backed Up" table. --- deploy/host/backup.sh | 3 +++ playbooks/06-backup.md | 1 + 2 files changed, 4 insertions(+) diff --git a/deploy/host/backup.sh b/deploy/host/backup.sh index 67a4ccb..462adc6 100755 --- a/deploy/host/backup.sh +++ b/deploy/host/backup.sh @@ -32,13 +32,16 @@ while IFS= read -r install_dir; do # Create backup of this claw's config and data. # Include all workspace dirs (main=workspace, agents=workspace-). + # Include matrix/ when present — contains sync state and E2EE crypto keys. # || true: continue to other instances if one fails (error still printed) workspace_dirs=$(cd "${inst_dir}" && ls -d .openclaw/workspace .openclaw/workspace-* 2>/dev/null || true) + matrix_dir=$(cd "${inst_dir}" && ls -d .openclaw/matrix 2>/dev/null || true) tar -czf "${BACKUP_FILE}" \ -C "${inst_dir}" \ .openclaw/openclaw.json \ .openclaw/credentials \ ${workspace_dirs} \ + ${matrix_dir} \ || true # Set ownership so container can also access backups if needed diff --git a/playbooks/06-backup.md b/playbooks/06-backup.md index 82319f3..cc9b302 100644 --- a/playbooks/06-backup.md +++ b/playbooks/06-backup.md @@ -129,6 +129,7 @@ cat /logs/session-prune.log | `instances//.openclaw/openclaw.json` | OpenClaw configuration (per-claw) | | `instances//.openclaw/credentials/` | API keys and tokens (per-claw) | | `instances//.openclaw/workspace/` | User workspaces and data (per-claw) | +| `instances//.openclaw/matrix/` | Matrix sync state and E2EE crypto keys (per-claw, when present) | | `docker-compose.yml` | Docker Compose deployment config | --- From 4e295ec84e4da6e9db5d5940673dbfc6cc1ee972 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 20:10:14 -0300 Subject: [PATCH 04/17] fix(matrix): address code review issues on feat/matrix-channel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Critical: - pre-deploy: always include MATRIX_HOMESERVER and MATRIX_ACCESS_TOKEN in empty-env-vars regardless of any claw's config. Previously computed against only the first claw, breaking multi-claw stacks with heterogeneous Matrix config (enabled on one claw, disabled on another). - openclaw.jsonc: add @openclaw/matrix to plugins.allow and plugins.entries (enabled via ${MATRIX_ENABLED}). Without this, a sync-deploy that rewrites openclaw.json removes Matrix plugin allowance even when the plugin is installed — and the entrypoint's directory-existence check prevents reinstall. Moderate: - entrypoint: change matrix plugin install failure from warning to hard error (exit 1). Booting with MATRIX_ENABLED=true and a non-functional Matrix channel is misleading; a clear failure is better than a silent degradation. - MATRIX.md: fix deploy command (service name is -openclaw-, not just the claw key); fix E2EE section (encryption not rendered from stack.yml — must edit per-claw openclaw.jsonc directly); update room config section to clarify groups require openclaw.jsonc edit or Control UI. - stack.yml.example: annotate which matrix fields are rendered into docker-compose.yml vs which require direct openclaw.jsonc configuration. --- build/pre-deploy.mjs | 13 ++++++----- deploy/openclaw-stack/entrypoint.sh | 9 +++++--- docs/MATRIX.md | 36 ++++++++++++++++++----------- openclaw/default/openclaw.jsonc | 10 +++++++- stack.yml.example | 22 +++++++++++------- 5 files changed, 58 insertions(+), 32 deletions(-) diff --git a/build/pre-deploy.mjs b/build/pre-deploy.mjs index a05ff5e..631c60e 100644 --- a/build/pre-deploy.mjs +++ b/build/pre-deploy.mjs @@ -654,11 +654,6 @@ async function main() { EVENTS_URL: "events_url", LLEMTRY_URL: "llemtry_url", ADMIN_TELEGRAM_ID: "telegram.allow_from", - // Matrix vars: always present in env (openclaw.jsonc references them via ${VAR}). - // When Matrix is disabled, they are empty — pre-resolved here so OpenClaw's - // ${VAR} substitution doesn't throw MissingEnvVarError on the matrix channel block. - MATRIX_HOMESERVER: "matrix.homeserver", - MATRIX_ACCESS_TOKEN: "matrix.access_token", }; // Check against first claw (these vars are stack-wide, same for all claws) const firstClaw = Object.values(claws)[0]; @@ -668,7 +663,13 @@ async function main() { return !val && val !== 0 && val !== false; }) .map(([envVar]) => envVar); - writeFileSync(join(DEPLOY_DIR, "openclaw-stack", "empty-env-vars"), emptyVars.join("\n") + "\n"); + // Matrix vars are per-claw: only emitted in docker-compose.yml when matrix.enabled=true. + // Always pre-resolve them regardless of any claw's config, so openclaw.jsonc ${MATRIX_*} + // substitution succeeds on claws where matrix is disabled and these env vars are absent + // from the container environment. Checking only the first claw would break multi-claw + // stacks where claws have heterogeneous Matrix config. + const alwaysResolveVars = ["MATRIX_HOMESERVER", "MATRIX_ACCESS_TOKEN"]; + writeFileSync(join(DEPLOY_DIR, "openclaw-stack", "empty-env-vars"), [...emptyVars, ...alwaysResolveVars].join("\n") + "\n"); // 7d-post. Resolve {{INSTALL_DIR}} in host/ files (cron configs, logrotate) const installDir = String(stack.install_dir || "/home/openclaw"); diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 38873fd..1f74c4f 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -109,9 +109,12 @@ if [ "${MATRIX_ENABLED:-false}" = "true" ]; then MATRIX_EXT_DIR="/home/node/.openclaw/extensions/@openclaw/matrix" if [ ! -d "$MATRIX_EXT_DIR" ]; then echo "[entrypoint] Installing @openclaw/matrix plugin..." - gosu node openclaw plugins install @openclaw/matrix && \ - echo "[entrypoint] @openclaw/matrix installed" || \ - echo "[entrypoint] WARNING: @openclaw/matrix install failed — Matrix channel will not be available" + if gosu node openclaw plugins install @openclaw/matrix; then + echo "[entrypoint] @openclaw/matrix installed" + else + echo "[entrypoint] ERROR: @openclaw/matrix install failed — cannot start with MATRIX_ENABLED=true" >&2 + exit 1 + fi else echo "[entrypoint] @openclaw/matrix already installed" fi diff --git a/docs/MATRIX.md b/docs/MATRIX.md index 881a3e1..dfd3caa 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -100,7 +100,8 @@ After running `npm run pre-deploy`, sync and restart the claw: ```bash scripts/sync-deploy.sh -sudo -u openclaw bash -c 'cd /home/openclaw && docker compose up -d personal-claw' +# Restart the specific claw (service name: -openclaw-) +sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-personal-claw' ``` On first boot, the entrypoint installs `@openclaw/matrix` into the claw's persisted extensions directory (`~/.openclaw/extensions/@openclaw/matrix`). Subsequent restarts skip the install (fast filesystem check). @@ -130,20 +131,25 @@ After approval, the bot responds to your DMs normally. ## Rooms and Mention Gating -Rooms use `groupPolicy: allowlist` by default — the bot only responds in rooms explicitly listed in `matrix.groups`. To respond in a room: +Rooms use `groupPolicy: allowlist` by default — the bot only responds in rooms explicitly listed in `matrix.groups`. Room allowlisting and mention gating are configured directly in the per-claw `openclaw.jsonc` (they are not rendered from `stack.yml`). To respond in a room: 1. Invite the bot account to the room from your Matrix client -2. Add the room ID to `stack.yml` under `matrix.groups` -3. Re-deploy +1. Edit `openclaw//openclaw.jsonc` and add the room under `channels.matrix.groups`: + +```jsonc +"matrix": { + // ... + "groups": { + "!roomid:matrix.org": { "enabled": true, "mention_only": false } + } +} +``` -To require the bot to be @mentioned before it responds in a room: +1. Run `npm run pre-deploy && scripts/sync-deploy.sh`, then restart the claw -```yaml -groups: - "!roomid:matrix.org": - enabled: true - mention_only: true -``` +Alternatively, configure rooms live via the Control UI without redeploying. + +To require the bot to be @mentioned before it responds: set `"mention_only": true` for that room. --- @@ -151,11 +157,13 @@ groups: Matrix E2EE is supported but requires additional setup: -1. Set `encryption: true` in `openclaw.jsonc` (in the `channels.matrix` block) +1. Edit `openclaw//openclaw.jsonc` and set `"encryption": true` in the `channels.matrix` block, then run `npm run pre-deploy && scripts/sync-deploy.sh` 2. Verify the bot's Matrix device from another Matrix client (e.g., Element → Security → Verify) -3. Ensure `~/.openclaw/matrix/` is included in your backup (crypto state must survive restarts) +3. Ensure `~/.openclaw/matrix/` is included in your backup (crypto state must survive restarts — the backup script covers this automatically) + +> **Note:** `encryption` is not rendered from `stack.yml` — it must be set directly in the per-claw `openclaw.jsonc`. Use the Control UI or a direct file edit. -> **Recommendation:** Start without E2EE (`encryption: false`). Add E2EE only after confirming the Matrix channel works and backup coverage includes `~/.openclaw/matrix/`. Rotating the access token creates a new device identity and requires re-verification in encrypted rooms. +> **Recommendation:** Start without E2EE (`encryption: false`). Add E2EE only after confirming the Matrix channel works. Rotating the access token creates a new device identity and requires re-verification in encrypted rooms. > **Beeper:** Beeper requires E2EE to be enabled. Configure `encryption: true` and complete device verification before using Beeper as a Matrix client. diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index 618a865..50d990a 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -272,7 +272,8 @@ "enabled": true, "allow": [ "telemetry", // Unified telemetry — replaces llm-logger plugin and debug-logger hook - "memory-core" // File-backed memory search tools + "memory-core", // File-backed memory search tools + "@openclaw/matrix" // Matrix channel — loaded when enabled; ignored when disabled or not installed ], "load": { "paths": ["/app/openclaw-stack/plugins"] // Load directly from openclaw-stack mount — avoids copying into OpenClaw's namespace @@ -322,6 +323,13 @@ // Loaded from /app/extensions/memory-core/ (ships with OpenClaw). "memory-core": { "enabled": true + }, + // Matrix Channel Plugin + // Installed by entrypoint on first boot when MATRIX_ENABLED=true. + // Declaring it here ensures the plugin remains allowed after sync-deploy rewrites openclaw.json. + // enabled: "false" → plugin is not loaded (channel inactive); OpenClaw skips missing extensions. + "@openclaw/matrix": { + "enabled": "${MATRIX_ENABLED}" } } }, diff --git a/stack.yml.example b/stack.yml.example index e3cc10d..4255698 100644 --- a/stack.yml.example +++ b/stack.yml.example @@ -83,17 +83,23 @@ defaults: # Matrix channel (optional — alternative or complement to Telegram) # Enable per-claw by setting matrix.enabled: true and providing an access token. # Full config reference: https://docs.openclaw.ai/channels/matrix + # Setup guide: docs/MATRIX.md + # + # Rendered into docker-compose.yml (controlled via stack.yml + .env): + # enabled, homeserver, access_token + # Requires direct openclaw.jsonc edit or Control UI (not rendered from stack.yml): + # encryption, dm_policy, group_policy, auto_join, groups (room allowlist) matrix: enabled: false homeserver: "https://matrix.org" - dm_policy: pairing # pairing | open | closed - dm_allow_from: [] - group_policy: allowlist # allowlist | open | closed - encryption: false # true requires crypto module — see docs/MATRIX.md - groups: {} # "!roomid:matrix.org": { enabled: true, mention_only: false } - group_allow_from: [] - auto_join: always # always | allowlist | never - auto_join_allowlist: [] + # Per-claw fields (rendered): + # access_token: ${CLAW_NAME_MATRIX_ACCESS_TOKEN} + # Not rendered — configure in per-claw openclaw.jsonc or via Control UI: + # dm_policy: pairing # pairing | open | closed + # group_policy: allowlist # allowlist | open | closed + # encryption: false # true requires E2EE crypto — see docs/MATRIX.md §E2EE + # auto_join: always # always | allowlist | never + # groups: {} # room allowlist — see docs/MATRIX.md §Rooms resources: cpus: 6 memory: 12G From 40cae9bdaade8aa87a3ca301bd322ef96f892bac Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 20:20:08 -0300 Subject: [PATCH 05/17] =?UTF-8?q?fix(matrix):=20correct=20plugin=20model?= =?UTF-8?q?=20=E2=80=94=20@openclaw/matrix=20is=20bundled,=20not=20externa?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirmed against a live OpenClaw installation: @openclaw/matrix ships bundled with OpenClaw (extensions dir is empty; matrix/ state dir exists). No install step is needed. Plugin key in config is "matrix" (unscoped), not "@openclaw/matrix". - entrypoint: remove the `openclaw plugins install @openclaw/matrix` block entirely - openclaw.jsonc: remove "@openclaw/matrix" from plugins.allow (not needed for bundled plugins); rename plugins.entries key from "@openclaw/matrix" to "matrix" - MATRIX.md: update §5 Deploy and Plugin install troubleshooting to reflect that the plugin is bundled and auto-loaded when MATRIX_ENABLED=true --- deploy/openclaw-stack/entrypoint.sh | 19 ------------------- docs/MATRIX.md | 14 ++++---------- openclaw/default/openclaw.jsonc | 11 +++++------ 3 files changed, 9 insertions(+), 35 deletions(-) diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 1f74c4f..019cc42 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -100,25 +100,6 @@ echo "prefix=$npm_global" > /home/node/.npmrc export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" -# ── Matrix plugin install (if enabled) ────────────────────────────── -# Fast filesystem check — if @openclaw/matrix is already installed, skip entirely. -# Only runs when MATRIX_ENABLED=true (controlled by matrix.enabled in stack.yml). -# Runs as node (uid 1000) so plugin files under ~/.openclaw/extensions keep the -# same ownership as the rest of the persisted .openclaw state. -if [ "${MATRIX_ENABLED:-false}" = "true" ]; then - MATRIX_EXT_DIR="/home/node/.openclaw/extensions/@openclaw/matrix" - if [ ! -d "$MATRIX_EXT_DIR" ]; then - echo "[entrypoint] Installing @openclaw/matrix plugin..." - if gosu node openclaw plugins install @openclaw/matrix; then - echo "[entrypoint] @openclaw/matrix installed" - else - echo "[entrypoint] ERROR: @openclaw/matrix install failed — cannot start with MATRIX_ENABLED=true" >&2 - exit 1 - fi - else - echo "[entrypoint] @openclaw/matrix already installed" - fi -fi # ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. diff --git a/docs/MATRIX.md b/docs/MATRIX.md index dfd3caa..37dfcae 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -104,7 +104,7 @@ scripts/sync-deploy.sh sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-personal-claw' ``` -On first boot, the entrypoint installs `@openclaw/matrix` into the claw's persisted extensions directory (`~/.openclaw/extensions/@openclaw/matrix`). Subsequent restarts skip the install (fast filesystem check). +On first boot, the OpenClaw gateway loads the bundled `@openclaw/matrix` plugin automatically when `MATRIX_ENABLED=true`. No installation step is required — the plugin ships with OpenClaw. --- @@ -199,18 +199,12 @@ openclaw pairing approve matrix - Confirm `auto_join` is not set to `never` - Check claw logs: `sudo docker logs -openclaw- 2>&1 | grep -i matrix` -### Plugin install failed +### Plugin not loading -Check entrypoint logs: +The `@openclaw/matrix` plugin is bundled with OpenClaw. If the channel is enabled but not responding, check that the plugin entry is in `openclaw.json`: ```bash -sudo docker logs -openclaw- 2>&1 | grep 'matrix' -``` - -If the plugin install failed, you can install it manually: - -```bash -sudo docker exec --user node -openclaw- openclaw plugins install @openclaw/matrix +sudo docker exec --user node -openclaw- openclaw plugins list ``` ### E2EE verification diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index 50d990a..38f306c 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -272,8 +272,8 @@ "enabled": true, "allow": [ "telemetry", // Unified telemetry — replaces llm-logger plugin and debug-logger hook - "memory-core", // File-backed memory search tools - "@openclaw/matrix" // Matrix channel — loaded when enabled; ignored when disabled or not installed + "memory-core" // File-backed memory search tools + // @openclaw/matrix is bundled with OpenClaw — no allow entry needed; declare in entries below ], "load": { "paths": ["/app/openclaw-stack/plugins"] // Load directly from openclaw-stack mount — avoids copying into OpenClaw's namespace @@ -325,10 +325,9 @@ "enabled": true }, // Matrix Channel Plugin - // Installed by entrypoint on first boot when MATRIX_ENABLED=true. - // Declaring it here ensures the plugin remains allowed after sync-deploy rewrites openclaw.json. - // enabled: "false" → plugin is not loaded (channel inactive); OpenClaw skips missing extensions. - "@openclaw/matrix": { + // @openclaw/matrix is bundled with OpenClaw — no install step needed. + // Plugin key is "matrix" (unscoped). enabled: "false" → channel inactive. + "matrix": { "enabled": "${MATRIX_ENABLED}" } } From e8317269eddf8bc317df982616aa635109eb81dc Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 20:23:18 -0300 Subject: [PATCH 06/17] docs(proposal): correct @openclaw/matrix plugin model Confirmed from live install: @openclaw/matrix is bundled with OpenClaw, not an external extension. Update all sections that assumed an install step was needed, that the plugin lives in ~/.openclaw/extensions/, or that the config key is "@openclaw/matrix". Update status to Implemented. --- docs/PROPOSAL-MATRIX-CHANNEL.md | 85 ++++++++++----------------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md index 1fff2a9..1d3bb30 100644 --- a/docs/PROPOSAL-MATRIX-CHANNEL.md +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -1,7 +1,7 @@ # Proposal: feat/matrix-channel — Matrix Channel Integration -**Status:** Proposed -**Branch:** `feat/matrix-channel` (not yet created) +**Status:** Implemented +**Branch:** `feat/matrix-channel` **Depends on:** None **Idempotency note:** In this context, "idempotent" means the change can be applied repeatedly without drift and can be introduced either before initial deployment or later on an existing deployment. @@ -19,16 +19,16 @@ OpenClaw supports Matrix via the `@openclaw/matrix` plugin. For this stack, the ## What Upstream Already Supports -Per the current OpenClaw docs, Matrix is: +Per the current OpenClaw docs and confirmed against a live installation, Matrix is: -- Supported via plugin, not built into the core install -- Installed with `openclaw plugins install @openclaw/matrix` +- **Bundled with OpenClaw core** — `~/.openclaw/extensions/` is empty on a working Matrix install; no `plugins install` step needed +- Plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` - Configured under `channels.matrix` - Compatible with direct messages, rooms, threads, media, reactions, polls, location, and native commands - Capable of E2EE when `channels.matrix.encryption: true` and the crypto module is available - Able to run multiple Matrix accounts via `channels.matrix.accounts` -Important implication: this stack does not need to invent a Matrix transport layer. It needs to make plugin installation and Matrix channel configuration first-class deployment concerns. +Important implication: this stack does not need to invent a Matrix transport layer, and does not need to manage plugin installation. It needs to make Matrix channel configuration first-class deployment concerns. --- @@ -36,7 +36,7 @@ Important implication: this stack does not need to invent a Matrix transport lay Implement Matrix in `openclaw-stack` using OpenClaw's native Matrix plugin and config model: -1. Install `@openclaw/matrix` during provisioning/startup for each OpenClaw instance. +1. Activate the bundled `@openclaw/matrix` plugin via `plugins.entries.matrix.enabled` — no install step needed. 2. Expose Matrix credentials/config through `stack.yml` and `.env`. 3. Render `channels.matrix` into each instance's `openclaw.json`. 4. Persist Matrix runtime state so access tokens, sync state, and crypto state survive restarts. @@ -48,24 +48,21 @@ Implement Matrix through the official plugin and stack-managed configuration. ## Proposed Stack Design -### 1. Plugin installation +### 1. Plugin activation -Matrix ships as a plugin and is not bundled with OpenClaw core. The stack therefore needs an explicit install step for: +Matrix is bundled with OpenClaw core — no install step is needed. The `~/.openclaw/extensions/` directory remains empty on a working Matrix installation. -```bash -openclaw plugins install @openclaw/matrix -``` - -This should happen in a deploy-managed, repeatable way, not as an ad hoc manual command after boot. +To activate the plugin, declare it in `plugins.entries` using the unscoped key `"matrix"`: -Implementation: - -- Install/check the plugin at runtime during claw startup, against the claw's persisted `.openclaw` directory -- Run the install/check as the claw's runtime user (`node`, uid `1000`) so files under `~/.openclaw/extensions` keep the same ownership model as the rest of the persisted state -- Keep plugin files in the per-instance persisted extensions path -- Make the install step safe to re-run on deploy +```jsonc +"plugins": { + "entries": { + "matrix": { "enabled": "${MATRIX_ENABLED}" } + } +} +``` -Build-time changes should only be used if Matrix needs extra OS/package dependencies in the image. Plugin presence itself should remain a per-claw runtime concern. +When `MATRIX_ENABLED=false`, the plugin entry remains in the config but the channel is inactive. When `MATRIX_ENABLED=true`, the channel starts on gateway boot — no restart of the entrypoint is needed beyond the normal container start. ### 2. Channel configuration @@ -219,23 +216,9 @@ Render `channels.matrix` only when enabled, and pass through supported options s ### Deploy/install scripts -Add a repeatable Matrix plugin install step to the stack's provisioning flow. - -Touchpoints: +No plugin install step is needed in the entrypoint or elsewhere. Matrix is bundled and activated through config (`plugins.entries.matrix.enabled`). -- `deploy/openclaw-stack/entrypoint.sh` for per-instance install/check logic against the persisted extensions directory -- `deploy/host/build-openclaw.sh` only if image/build-time preparation is needed for Matrix runtime dependencies -- The per-instance `~/.openclaw/extensions` path -- Startup validation so a Matrix-enabled config fails clearly if the plugin is missing - -Expected behavior: - -- If `matrix.enabled` is false, no Matrix plugin install/check work is done -- If `matrix.enabled` is true, startup performs a fast existence check for the plugin under the claw's persisted extensions directory and installs it only if missing -- Re-running deploy or restart is safe and does not duplicate plugin state - -The idempotency check should be cheap, such as testing for the installed plugin directory under `~/.openclaw/extensions/`, so normal restarts do not pay repeated install overhead. -The install/check should run as the persisted runtime user so the plugin directory does not become root-owned. +The entrypoint requires no Matrix-specific changes. The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. ### `docker-compose.yml.hbs` @@ -280,7 +263,6 @@ Add pre-deploy and startup validation for these cases: - `matrix.enabled: true` requires `matrix.access_token` - `npm run pre-deploy` should fail before rendering deploy artifacts if required Matrix config is missing - `npm run pre-deploy` should fail if the resolved per-claw secret (for example `MAIN_CLAW_MATRIX_ACCESS_TOKEN`) is unset while Matrix is enabled for that claw -- Matrix-enabled config fails clearly if plugin install/check fails - `matrix.encryption: true` emits a clear warning or hard failure if crypto support is unavailable ### Restart behavior @@ -356,7 +338,7 @@ For this stack, the normal model is one Matrix account per claw, and that is the Matrix integration should remain fully idempotent in the deployment sense: - **Before initial deployment:** enable Matrix in config, provide credentials, deploy once -- **After deployment:** add Matrix config, run the normal deploy flow, and let the stack install/configure the plugin +- **After deployment:** add Matrix config, run the normal deploy flow, and let the stack configure the channel - **Repeat deploys:** re-running deploy should not duplicate plugin state or require cleanup - **Disable later:** turning Matrix off should stop channel startup without affecting unrelated OpenClaw state @@ -369,24 +351,11 @@ Clarification: ## Implementation Constraints -### Plugin install location - -Install `@openclaw/matrix` into the standard per-instance OpenClaw extensions path: - -- `~/.openclaw/extensions/` - -Reasoning: - -- This is the upstream plugin install location -- This stack already persists each claw's full `~/.openclaw` tree -- It survives restarts and image rebuilds naturally -- It avoids mixing third-party installed plugins with repo-owned plugins under `/app/openclaw-stack/plugins` +### Plugin model -Version pinning: +`@openclaw/matrix` is bundled with OpenClaw and does not live in `~/.openclaw/extensions/`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). No version pinning, no install path, no extensions directory management. -- v1 does not need explicit plugin version pinning in the proposal -- initial implementation may follow the normal install path for `@openclaw/matrix` -- if upgrade churn or regressions appear in practice, the stack can add explicit plugin version pinning later +If a future OpenClaw version moves Matrix to an external plugin, the entrypoint install pattern can be reintroduced at that time. ### Runtime persistence @@ -408,19 +377,17 @@ The stack should define expected behavior for claws that already have manual Mat Cases to handle: -- Plugin already present under `~/.openclaw/extensions` - Existing Matrix credentials and sync state already present under `~/.openclaw` - Existing claw config updated from "no Matrix" to stack-managed Matrix Expected behavior: -- The stack reuses existing persisted plugin/state where valid +- The stack reuses existing persisted Matrix state where valid - Deploy converges config without deleting valid runtime state - Validation surfaces mismatches clearly rather than silently overwriting them Mismatch handling: -- Plugin exists and version differs: warn and leave the installed plugin in place unless the stack explicitly adds plugin version pinning later - Persisted credentials exist and configured access token differs: treat the configured token as source of truth, reuse existing state where possible, and warn that Matrix device/session state may need re-verification - Persisted sync/crypto state exists and homeserver changes: warn clearly and treat this as a migration-risk change that may require a fresh Matrix session or re-sync - Persisted plugin/state exists but Matrix is now disabled in stack config: do not delete runtime state automatically; disable startup only @@ -473,4 +440,4 @@ Reasoning: ## Conclusion -The implementation should integrate the official Matrix plugin into `openclaw-stack` as a deploy-managed extension with first-class config and persistence support. +The implementation integrates Matrix into `openclaw-stack` through config and env-var wiring only. No plugin install step is needed — the plugin is bundled with OpenClaw and activated via `plugins.entries.matrix.enabled`. The stack's job is channel configuration, credential management, and persistence coverage. From 614a31c6b9170f3977e044ed1654c9bfda6441e5 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 20:40:49 -0300 Subject: [PATCH 07/17] =?UTF-8?q?fix(matrix):=20restore=20plugin=20install?= =?UTF-8?q?=20=E2=80=94=20@openclaw/matrix=20is=20external,=20not=20bundle?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverts the incorrect inference from 40cae9b and e831726 that @openclaw/matrix is bundled with OpenClaw. The empty ~/.openclaw/extensions/ on the reference server was because OpenClaw was installed from a git checkout (local path ./extensions/matrix), not because the plugin ships with the binary. Source of truth: extensions/matrix/package.json blurb says "install the plugin to enable" and openclaw.install.npmSpec = "@openclaw/matrix" with defaultChoice = "npm". The plugin key "matrix" (unscoped) from openclaw.plugin.json is kept — that part was correctly identified. - Restore entrypoint.sh matrix plugin install block (exit 1 on failure, gosu node, directory-existence idempotency check) - Restore plugins.allow "matrix" entry in openclaw/default/openclaw.jsonc - Fix plugin comments in openclaw.jsonc (external, not bundled) - Fix docs/MATRIX.md §5 and "Plugin not loading" section - Fix docs/PROPOSAL-MATRIX-CHANNEL.md plugin model description throughout --- deploy/openclaw-stack/entrypoint.sh | 22 +++++++++++++++++++++- docs/MATRIX.md | 5 +++-- docs/PROPOSAL-MATRIX-CHANNEL.md | 19 ++++++++----------- openclaw/default/openclaw.jsonc | 6 +++--- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 019cc42..fb45130 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -100,8 +100,28 @@ echo "prefix=$npm_global" > /home/node/.npmrc export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" +# ── 1h. Install @openclaw/matrix plugin (if enabled) ──────────────── +# @openclaw/matrix is an external plugin — not bundled with OpenClaw. +# Install on first boot; skip if already present (idempotent). +# Hard-fails (exit 1) if install fails while MATRIX_ENABLED=true — starting +# the gateway without the plugin would silently drop Matrix messages. +# Runs as node (uid 1000) so the install lands in node's home dir. +if [ "${MATRIX_ENABLED:-false}" = "true" ]; then + MATRIX_EXT_DIR="/home/node/.openclaw/extensions/@openclaw/matrix" + if [ ! -d "$MATRIX_EXT_DIR" ]; then + echo "[entrypoint] Installing @openclaw/matrix plugin..." + if gosu node openclaw plugins install @openclaw/matrix; then + echo "[entrypoint] @openclaw/matrix installed" + else + echo "[entrypoint] ERROR: @openclaw/matrix install failed — cannot start with MATRIX_ENABLED=true" >&2 + exit 1 + fi + else + echo "[entrypoint] @openclaw/matrix already installed, skipping" + fi +fi -# ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── +# ── 1i. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. # Real binaries live in sandbox images — shims are gateway-only (not bind-mounted). SKILL_BINS="/opt/skill-bins" diff --git a/docs/MATRIX.md b/docs/MATRIX.md index 37dfcae..ff028ae 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -104,7 +104,7 @@ scripts/sync-deploy.sh sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-personal-claw' ``` -On first boot, the OpenClaw gateway loads the bundled `@openclaw/matrix` plugin automatically when `MATRIX_ENABLED=true`. No installation step is required — the plugin ships with OpenClaw. +On first boot, the entrypoint automatically installs `@openclaw/matrix` when `MATRIX_ENABLED=true`. No manual installation step is required — the stack handles it. --- @@ -201,9 +201,10 @@ openclaw pairing approve matrix ### Plugin not loading -The `@openclaw/matrix` plugin is bundled with OpenClaw. If the channel is enabled but not responding, check that the plugin entry is in `openclaw.json`: +`@openclaw/matrix` is installed by the entrypoint on first boot when `MATRIX_ENABLED=true`. If the channel is enabled but not responding, check the container logs for install errors and verify the plugin entry is active: ```bash +sudo docker logs -openclaw- 2>&1 | grep -i '\[entrypoint\].*matrix' sudo docker exec --user node -openclaw- openclaw plugins list ``` diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md index 1d3bb30..3dcf10a 100644 --- a/docs/PROPOSAL-MATRIX-CHANNEL.md +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -21,14 +21,13 @@ OpenClaw supports Matrix via the `@openclaw/matrix` plugin. For this stack, the Per the current OpenClaw docs and confirmed against a live installation, Matrix is: -- **Bundled with OpenClaw core** — `~/.openclaw/extensions/` is empty on a working Matrix install; no `plugins install` step needed -- Plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` +- **Available as an external plugin** — `@openclaw/matrix` is installed via `openclaw plugins install @openclaw/matrix`; plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` - Configured under `channels.matrix` - Compatible with direct messages, rooms, threads, media, reactions, polls, location, and native commands - Capable of E2EE when `channels.matrix.encryption: true` and the crypto module is available - Able to run multiple Matrix accounts via `channels.matrix.accounts` -Important implication: this stack does not need to invent a Matrix transport layer, and does not need to manage plugin installation. It needs to make Matrix channel configuration first-class deployment concerns. +Important implication: this stack does not need to invent a Matrix transport layer. It needs to manage plugin installation (via the entrypoint) and make Matrix channel configuration first-class deployment concerns. --- @@ -36,7 +35,7 @@ Important implication: this stack does not need to invent a Matrix transport lay Implement Matrix in `openclaw-stack` using OpenClaw's native Matrix plugin and config model: -1. Activate the bundled `@openclaw/matrix` plugin via `plugins.entries.matrix.enabled` — no install step needed. +1. Install `@openclaw/matrix` via the entrypoint when `MATRIX_ENABLED=true` and activate via `plugins.entries.matrix.enabled`. 2. Expose Matrix credentials/config through `stack.yml` and `.env`. 3. Render `channels.matrix` into each instance's `openclaw.json`. 4. Persist Matrix runtime state so access tokens, sync state, and crypto state survive restarts. @@ -50,7 +49,7 @@ Implement Matrix through the official plugin and stack-managed configuration. ### 1. Plugin activation -Matrix is bundled with OpenClaw core — no install step is needed. The `~/.openclaw/extensions/` directory remains empty on a working Matrix installation. +`@openclaw/matrix` is an external plugin installed via `openclaw plugins install @openclaw/matrix`. The entrypoint handles this automatically when `MATRIX_ENABLED=true`, using a directory-existence check for idempotency. Install failure is a hard error (`exit 1`) — the gateway should not start with Matrix enabled but the plugin missing. To activate the plugin, declare it in `plugins.entries` using the unscoped key `"matrix"`: @@ -216,9 +215,9 @@ Render `channels.matrix` only when enabled, and pass through supported options s ### Deploy/install scripts -No plugin install step is needed in the entrypoint or elsewhere. Matrix is bundled and activated through config (`plugins.entries.matrix.enabled`). +The entrypoint installs `@openclaw/matrix` when `MATRIX_ENABLED=true`, using a directory-existence check so repeat deploys are idempotent. Install failure exits with code 1 — the gateway must not start with Matrix enabled but the plugin absent. -The entrypoint requires no Matrix-specific changes. The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. +The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. ### `docker-compose.yml.hbs` @@ -353,9 +352,7 @@ Clarification: ### Plugin model -`@openclaw/matrix` is bundled with OpenClaw and does not live in `~/.openclaw/extensions/`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). No version pinning, no install path, no extensions directory management. - -If a future OpenClaw version moves Matrix to an external plugin, the entrypoint install pattern can be reintroduced at that time. +`@openclaw/matrix` is an external plugin installed via `openclaw plugins install @openclaw/matrix`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). The entrypoint handles installation automatically when `MATRIX_ENABLED=true` and checks for an existing install to stay idempotent. ### Runtime persistence @@ -440,4 +437,4 @@ Reasoning: ## Conclusion -The implementation integrates Matrix into `openclaw-stack` through config and env-var wiring only. No plugin install step is needed — the plugin is bundled with OpenClaw and activated via `plugins.entries.matrix.enabled`. The stack's job is channel configuration, credential management, and persistence coverage. +The implementation integrates Matrix into `openclaw-stack` through plugin installation, config wiring, and env-var management. `@openclaw/matrix` is an external plugin — the entrypoint installs it automatically when `MATRIX_ENABLED=true` and activates it via `plugins.entries.matrix.enabled`. The stack's job is plugin install, channel configuration, credential management, and persistence coverage. diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index 38f306c..15698c8 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -272,8 +272,8 @@ "enabled": true, "allow": [ "telemetry", // Unified telemetry — replaces llm-logger plugin and debug-logger hook - "memory-core" // File-backed memory search tools - // @openclaw/matrix is bundled with OpenClaw — no allow entry needed; declare in entries below + "memory-core", // File-backed memory search tools + "matrix" // @openclaw/matrix channel plugin (external; installed by entrypoint when MATRIX_ENABLED=true) ], "load": { "paths": ["/app/openclaw-stack/plugins"] // Load directly from openclaw-stack mount — avoids copying into OpenClaw's namespace @@ -325,7 +325,7 @@ "enabled": true }, // Matrix Channel Plugin - // @openclaw/matrix is bundled with OpenClaw — no install step needed. + // @openclaw/matrix is an external plugin — installed by entrypoint.sh on first boot when MATRIX_ENABLED=true. // Plugin key is "matrix" (unscoped). enabled: "false" → channel inactive. "matrix": { "enabled": "${MATRIX_ENABLED}" From 07cee17f9671ae542b4a807dcefb91cf98d89c88 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 20:57:27 -0300 Subject: [PATCH 08/17] feat(matrix): add deviceName to channel config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Set deviceName to "OPENCLAW_GATEWAY" statically in openclaw.jsonc. userId is auto-generated by OpenClaw from the claw name — not configured here. --- openclaw/default/openclaw.jsonc | 1 + 1 file changed, 1 insertion(+) diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index 15698c8..b0070fd 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -73,6 +73,7 @@ "enabled": "${MATRIX_ENABLED}", "homeserver": "${MATRIX_HOMESERVER}", "accessToken": "${MATRIX_ACCESS_TOKEN}", // Access token for the Matrix bot account + "deviceName": "OPENCLAW_GATEWAY", // Device name shown in Matrix clients' device list "encryption": false, // Set to true for E2EE (requires crypto module — see docs/MATRIX.md) "dm": { "policy": "pairing" }, // Require device pairing for DMs "groupPolicy": "allowlist" // Only respond in explicitly listed rooms From 91d93464bd02fcfcb055ead319eb01359416d9b2 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 22:05:02 -0300 Subject: [PATCH 09/17] fix(docs): correct MATRIX.md room config and token rotation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - §4: remove groups-in-stack.yml example (groups not rendered from stack.yml); point to openclaw.jsonc via cross-reference to Rooms section - Rooms section: fix duplicate 1. list items → 1/2/1 (linter requires 1/1/1) - Token rotation: fix service name to match correct pattern --- docs/MATRIX.md | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/docs/MATRIX.md b/docs/MATRIX.md index ff028ae..046e086 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -78,20 +78,10 @@ By default: - Group rooms require explicit allowlisting (`groupPolicy: allowlist`) - The bot auto-joins any room it's invited to (`auto_join: always`) -To allow specific rooms, add them to `stack.yml`: - -```yaml -matrix: - enabled: true - access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} - groups: - "!roomid:matrix.org": - enabled: true - mention_only: false # true = only respond when @mentioned -``` - Room IDs look like `!abc123:matrix.org`. Find yours in Element under **Room Settings** → **Advanced** → **Internal Room ID**. +Room allowlisting is configured directly in `openclaw.jsonc` — see **Rooms and Mention Gating** below. + --- ## 5. Deploy @@ -134,7 +124,7 @@ After approval, the bot responds to your DMs normally. Rooms use `groupPolicy: allowlist` by default — the bot only responds in rooms explicitly listed in `matrix.groups`. Room allowlisting and mention gating are configured directly in the per-claw `openclaw.jsonc` (they are not rendered from `stack.yml`). To respond in a room: 1. Invite the bot account to the room from your Matrix client -1. Edit `openclaw//openclaw.jsonc` and add the room under `channels.matrix.groups`: +2. Edit `openclaw//openclaw.jsonc` and add the room under `channels.matrix.groups`: ```jsonc "matrix": { @@ -176,7 +166,7 @@ If you need to rotate the Matrix access token: 1. Generate a new token for the bot account (via Element Settings or the login API) 2. Update `.env` with the new token 3. Run `npm run pre-deploy` → `scripts/sync-deploy.sh` -4. Restart the claw: `docker compose up -d ` +4. Restart the claw: `sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-'` Token rotation does not affect OpenClaw device pairings, but may require Matrix-side device re-verification in encrypted rooms. From 89b114184313437e9b1dbdca0ca158143248672e Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 22:46:33 -0300 Subject: [PATCH 10/17] Update .env.example with Matrix access token key --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index f576b8b..ed697b2 100644 --- a/.env.example +++ b/.env.example @@ -47,6 +47,11 @@ ROOT_DOMAIN=example.com # Used in stack.yml.example for configuring e PERSONAL_CLAW_TELEGRAM_BOT_TOKEN= WORK_CLAW_TELEGRAM_BOT_TOKEN= +# ── Per-Claw Matrix Bots ───────────────────────────────────── +# Convention: _MATRIX_BOT_TOKEN +# Each claw MUST use a unique bot token to avoid polling conflicts. +PERSONAL_CLAW_MATRIX_BOT_TOKEN= + # ── Local Browser Node (docker/local-browser-node) ────────────── # Claw name in stack.yml for the local browser node to connect to # Also requires Cloudflare Access Token. From 2949d7a25fe523ef98a3d027b8f5008b16742891 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 23:04:48 -0300 Subject: [PATCH 11/17] =?UTF-8?q?fix(matrix):=20revert=20614a31c=20?= =?UTF-8?q?=E2=80=94=20@openclaw/matrix=20is=20bundled,=20not=20external?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 614a31c incorrectly concluded the plugin was external based on package.json metadata. Live testing confirms it ships inside the Docker image at /app/extensions/matrix/ and loads without an install step. The entrypoint install block was creating a second copy at ~/.openclaw/extensions/@openclaw/matrix/, causing a "duplicate plugin id" warning and a load failure (wrong import paths in the installed copy). - Remove entrypoint matrix plugin install block - Remove "matrix" from plugins.allow (not needed for bundled plugins) - Update docs and proposal to reflect bundled model --- deploy/openclaw-stack/entrypoint.sh | 22 +--------------------- docs/MATRIX.md | 5 ++--- docs/PROPOSAL-MATRIX-CHANNEL.md | 19 +++++++++++-------- openclaw/default/openclaw.jsonc | 6 +++--- 4 files changed, 17 insertions(+), 35 deletions(-) diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index fb45130..019cc42 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -100,28 +100,8 @@ echo "prefix=$npm_global" > /home/node/.npmrc export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" -# ── 1h. Install @openclaw/matrix plugin (if enabled) ──────────────── -# @openclaw/matrix is an external plugin — not bundled with OpenClaw. -# Install on first boot; skip if already present (idempotent). -# Hard-fails (exit 1) if install fails while MATRIX_ENABLED=true — starting -# the gateway without the plugin would silently drop Matrix messages. -# Runs as node (uid 1000) so the install lands in node's home dir. -if [ "${MATRIX_ENABLED:-false}" = "true" ]; then - MATRIX_EXT_DIR="/home/node/.openclaw/extensions/@openclaw/matrix" - if [ ! -d "$MATRIX_EXT_DIR" ]; then - echo "[entrypoint] Installing @openclaw/matrix plugin..." - if gosu node openclaw plugins install @openclaw/matrix; then - echo "[entrypoint] @openclaw/matrix installed" - else - echo "[entrypoint] ERROR: @openclaw/matrix install failed — cannot start with MATRIX_ENABLED=true" >&2 - exit 1 - fi - else - echo "[entrypoint] @openclaw/matrix already installed, skipping" - fi -fi -# ── 1i. Auto-generate gateway shims from sandbox-toolkit.yaml ────── +# ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. # Real binaries live in sandbox images — shims are gateway-only (not bind-mounted). SKILL_BINS="/opt/skill-bins" diff --git a/docs/MATRIX.md b/docs/MATRIX.md index 046e086..6dcee6e 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -94,7 +94,7 @@ scripts/sync-deploy.sh sudo -u openclaw bash -c 'cd && docker compose up -d -openclaw-personal-claw' ``` -On first boot, the entrypoint automatically installs `@openclaw/matrix` when `MATRIX_ENABLED=true`. No manual installation step is required — the stack handles it. +On first boot, the OpenClaw gateway loads the bundled `@openclaw/matrix` plugin automatically when `MATRIX_ENABLED=true`. No installation step is required — the plugin ships with OpenClaw. --- @@ -191,10 +191,9 @@ openclaw pairing approve matrix ### Plugin not loading -`@openclaw/matrix` is installed by the entrypoint on first boot when `MATRIX_ENABLED=true`. If the channel is enabled but not responding, check the container logs for install errors and verify the plugin entry is active: +The `@openclaw/matrix` plugin is bundled with OpenClaw. If the channel is enabled but not responding, check that the plugin entry is in `openclaw.json`: ```bash -sudo docker logs -openclaw- 2>&1 | grep -i '\[entrypoint\].*matrix' sudo docker exec --user node -openclaw- openclaw plugins list ``` diff --git a/docs/PROPOSAL-MATRIX-CHANNEL.md b/docs/PROPOSAL-MATRIX-CHANNEL.md index 3dcf10a..1d3bb30 100644 --- a/docs/PROPOSAL-MATRIX-CHANNEL.md +++ b/docs/PROPOSAL-MATRIX-CHANNEL.md @@ -21,13 +21,14 @@ OpenClaw supports Matrix via the `@openclaw/matrix` plugin. For this stack, the Per the current OpenClaw docs and confirmed against a live installation, Matrix is: -- **Available as an external plugin** — `@openclaw/matrix` is installed via `openclaw plugins install @openclaw/matrix`; plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` +- **Bundled with OpenClaw core** — `~/.openclaw/extensions/` is empty on a working Matrix install; no `plugins install` step needed +- Plugin key in `plugins.entries` is `"matrix"` (unscoped), not `"@openclaw/matrix"` - Configured under `channels.matrix` - Compatible with direct messages, rooms, threads, media, reactions, polls, location, and native commands - Capable of E2EE when `channels.matrix.encryption: true` and the crypto module is available - Able to run multiple Matrix accounts via `channels.matrix.accounts` -Important implication: this stack does not need to invent a Matrix transport layer. It needs to manage plugin installation (via the entrypoint) and make Matrix channel configuration first-class deployment concerns. +Important implication: this stack does not need to invent a Matrix transport layer, and does not need to manage plugin installation. It needs to make Matrix channel configuration first-class deployment concerns. --- @@ -35,7 +36,7 @@ Important implication: this stack does not need to invent a Matrix transport lay Implement Matrix in `openclaw-stack` using OpenClaw's native Matrix plugin and config model: -1. Install `@openclaw/matrix` via the entrypoint when `MATRIX_ENABLED=true` and activate via `plugins.entries.matrix.enabled`. +1. Activate the bundled `@openclaw/matrix` plugin via `plugins.entries.matrix.enabled` — no install step needed. 2. Expose Matrix credentials/config through `stack.yml` and `.env`. 3. Render `channels.matrix` into each instance's `openclaw.json`. 4. Persist Matrix runtime state so access tokens, sync state, and crypto state survive restarts. @@ -49,7 +50,7 @@ Implement Matrix through the official plugin and stack-managed configuration. ### 1. Plugin activation -`@openclaw/matrix` is an external plugin installed via `openclaw plugins install @openclaw/matrix`. The entrypoint handles this automatically when `MATRIX_ENABLED=true`, using a directory-existence check for idempotency. Install failure is a hard error (`exit 1`) — the gateway should not start with Matrix enabled but the plugin missing. +Matrix is bundled with OpenClaw core — no install step is needed. The `~/.openclaw/extensions/` directory remains empty on a working Matrix installation. To activate the plugin, declare it in `plugins.entries` using the unscoped key `"matrix"`: @@ -215,9 +216,9 @@ Render `channels.matrix` only when enabled, and pass through supported options s ### Deploy/install scripts -The entrypoint installs `@openclaw/matrix` when `MATRIX_ENABLED=true`, using a directory-existence check so repeat deploys are idempotent. Install failure exits with code 1 — the gateway must not start with Matrix enabled but the plugin absent. +No plugin install step is needed in the entrypoint or elsewhere. Matrix is bundled and activated through config (`plugins.entries.matrix.enabled`). -The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. +The entrypoint requires no Matrix-specific changes. The plugin is loaded by the OpenClaw gateway process on startup when the `plugins.entries.matrix` entry is enabled. ### `docker-compose.yml.hbs` @@ -352,7 +353,9 @@ Clarification: ### Plugin model -`@openclaw/matrix` is an external plugin installed via `openclaw plugins install @openclaw/matrix`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). The entrypoint handles installation automatically when `MATRIX_ENABLED=true` and checks for an existing install to stay idempotent. +`@openclaw/matrix` is bundled with OpenClaw and does not live in `~/.openclaw/extensions/`. The plugin key in `plugins.entries` is `"matrix"` (unscoped). No version pinning, no install path, no extensions directory management. + +If a future OpenClaw version moves Matrix to an external plugin, the entrypoint install pattern can be reintroduced at that time. ### Runtime persistence @@ -437,4 +440,4 @@ Reasoning: ## Conclusion -The implementation integrates Matrix into `openclaw-stack` through plugin installation, config wiring, and env-var management. `@openclaw/matrix` is an external plugin — the entrypoint installs it automatically when `MATRIX_ENABLED=true` and activates it via `plugins.entries.matrix.enabled`. The stack's job is plugin install, channel configuration, credential management, and persistence coverage. +The implementation integrates Matrix into `openclaw-stack` through config and env-var wiring only. No plugin install step is needed — the plugin is bundled with OpenClaw and activated via `plugins.entries.matrix.enabled`. The stack's job is channel configuration, credential management, and persistence coverage. diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index b0070fd..fab816f 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -273,8 +273,8 @@ "enabled": true, "allow": [ "telemetry", // Unified telemetry — replaces llm-logger plugin and debug-logger hook - "memory-core", // File-backed memory search tools - "matrix" // @openclaw/matrix channel plugin (external; installed by entrypoint when MATRIX_ENABLED=true) + "memory-core" // File-backed memory search tools + // @openclaw/matrix is bundled with OpenClaw — no allow entry needed; declare in entries below ], "load": { "paths": ["/app/openclaw-stack/plugins"] // Load directly from openclaw-stack mount — avoids copying into OpenClaw's namespace @@ -326,7 +326,7 @@ "enabled": true }, // Matrix Channel Plugin - // @openclaw/matrix is an external plugin — installed by entrypoint.sh on first boot when MATRIX_ENABLED=true. + // @openclaw/matrix is bundled with OpenClaw — no install step needed. // Plugin key is "matrix" (unscoped). enabled: "false" → channel inactive. "matrix": { "enabled": "${MATRIX_ENABLED}" From 8c38c67c6539999887d3d5cc68416a134e79fb12 Mon Sep 17 00:00:00 2001 From: Nim G Date: Sat, 7 Mar 2026 23:10:30 -0300 Subject: [PATCH 12/17] Rename Matrix bot token variable --- .env.example | 5 +++-- stack.yml.example | 4 +--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index ed697b2..fe440c9 100644 --- a/.env.example +++ b/.env.example @@ -49,8 +49,9 @@ WORK_CLAW_TELEGRAM_BOT_TOKEN= # ── Per-Claw Matrix Bots ───────────────────────────────────── # Convention: _MATRIX_BOT_TOKEN -# Each claw MUST use a unique bot token to avoid polling conflicts. -PERSONAL_CLAW_MATRIX_BOT_TOKEN= +# *Each* claw MUST use a unique bot token to avoid polling conflicts. + +CLAW_NAME_MATRIX_ACCESS_TOKEN= # ── Local Browser Node (docker/local-browser-node) ────────────── # Claw name in stack.yml for the local browser node to connect to diff --git a/stack.yml.example b/stack.yml.example index 4255698..390efa1 100644 --- a/stack.yml.example +++ b/stack.yml.example @@ -92,8 +92,6 @@ defaults: matrix: enabled: false homeserver: "https://matrix.org" - # Per-claw fields (rendered): - # access_token: ${CLAW_NAME_MATRIX_ACCESS_TOKEN} # Not rendered — configure in per-claw openclaw.jsonc or via Control UI: # dm_policy: pairing # pairing | open | closed # group_policy: allowlist # allowlist | open | closed @@ -117,7 +115,7 @@ claws: bot_token: ${PERSONAL_CLAW_TELEGRAM_BOT_TOKEN} # matrix: # enabled: true - # access_token: ${PERSONAL_CLAW_MATRIX_ACCESS_TOKEN} # Access token for the Matrix bot account + # access_token: ${CLAW_NAME_MATRIX_ACCESS_TOKEN} # Access token for the Matrix bot account, set per claw # # homeserver: "https://matrix.org" # Override stack-wide default if needed health_check_cron: true # Enable health check cron on the main claw - not needed on each claw From 387adfdf10a3f0f2fb2f949c824e091e241e0a7f Mon Sep 17 00:00:00 2001 From: Nim G Date: Sun, 8 Mar 2026 14:03:49 -0300 Subject: [PATCH 13/17] feat(matrix): add entrypoint patches, config var coercion, and default template updates Entrypoint: patch keyed-async-queue subpath regression (openclaw#32772), install matrix plugin deps on first boot (openclaw#16031). resolve-config-vars: coerce "${VAR}" whole-value patterns to native booleans/numbers so MATRIX_ENABLED=true resolves to true, not "true". Default template: set groupPolicy to "open", always load matrix plugin, keep encryption off as safe default. --- deploy/openclaw-stack/entrypoint.sh | 35 ++++++++++++++++++- deploy/openclaw-stack/resolve-config-vars.mjs | 15 +++++++- openclaw/default/openclaw.jsonc | 7 ++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index 019cc42..a7fcdc8 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -101,7 +101,40 @@ export PATH="$npm_global/bin:$PATH" echo "[entrypoint] npm global prefix set to $npm_global" -# ── 1h. Auto-generate gateway shims from sandbox-toolkit.yaml ────── +# ── 1h. Patch matrix plugin: keyed-async-queue subpath regression ── +# TODO: remove once openclaw/openclaw#32772 is fixed +# OpenClaw 2026.3.2 broke the matrix plugin — send-queue.ts imports +# "openclaw/plugin-sdk/keyed-async-queue" but jiti doesn't resolve the +# subpath. Patch to use the barrel export. Version-gated so it no-ops +# once upstream ships a fix. +SEND_QUEUE="/app/extensions/matrix/src/matrix/send-queue.ts" +OPENCLAW_VERSION=$(node -e "console.log(require('/app/package.json').version)" 2>/dev/null || echo "unknown") +if [ "$OPENCLAW_VERSION" = "2026.3.2" ] && [ -f "$SEND_QUEUE" ] && grep -q '"openclaw/plugin-sdk/keyed-async-queue"' "$SEND_QUEUE"; then + sed -i 's|"openclaw/plugin-sdk/keyed-async-queue"|"openclaw/plugin-sdk"|' "$SEND_QUEUE" + echo "[entrypoint] Patched matrix plugin keyed-async-queue import (openclaw/openclaw#32772)" +elif [ "$OPENCLAW_VERSION" != "2026.3.2" ] && [ -f "$SEND_QUEUE" ]; then + echo "[entrypoint] OpenClaw $OPENCLAW_VERSION — matrix keyed-async-queue patch no longer needed, consider removing" +fi + +# ── 1i. Install matrix plugin dependencies ───────────────────────── +# TODO: remove once openclaw/openclaw#16031 is fixed +# The bundled matrix plugin at /app/extensions/matrix declares deps in +# package.json but they aren't installed in the Docker image. Install +# them on first boot if the node_modules dir is missing. +MATRIX_EXT="/app/extensions/matrix" +if [ "${MATRIX_ENABLED:-false}" = "true" ] && [ -f "$MATRIX_EXT/package.json" ] && [ ! -d "$MATRIX_EXT/node_modules" ]; then + echo "[entrypoint] Installing matrix plugin dependencies..." + cd "$MATRIX_EXT" + if npm install --omit=dev 2>&1; then + echo "[entrypoint] Matrix plugin dependencies installed" + else + echo "[entrypoint] WARNING: matrix plugin dependency install failed" >&2 + fi + cd /app +fi + + +# ── 1j. Auto-generate gateway shims from sandbox-toolkit.yaml ────── # Shims satisfy the gateway's load-time skill binary preflight checks. # Real binaries live in sandbox images — shims are gateway-only (not bind-mounted). SKILL_BINS="/opt/skill-bins" diff --git a/deploy/openclaw-stack/resolve-config-vars.mjs b/deploy/openclaw-stack/resolve-config-vars.mjs index 1eac576..7563ae4 100644 --- a/deploy/openclaw-stack/resolve-config-vars.mjs +++ b/deploy/openclaw-stack/resolve-config-vars.mjs @@ -53,7 +53,8 @@ for (const entry of service[1]?.environment || []) { // Resolve ${VAR} and ${VAR:-default} in the raw text (preserves comments, formatting) let content = readFileSync(configFile, "utf-8"); -content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => { + +function resolveExpr(expr) { const defaultMatch = expr.match(/^([^:]+):-(.*)$/); if (defaultMatch) { const key = defaultMatch[1]; @@ -61,6 +62,18 @@ content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => { return (key in env && env[key] !== "") ? env[key] : defaultVal; } return (expr in env) ? env[expr] : ""; +} + +// When a "${VAR}" is the entire JSON value (quoted), coerce booleans and numbers +// so "enabled": "${MATRIX_ENABLED}" becomes "enabled": true, not "enabled": "true". +content = content.replace(/"(\$\{([^}]+)\})"/g, (_match, _fullRef, expr) => { + const val = resolveExpr(expr); + if (val === "true" || val === "false") return val; + if (val !== "" && !isNaN(val) && !isNaN(parseFloat(val))) return val; + return `"${val}"`; }); +// Resolve remaining ${VAR} refs (inside longer strings, unquoted positions) +content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => resolveExpr(expr)); + process.stdout.write(content); diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index fab816f..dcd946f 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -76,7 +76,7 @@ "deviceName": "OPENCLAW_GATEWAY", // Device name shown in Matrix clients' device list "encryption": false, // Set to true for E2EE (requires crypto module — see docs/MATRIX.md) "dm": { "policy": "pairing" }, // Require device pairing for DMs - "groupPolicy": "allowlist" // Only respond in explicitly listed rooms + "groupPolicy": "open" // Respond in any room when @mentioned } }, "logging": { @@ -327,9 +327,10 @@ }, // Matrix Channel Plugin // @openclaw/matrix is bundled with OpenClaw — no install step needed. - // Plugin key is "matrix" (unscoped). enabled: "false" → channel inactive. + // Plugin key is "matrix" (unscoped). Always loaded; channel activation + // is controlled by channels.matrix.enabled, not this field. "matrix": { - "enabled": "${MATRIX_ENABLED}" + "enabled": true } } }, From bc48fb5de4870f0a2ece91d53229f241d8a0db2b Mon Sep 17 00:00:00 2001 From: Nim G Date: Sun, 8 Mar 2026 14:37:54 -0300 Subject: [PATCH 14/17] feat(telegram): add telegram.enabled toggle mirroring Matrix pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate Telegram channel on stack.yml telegram.enabled (default: true). When disabled, the channel block is stripped from the deployed openclaw.json so it doesn't appear in the Control UI. Same mechanism as matrix.enabled — resolve-config-vars strips disabled channels after resolving env vars. --- build/pre-deploy.mjs | 20 +++++------ deploy/openclaw-stack/resolve-config-vars.mjs | 17 +++++++++ docker-compose.yml.hbs | 3 ++ docs/TELEGRAM.md | 35 ++++++++++++++----- openclaw/default/openclaw.jsonc | 2 +- playbooks/00-fresh-deploy-setup.md | 4 +-- playbooks/00-onboarding.md | 31 +++++++++++++--- playbooks/07-verification.md | 6 ++-- playbooks/08b-pair-devices.md | 6 ++-- playbooks/maintenance.md | 6 ++-- stack.yml.example | 1 + 11 files changed, 98 insertions(+), 33 deletions(-) diff --git a/build/pre-deploy.mjs b/build/pre-deploy.mjs index 631c60e..173e5f9 100644 --- a/build/pre-deploy.mjs +++ b/build/pre-deploy.mjs @@ -294,8 +294,8 @@ function validateClaw(name, claw) { } const telegram = claw.telegram; - if (!telegram?.bot_token) { - warn(`Claw '${name}' has no telegram.bot_token — Telegram will be disabled`); + if (telegram?.enabled && !telegram?.bot_token) { + warn(`Claw '${name}' has telegram.enabled: true but no bot_token — Telegram may not work without a token (env var fallback allowed)`); } const matrix = claw.matrix; @@ -366,8 +366,9 @@ function computeDerivedValues(claws, stack, host, previousDeploy) { claw.llemtry_url = logUrl ? logUrl + "/llemtry" : ""; claw.enable_events_logging = stack.logging?.events || false; claw.enable_llemtry_logging = stack.logging?.llemtry || false; - // matrix_enabled is a flat boolean for Handlebars — avoids empty-string output - // when matrix: is absent from stack.yml ({{this.matrix.enabled}} would be empty). + // telegram_enabled / matrix_enabled are flat booleans for Handlebars — avoids + // empty-string output when the section is absent from stack.yml. + claw.telegram_enabled = claw.telegram?.enabled === true; claw.matrix_enabled = claw.matrix?.enabled === true; } @@ -653,7 +654,6 @@ async function main() { LOG_WORKER_TOKEN: "log_worker_token", EVENTS_URL: "events_url", LLEMTRY_URL: "llemtry_url", - ADMIN_TELEGRAM_ID: "telegram.allow_from", }; // Check against first claw (these vars are stack-wide, same for all claws) const firstClaw = Object.values(claws)[0]; @@ -663,12 +663,12 @@ async function main() { return !val && val !== 0 && val !== false; }) .map(([envVar]) => envVar); - // Matrix vars are per-claw: only emitted in docker-compose.yml when matrix.enabled=true. - // Always pre-resolve them regardless of any claw's config, so openclaw.jsonc ${MATRIX_*} - // substitution succeeds on claws where matrix is disabled and these env vars are absent + // Channel vars are per-claw: only emitted in docker-compose.yml when the channel is enabled. + // Always pre-resolve them regardless of any claw's config, so openclaw.jsonc ${VAR} + // substitution succeeds on claws where the channel is disabled and these env vars are absent // from the container environment. Checking only the first claw would break multi-claw - // stacks where claws have heterogeneous Matrix config. - const alwaysResolveVars = ["MATRIX_HOMESERVER", "MATRIX_ACCESS_TOKEN"]; + // stacks where claws have heterogeneous channel config. + const alwaysResolveVars = ["ADMIN_TELEGRAM_ID", "MATRIX_HOMESERVER", "MATRIX_ACCESS_TOKEN"]; writeFileSync(join(DEPLOY_DIR, "openclaw-stack", "empty-env-vars"), [...emptyVars, ...alwaysResolveVars].join("\n") + "\n"); // 7d-post. Resolve {{INSTALL_DIR}} in host/ files (cron configs, logrotate) diff --git a/deploy/openclaw-stack/resolve-config-vars.mjs b/deploy/openclaw-stack/resolve-config-vars.mjs index 7563ae4..5d1e6c1 100644 --- a/deploy/openclaw-stack/resolve-config-vars.mjs +++ b/deploy/openclaw-stack/resolve-config-vars.mjs @@ -12,6 +12,7 @@ import { readFileSync, existsSync } from "fs"; import { join, dirname } from "path"; import yaml from "js-yaml"; +import { parse as parseJsonc } from "jsonc-parser"; const [configFile, clawName] = process.argv.slice(2); if (!configFile || !clawName) { @@ -76,4 +77,20 @@ content = content.replace(/"(\$\{([^}]+)\})"/g, (_match, _fullRef, expr) => { // Resolve remaining ${VAR} refs (inside longer strings, unquoted positions) content = content.replace(/\$\{([^}]+)\}/g, (_match, expr) => resolveExpr(expr)); +// Strip disabled channel blocks from the resolved config. +// Removing the block entirely prevents the channel from appearing in the Control UI +// (same as unconfigured channels like WhatsApp, iMessage, etc.). +// The local .jsonc source of truth retains the full config with comments. +const stripTelegram = env.TELEGRAM_ENABLED === "false"; +const stripMatrix = env.MATRIX_ENABLED === "false"; + +if (stripTelegram || stripMatrix) { + const config = parseJsonc(content, [], { allowTrailingComma: true }); + if (config?.channels) { + if (stripTelegram) delete config.channels.telegram; + if (stripMatrix) delete config.channels.matrix; + } + content = JSON.stringify(config, null, 2) + "\n"; +} + process.stdout.write(content); diff --git a/docker-compose.yml.hbs b/docker-compose.yml.hbs index 990ce9b..7a94481 100644 --- a/docker-compose.yml.hbs +++ b/docker-compose.yml.hbs @@ -59,8 +59,11 @@ services: - OPENAI_BASE_URL={{this.openai_base_url}} - OPENAI_CODEX_BASE_URL={{this.openai_codex_base_url}} # ── Telegram ── + - TELEGRAM_ENABLED={{this.telegram_enabled}} +{{#if this.telegram_enabled}} - TELEGRAM_BOT_TOKEN={{this.telegram.bot_token}} - ADMIN_TELEGRAM_ID={{this.telegram.allow_from}} +{{/if}} # ── Matrix ── # MATRIX_ENABLED is always emitted so openclaw.jsonc ${MATRIX_ENABLED} resolves cleanly. # Sensitive vars (homeserver, token) are only emitted when Matrix is enabled. diff --git a/docs/TELEGRAM.md b/docs/TELEGRAM.md index 0c601ee..bfcf8c7 100644 --- a/docs/TELEGRAM.md +++ b/docs/TELEGRAM.md @@ -2,10 +2,10 @@ OpenClaw uses Telegram in two ways: -1. **OpenClaw channel** — chat with your AI agent via a Telegram bot (required) +1. **OpenClaw channel** — chat with your AI agent via a Telegram bot (optional per-claw) 2. **Host alerter** — VPS health alerts sent to Telegram (optional) -Both can use the same bot, or you can create separate bots. +Both are optional. If you don't use Telegram, set `telegram.enabled: false` in `stack.yml` (under `defaults` or per-claw) and skip this guide. Both can use the same bot, or you can create separate bots. ## 1. Create a Telegram Bot @@ -21,16 +21,35 @@ Send any message to [@userinfobot](https://t.me/userinfobot) — it replies with ## 3. Configure -Add both values to `stack.yml` under your claw config, and the bot token to `.env`: +Telegram is controlled by the `telegram.enabled` toggle in `stack.yml`. It defaults to `true` in `stack.yml.example`, so existing deployments continue working. To disable Telegram for a claw (or globally via `defaults`), set `enabled: false`: ```yaml -# stack.yml — under claws. -telegram_bot_token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz" -your_telegram_id: "123456789" +# stack.yml — defaults (applies to all claws unless overridden) +defaults: + telegram: + enabled: true # false to disable Telegram for all claws + allow_from: ${ADMIN_TELEGRAM_ID} + +# stack.yml — per-claw +claws: + personal-claw: + telegram: + bot_token: ${PERSONAL_CLAW_TELEGRAM_BOT_TOKEN} + # telegram: + # enabled: false # override default to disable for this claw only ``` -- `YOUR_TELEGRAM_ID` gates elevated mode — only this Telegram user can activate `/elevated` commands -- `OPENCLAW_TELEGRAM_BOT_TOKEN` connects the gateway to Telegram as a messaging channel +Add the bot token to `.env`: + +```env +PERSONAL_CLAW_TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz +ADMIN_TELEGRAM_ID=123456789 +``` + +- `ADMIN_TELEGRAM_ID` gates elevated mode — only this Telegram user can activate `/elevated` commands +- The per-claw bot token connects the gateway to Telegram as a messaging channel + +When `telegram.enabled: false`, the Telegram channel block is stripped from the deployed `openclaw.json` entirely — it won't appear in the Control UI. After deployment, the gateway connects to Telegram automatically. Message your bot to start chatting — the gateway may prompt you to approve the device via `openclaw devices approve` (same flow as browser pairing). diff --git a/openclaw/default/openclaw.jsonc b/openclaw/default/openclaw.jsonc index dcd946f..74ba8ff 100644 --- a/openclaw/default/openclaw.jsonc +++ b/openclaw/default/openclaw.jsonc @@ -58,7 +58,7 @@ }, "channels": { "telegram": { - "enabled": true, + "enabled": "${TELEGRAM_ENABLED}", "dmPolicy": "pairing", // Require device pairing for DMs "groupPolicy": "allowlist", // Only respond in explicitly allowed groups "streamMode": "partial" // Stream partial responses to Telegram diff --git a/playbooks/00-fresh-deploy-setup.md b/playbooks/00-fresh-deploy-setup.md index 3809fee..8f2bddd 100644 --- a/playbooks/00-fresh-deploy-setup.md +++ b/playbooks/00-fresh-deploy-setup.md @@ -61,8 +61,8 @@ grep -A1 '^claws:' stack.yml | tail -n +2 | grep '^\s\+[a-z]' | sed 's/://;s/^\s 2. **`VPS_IP`** — Must not be `EMPTY` or contain `<`. 3. **`CF_TUNNEL_TOKEN`** or **`CF_API_TOKEN`** — At least one must show `SET`. If both missing: "Set `CLOUDFLARE_TUNNEL_TOKEN` (manual — create tunnel in CF Dashboard) or `CLOUDFLARE_API_TOKEN` (automated). See [`docs/CLOUDFLARE-TUNNEL.md`](../docs/CLOUDFLARE-TUNNEL.md)." 4. **`domain`** — The `stack.yml` domain line must not contain angle brackets (e.g., ``). `${VAR}` references are OK — verify the referenced `.env` variable (e.g., `ROOT_DOMAIN`) is set. -5. **`ADMIN_TELEGRAM_ID`** — Must be numeric. If empty: "Send a message to @userinfobot on Telegram to get your numeric user ID." -6. **Bot tokens** — Each claw name needs a matching `_TELEGRAM_BOT_TOKEN` line in `.env` (uppercased, hyphens→underscores). If missing: "Create a Telegram bot via @BotFather and paste the token. See `docs/TELEGRAM.md`." +5. **`ADMIN_TELEGRAM_ID`** — Required if `telegram.enabled` is `true` for any claw (the default). Must be numeric. If empty: "Send a message to @userinfobot on Telegram to get your numeric user ID." Skip if Telegram is disabled for all claws. +6. **Bot tokens** — Each claw with `telegram.enabled: true` (the default) needs a matching `_TELEGRAM_BOT_TOKEN` line in `.env` (uppercased, hyphens→underscores). If missing: "Create a Telegram bot via @BotFather and paste the token. See `docs/TELEGRAM.md`." Skip for claws with `telegram.enabled: false`. 7. **Claws** — The `claws` section lists claw names. Single claw = standard deploy. Multiple claws: inform user each gets its own container/domain. ### If any fields are invalid or missing diff --git a/playbooks/00-onboarding.md b/playbooks/00-onboarding.md index eaee212..8a2c490 100644 --- a/playbooks/00-onboarding.md +++ b/playbooks/00-onboarding.md @@ -118,7 +118,7 @@ Ask: > Do you want **one** OpenClaw instance or **multiple**? > > - **One** (default) — A single claw named `personal-claw`. Good for most users. -> - **Multiple** — Separate claws for different contexts (e.g., personal + work). Each gets its own container, Telegram bot, and subdomain. +> - **Multiple** — Separate claws for different contexts (e.g., personal + work). Each gets its own container, subdomain, and optionally its own Telegram bot. > > You can always add more later. @@ -147,7 +147,18 @@ For each additional claw beyond `personal-claw`: 4. Add the corresponding env var to `.env`: `_TELEGRAM_BOT_TOKEN=` -### 3.2 Telegram Bot(s) +### 3.2 Telegram Channel + +Ask: + +> Do you want to use **Telegram** to chat with your claws? +> +> - **Yes** (default) — Set up a Telegram bot for each claw +> - **No** — Disable Telegram (you can still use the browser Control UI) + +#### Yes (Telegram enabled) + +`telegram.enabled` defaults to `true` in `stack.yml.example` — no config change needed. For **each claw**, walk through bot creation: @@ -166,9 +177,7 @@ Write to `.env` as the claw's bot token variable (e.g., `PERSONAL_CLAW_TELEGRAM_ > Each claw **must** have a unique bot token — sharing tokens causes polling conflicts. -### 3.3 Admin Telegram ID - -Ask: +Then ask for Admin Telegram ID: > Now we need your Telegram user ID so OpenClaw knows who you are. > @@ -181,6 +190,18 @@ Validate: positive integer. Write to `.env` as `ADMIN_TELEGRAM_ID`. +#### No (Telegram disabled) + +Set `telegram.enabled: false` in `stack.yml` under `defaults`: + +```yaml +defaults: + telegram: + enabled: false +``` + +Skip bot token and Admin Telegram ID setup. The Telegram channel won't appear in the Control UI. + --- ## § 4. Host Alerts diff --git a/playbooks/07-verification.md b/playbooks/07-verification.md index 490563a..f59b0d8 100644 --- a/playbooks/07-verification.md +++ b/playbooks/07-verification.md @@ -419,10 +419,12 @@ openclaw cron list **Expected:** Both scripts exit 0 with no errors. `health.json` and `maintenance.json` contain valid JSON with current timestamps. Workspace copies exist. Both host cron entries exist. `openclaw cron list` shows "Daily VPS Health Check" with status `ok`. -### Telegram Delivery Test +### Telegram Delivery Test (Host Alerter) + +> **Skip** if `host_alerter` is not configured in `stack.yml` or its Telegram credentials are empty. ```bash -# Test Telegram delivery (if configured) +# Test Telegram delivery (if host alerter configured) TELEGRAM_TOKEN=$(sudo grep -oP 'ENV__HOSTALERT_TELEGRAM_BOT_TOKEN=\K.+' /stack.env) TELEGRAM_CHAT=$(sudo grep -oP 'ENV__HOSTALERT_TELEGRAM_CHAT_ID=\K.+' /stack.env) diff --git a/playbooks/08b-pair-devices.md b/playbooks/08b-pair-devices.md index 0496790..e2bbcd6 100644 --- a/playbooks/08b-pair-devices.md +++ b/playbooks/08b-pair-devices.md @@ -147,11 +147,13 @@ If the device shows as approved but the browser still can't connect, ask the use ## Telegram Pairing -If the claw's Telegram bot token is configured in `.env`, the claws are already connected to Telegram. Tell the user: +> **Skip this section** if `telegram.enabled` is `false` for all claws in `stack.yml`. + +If the claw has `telegram.enabled: true` and a bot token configured in `.env`, the claws are already connected to Telegram. Tell the user: > **Telegram:** Your bot is live. Open Telegram and send a message to your bot. If the claw prompts for device approval, run `openclaw --instance devices approve ` the same way you approved the browser. Repeat for each claw if needed. -If the bot token is empty, skip this step — Telegram was not configured. +If Telegram is disabled or the bot token is empty, skip this step. --- diff --git a/playbooks/maintenance.md b/playbooks/maintenance.md index a659270..432e0c7 100644 --- a/playbooks/maintenance.md +++ b/playbooks/maintenance.md @@ -17,7 +17,7 @@ All secrets should be rotated on a regular cadence. If a token is suspected comp | Provider API keys (Anthropic, OpenAI, etc.) | AI Gateway KV (`creds:*`) — managed via `/config` UI | Per provider policy | | `EGRESS_PROXY_AUTH_TOKEN` | Local `.env` + AI Gateway Worker secret + VPS egress proxy container | 90 days | | `EGRESS_PROXY_URL` | AI Gateway Worker secret | Only if hostname changes | -| `HOSTALERT_TELEGRAM_BOT_TOKEN` | Local `.env` (deployed via `npm run pre-deploy`) | As needed | +| `HOSTALERT_TELEGRAM_BOT_TOKEN` | Local `.env` (deployed via `npm run pre-deploy`) | As needed (only if host alerter uses Telegram) | | `SANDBOX_REGISTRY_TOKEN` | Local `.env` + VPS `sandbox-registry/htpasswd` | 90 days | | SSH keys (`~/.ssh/vps1_openclaw_ed25519`) | Local machine + VPS `authorized_keys` | Annual | @@ -327,8 +327,8 @@ Hot-reloadable config changes (agents, skills, models) take effect without resta ## Adding a New Claw -1. Add a new entry under `claws` in `stack.yml` with per-claw overrides (domain, resources, Telegram bot token, etc.) -2. Add the claw's Telegram bot token to `.env` (e.g., `NEW_CLAW_TELEGRAM_BOT_TOKEN=...`) +1. Add a new entry under `claws` in `stack.yml` with per-claw overrides (domain, resources, etc.) +2. If `telegram.enabled` is `true` (the default), add the claw's Telegram bot token to `.env` (e.g., `NEW_CLAW_TELEGRAM_BOT_TOKEN=...`) 3. Deploy (builds, syncs configs + workspaces, auto-starts the new service): ```bash scripts/deploy.sh diff --git a/stack.yml.example b/stack.yml.example index 390efa1..61c6980 100644 --- a/stack.yml.example +++ b/stack.yml.example @@ -79,6 +79,7 @@ defaults: domain_path: "" dashboard_path: /dashboard telegram: + enabled: true allow_from: ${ADMIN_TELEGRAM_ID} # Matrix channel (optional — alternative or complement to Telegram) # Enable per-claw by setting matrix.enabled: true and providing an access token. From 6af086cdfb0f0b950c6720c83790001e596ee3fc Mon Sep 17 00:00:00 2001 From: Nim G Date: Mon, 9 Mar 2026 19:39:11 -0300 Subject: [PATCH 15/17] fix(entrypoint): reinstall matrix deps when bot-sdk dir is missing --- deploy/openclaw-stack/entrypoint.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/openclaw-stack/entrypoint.sh b/deploy/openclaw-stack/entrypoint.sh index a7fcdc8..e425a98 100755 --- a/deploy/openclaw-stack/entrypoint.sh +++ b/deploy/openclaw-stack/entrypoint.sh @@ -122,7 +122,8 @@ fi # package.json but they aren't installed in the Docker image. Install # them on first boot if the node_modules dir is missing. MATRIX_EXT="/app/extensions/matrix" -if [ "${MATRIX_ENABLED:-false}" = "true" ] && [ -f "$MATRIX_EXT/package.json" ] && [ ! -d "$MATRIX_EXT/node_modules" ]; then +if [ "${MATRIX_ENABLED:-false}" = "true" ] && [ -f "$MATRIX_EXT/package.json" ] && \ + { [ ! -d "$MATRIX_EXT/node_modules" ] || [ ! -d "$MATRIX_EXT/node_modules/@vector-im/matrix-bot-sdk" ]; }; then echo "[entrypoint] Installing matrix plugin dependencies..." cd "$MATRIX_EXT" if npm install --omit=dev 2>&1; then From 313a4f20c091ebe48b5b26cbadec02a625785f71 Mon Sep 17 00:00:00 2001 From: Nim G Date: Mon, 9 Mar 2026 19:45:58 -0300 Subject: [PATCH 16/17] fix(resolve-config): strip blank IDs from allowFrom on env substitution When a channel ID env var (e.g. ADMIN_TELEGRAM_ID) is unset, ${VAR} resolves to "", producing [""] in allowFrom arrays. Filter these out so optional channels don't leave phantom empty entries. --- deploy/openclaw-stack/resolve-config-vars.mjs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/deploy/openclaw-stack/resolve-config-vars.mjs b/deploy/openclaw-stack/resolve-config-vars.mjs index 5d1e6c1..0d6107f 100644 --- a/deploy/openclaw-stack/resolve-config-vars.mjs +++ b/deploy/openclaw-stack/resolve-config-vars.mjs @@ -93,4 +93,16 @@ if (stripTelegram || stripMatrix) { content = JSON.stringify(config, null, 2) + "\n"; } +// Drop blank IDs introduced by env substitution, e.g. [""] when +// ADMIN_TELEGRAM_ID is unset. The live config UI normalizes these to []. +const config = parseJsonc(content, [], { allowTrailingComma: true }); +const allowFrom = config?.tools?.elevated?.allowFrom; +if (allowFrom && typeof allowFrom === "object") { + for (const [channel, ids] of Object.entries(allowFrom)) { + if (!Array.isArray(ids)) continue; + allowFrom[channel] = ids.filter((id) => typeof id !== "string" || id.trim() !== ""); + } + content = JSON.stringify(config, null, 2) + "\n"; +} + process.stdout.write(content); From 522b475f4d553448da4cc8416951dd62018fe5e2 Mon Sep 17 00:00:00 2001 From: Nim G Date: Mon, 9 Mar 2026 20:50:26 -0300 Subject: [PATCH 17/17] fix(docs): update MATRIX.md to reflect groupPolicy: open default The default template uses groupPolicy: open (respond in any joined room when mentioned), not allowlist. Updated docs to match and explain how to switch to allowlist if needed. --- docs/MATRIX.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/MATRIX.md b/docs/MATRIX.md index 6dcee6e..acc0f4f 100644 --- a/docs/MATRIX.md +++ b/docs/MATRIX.md @@ -75,12 +75,10 @@ npm run pre-deploy By default: - DMs require pairing approval (`dm.policy: pairing`) -- Group rooms require explicit allowlisting (`groupPolicy: allowlist`) +- Group rooms respond to @mentions in any joined room (`groupPolicy: open`) - The bot auto-joins any room it's invited to (`auto_join: always`) -Room IDs look like `!abc123:matrix.org`. Find yours in Element under **Room Settings** → **Advanced** → **Internal Room ID**. - -Room allowlisting is configured directly in `openclaw.jsonc` — see **Rooms and Mention Gating** below. +To restrict to specific rooms, change `groupPolicy` to `allowlist` in `openclaw.jsonc` — see **Rooms and Mention Gating** below. --- @@ -121,7 +119,7 @@ After approval, the bot responds to your DMs normally. ## Rooms and Mention Gating -Rooms use `groupPolicy: allowlist` by default — the bot only responds in rooms explicitly listed in `matrix.groups`. Room allowlisting and mention gating are configured directly in the per-claw `openclaw.jsonc` (they are not rendered from `stack.yml`). To respond in a room: +Rooms use `groupPolicy: open` by default — the bot responds when @mentioned in any room it has joined. To restrict to specific rooms, set `groupPolicy: "allowlist"` in the per-claw `openclaw.jsonc` and add rooms under `channels.matrix.groups`. To add a room to the allowlist: 1. Invite the bot account to the room from your Matrix client 2. Edit `openclaw//openclaw.jsonc` and add the room under `channels.matrix.groups`: