diff --git a/AGENTS.md b/AGENTS.md index 9f216346dfb..80747d5807b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -144,7 +144,7 @@ Telegraph style. Root rules only. Read scoped `AGENTS.md` before subtree work. - Docs change with behavior/API. Use docs list/read_when hints; docs links per `docs/AGENTS.md`. - Docs final answers: when doc files changed, end with the relevant full `https://docs.openclaw.ai/...` URL(s). - Changelog user-facing only; fixing an issue or landing/merging a PR needs one unless pure test/internal. -- Changelog placement: active version `### Changes`/`### Fixes`; every added entry must include at least one `Thanks @author` attribution, using credited GitHub username(s). Never add `Thanks @codex`, `Thanks @openclaw`, or `Thanks @steipete`. +- Changelog placement: active version `### Changes`/`### Fixes`; contributor-facing added entries should include at least one `Thanks @author` attribution, using credited human GitHub username(s). Never add `Thanks @codex`, `Thanks @openclaw`, `Thanks @clawsweeper`, or `Thanks @steipete`; for maintainer-owned or automation-only changes, omit the thanks instead of inventing credit. - Changelog bullets are always single-line. No wrapping/continuation across multiple lines. Long entries stay on one long line so dedupe, PR-ref, and credit-audit tooling work and so the visual style stays uniform. ## Git diff --git a/CHANGELOG.md b/CHANGELOG.md index 36729d654d4..0a4c92be801 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Docs: https://docs.openclaw.ai - Providers/OpenAI: add `extraBody`/`extra_body` passthrough for OpenAI-compatible TTS endpoints, so custom speech servers can receive fields such as `lang` in `/audio/speech` requests. Fixes #39900. Thanks @R3NK0R. - Dependencies: refresh workspace dependency pins, including TypeBox 1.1.37, AWS SDK 3.1041.0, Microsoft Teams 2.0.9, and Marked 18.0.3. Thanks @mariozechner, @aws, and @microsoft. -- Discord/channels: add reusable message-channel access groups plus Discord channel-audience DM authorization, so allowlists can reference `accessGroup:` across channel auth paths. (#75813) Thanks @clawsweeper. +- Discord/channels: add reusable message-channel access groups plus Discord channel-audience DM authorization, so allowlists can reference `accessGroup:` across channel auth paths. (#75813) ### Fixes @@ -69,7 +69,7 @@ Docs: https://docs.openclaw.ai - Discord/voice: rerun configured voice auto-join after Discord gateway RESUMED events and ignore already-destroyed stale voice connections during reconnect cleanup, so health-monitor account restarts can rejoin configured channels. Fixes #40665. Thanks @liz709. - Plugins/CLI: reuse the cold manifest registry while building plugin status and inspect reports, so large configured plugin sets no longer rediscover the bundled/plugin registry once per inspect row. Thanks @vincentkoc. - Discord/voice: lengthen the default voice join Ready wait, add configurable `voice.connectTimeoutMs`/`voice.reconnectGraceMs`, and warn before destroying unrecovered disconnected sessions so slow Discord voice handshakes and reconnects no longer fail silently. Fixes #63098; refs #39825 and #65039. Thanks @darealgege, @kzicherman, and @ayochim. -- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) Thanks @clawsweeper. +- Gateway/health: refresh cached health RPC snapshots when channel runtime state diverges, so Discord and other channel status reads no longer report stale running or connected values until the cache TTL expires. (#75423) - Gateway/sessions: keep session-store reads from running stale prune and entry-count cap maintenance during startup, so oversized stores no longer block chat history readiness after updates while writes and `sessions cleanup --enforce` still preserve the cleanup safeguards. Fixes #70050. Thanks @tangda18. - Security/audit: keep plain `security audit` on the cold config/filesystem path and reserve plugin runtime security collectors for `--deep`, so large plugin installs cannot execute every plugin runtime during routine audits. Thanks @vincentkoc. - Discord/voice: merge configured media-understanding providers such as Deepgram into partial active provider registries, so follow-up voice turns keep transcribing after another media plugin is already active. Fixes #65687. Thanks @OneMintJulep. @@ -266,7 +266,7 @@ Docs: https://docs.openclaw.ai - Gateway/models: serve the last successful model catalog while stale reloads refresh in the background, so Gateway control-plane and OpenAI-compatible requests no longer block behind model-provider rediscovery after model config changes. Refs #74135, #74630, and #74633. Thanks @DerFlash, @moltar-bot, and @Saboor711. - CLI/status: resolve read-only channel setup runtime fallback from the packaged OpenClaw dist root, so `status --all`, `status --deep`, channel, and doctor paths do not crash when an external channel plugin needs setup metadata. Fixes #74693. Thanks @giangthb. - SDK/events: keep per-run SDK event streams from surfacing duplicate raw chat projection frames, while normalizing chat-only projection frames and preserving raw access through `rawEvents`. Refs #74704. Thanks @BunsDev. -- SDK: report Gateway terminal `agent.wait` timeout snapshots with lifecycle metadata as `timed_out` while keeping bare wait deadlines non-terminal. Thanks @clawsweeper. +- SDK: report Gateway terminal `agent.wait` timeout snapshots with lifecycle metadata as `timed_out` while keeping bare wait deadlines non-terminal. - Google Meet: block managed Chrome intro/test speech until browser health proves the participant is in-call, and expose `speechReady` diagnostics so login, admission, permission, and audio-bridge blockers no longer look like successful speech. Refs #72478. Thanks @DougButdorf. - Slack/commands: keep native command argument menus on select controls for encoded choice values up to Slack's option limit and truncate fallback button labels to Slack's button-text limit, so long valid choices no longer render invalid Slack blocks. Thanks @slackapi. - Agents/Codex: flush accepted debounced steering messages before normal app-server turn cleanup, so inbound follow-ups acknowledged as queued are not dropped when the turn completes before the debounce fires. Thanks @vincentkoc. diff --git a/docs/channels/access-groups.md b/docs/channels/access-groups.md new file mode 100644 index 00000000000..827d0217a86 --- /dev/null +++ b/docs/channels/access-groups.md @@ -0,0 +1,182 @@ +--- +summary: "Reusable sender allowlists for message channels" +read_when: + - Configuring the same allowlist across multiple message channels + - Sharing DM and group sender access rules + - Reviewing message-channel access control +title: "Access groups" +--- + +Access groups are named sender lists you define once and reference from channel allowlists with `accessGroup:`. + +Use them when the same people should be allowed across several message channels, or when one trusted set should apply to both DMs and group sender authorization. + +Access groups do not grant access by themselves. A group only matters when an allowlist field references it. + +## Static message sender groups + +Static sender groups use `type: "message.senders"`. + +```json5 +{ + accessGroups: { + operators: { + type: "message.senders", + members: { + "*": ["global-owner-id"], + discord: ["discord:123456789012345678"], + telegram: ["987654321"], + whatsapp: ["+15551234567"], + }, + }, + }, +} +``` + +Member lists are keyed by message-channel id: + +| Key | Meaning | +| ---------- | ----------------------------------------------------------------------- | +| `"*"` | Shared entries checked for every message channel that references group. | +| `discord` | Entries checked only for Discord allowlist matching. | +| `telegram` | Entries checked only for Telegram allowlist matching. | +| `whatsapp` | Entries checked only for WhatsApp allowlist matching. | + +Entries are matched with the destination channel's normal `allowFrom` rules. OpenClaw does not translate sender ids between channels. If Alice has a Telegram id and a Discord id, list both ids under the appropriate keys. + +## Reference groups from allowlists + +Reference a group with `accessGroup:` anywhere the message channel path supports sender allowlists. + +DM allowlist example: + +```json5 +{ + accessGroups: { + operators: { + type: "message.senders", + members: { + discord: ["discord:123456789012345678"], + telegram: ["987654321"], + }, + }, + }, + channels: { + discord: { + dmPolicy: "allowlist", + allowFrom: ["accessGroup:operators"], + }, + telegram: { + dmPolicy: "allowlist", + allowFrom: ["accessGroup:operators"], + }, + }, +} +``` + +Group sender allowlist example: + +```json5 +{ + accessGroups: { + oncall: { + type: "message.senders", + members: { + whatsapp: ["+15551234567"], + googlechat: ["users/1234567890"], + }, + }, + }, + channels: { + whatsapp: { + groupPolicy: "allowlist", + groupAllowFrom: ["accessGroup:oncall"], + }, + googlechat: { + spaces: { + "spaces/AAA": { + users: ["accessGroup:oncall"], + }, + }, + }, + }, +} +``` + +You can mix groups and direct entries: + +```json5 +{ + channels: { + discord: { + dmPolicy: "allowlist", + allowFrom: ["accessGroup:operators", "discord:123456789012345678"], + }, + }, +} +``` + +## Supported message-channel paths + +Access groups are available in shared message-channel authorization paths, including: + +- DM sender allowlists such as `channels..allowFrom` +- group sender allowlists such as `channels..groupAllowFrom` +- channel-specific per-room sender allowlists that use the same sender matching rules +- command authorization paths that reuse message-channel sender allowlists + +Channel support depends on whether that channel is wired through the shared OpenClaw sender-authorization helpers. Current bundled support includes Discord, Google Chat, Nostr, WhatsApp, Zalo, and Zalo Personal. Static `message.senders` groups are designed to be channel-agnostic, so new message channels should support them by using the shared plugin SDK helpers instead of custom allowlist expansion. + +## Discord channel audiences + +Discord also supports a dynamic access group type: + +```json5 +{ + accessGroups: { + maintainers: { + type: "discord.channelAudience", + guildId: "1456350064065904867", + channelId: "1456744319972282449", + membership: "canViewChannel", + }, + }, + channels: { + discord: { + dmPolicy: "allowlist", + allowFrom: ["accessGroup:maintainers"], + }, + }, +} +``` + +`discord.channelAudience` means "allow Discord DM senders who can currently view this guild channel." OpenClaw resolves the sender through Discord at authorization time and applies Discord `ViewChannel` permission rules. + +Use this when a Discord channel is already the source of truth for a team, such as `#maintainers` or `#on-call`. + +Requirements and failure behavior: + +- The bot needs access to the guild and channel. +- The bot needs the Discord Developer Portal **Server Members Intent**. +- The access group fails closed when Discord returns `Missing Access`, the sender cannot be resolved as a guild member, or the channel belongs to another guild. + +More Discord-specific examples: [Discord access control](/channels/discord#access-control-and-routing) + +## Security notes + +- Access groups are allowlist aliases, not roles. They do not create owners, approve pairing requests, or grant tool permissions by themselves. +- `dmPolicy: "open"` still requires `"*"` in the effective DM allowlist. Referencing an access group is not the same as public access. +- Missing group names fail closed. If `allowFrom` contains `accessGroup:operators` and `accessGroups.operators` is absent, that entry authorizes nobody. +- Keep channel ids stable. Prefer numeric/user ids over display names when the channel supports both. + +## Troubleshooting + +If a sender should match but is blocked: + +1. Confirm the allowlist field contains the exact `accessGroup:` reference. +2. Confirm `accessGroups..type` is correct. +3. Confirm the sender id is listed under the matching channel key, or under `"*"`. +4. Confirm the entry uses that channel's normal allowlist syntax. +5. For Discord channel audiences, confirm the bot can see the guild channel and has Server Members Intent enabled. + +Run `openclaw doctor` after editing access-control config. It catches many invalid allowlist and policy combinations before runtime. diff --git a/docs/channels/discord.md b/docs/channels/discord.md index 8a74aa83f0d..9e2c58ba098 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -452,7 +452,7 @@ Example: Discord DMs can use dynamic `accessGroup:` entries in `channels.discord.allowFrom`. - Access group names are shared across message channels. Use `type: "message.senders"` for a static group whose members are expressed in each channel's normal `allowFrom` syntax, or `type: "discord.channelAudience"` when a Discord channel's current `ViewChannel` audience should define membership dynamically. + Access group names are shared across message channels. Use `type: "message.senders"` for a static group whose members are expressed in each channel's normal `allowFrom` syntax, or `type: "discord.channelAudience"` when a Discord channel's current `ViewChannel` audience should define membership dynamically. Shared access-group behavior is documented here: [Access groups](/channels/access-groups). ```json5 { diff --git a/docs/channels/groups.md b/docs/channels/groups.md index 2e65f573f04..80215412114 100644 --- a/docs/channels/groups.md +++ b/docs/channels/groups.md @@ -115,6 +115,9 @@ If you want... | Disable all group replies | `groupPolicy: "disabled"` | | Only specific groups | `groups: { "": { ... } }` (no `"*"` key) | | Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` | +| Reuse one trusted sender set across channels | `groupAllowFrom: ["accessGroup:operators"]` | + +For reusable sender allowlists, see [Access groups](/channels/access-groups). ## Session keys diff --git a/docs/channels/pairing.md b/docs/channels/pairing.md index 76b2eb0bdee..50284c72a2d 100644 --- a/docs/channels/pairing.md +++ b/docs/channels/pairing.md @@ -49,7 +49,11 @@ Supported channels: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage` ### Reusable sender groups -Use top-level `accessGroups` when the same trusted sender set should apply to multiple message channels or to both DM and group allowlists. Static sender groups use `type: "message.senders"` and list members in each channel's normal `allowFrom` syntax. +Use top-level `accessGroups` when the same trusted sender set should apply to +multiple message channels or to both DM and group allowlists. + +Static groups use `type: "message.senders"` and are referenced with +`accessGroup:` from channel allowlists: ```json5 { @@ -57,7 +61,6 @@ Use top-level `accessGroups` when the same trusted sender set should apply to mu operators: { type: "message.senders", members: { - "*": ["global-owner-id"], discord: ["discord:123456789012345678"], telegram: ["987654321"], whatsapp: ["+15551234567"], @@ -71,7 +74,7 @@ Use top-level `accessGroups` when the same trusted sender set should apply to mu } ``` -The `"*"` member list is shared by all message channels. Channel-specific lists are checked with that channel's own sender matching rules. +Access groups are documented in detail here: [Access groups](/channels/access-groups) ### Where the state lives diff --git a/docs/docs.json b/docs/docs.json index 7d6438d5633..f625eefce5e 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1093,6 +1093,7 @@ "group": "Configuration", "pages": [ "channels/pairing", + "channels/access-groups", "channels/group-messages", "channels/groups", "channels/broadcast-groups",