feat: move group mention gating to provider groups

This commit is contained in:
Peter Steinberger
2026-01-02 22:23:00 +01:00
parent e93102b276
commit 5cf1a9535e
27 changed files with 613 additions and 50 deletions

BIN
docs/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -125,11 +125,13 @@ Example:
heartbeat: { every: "0m" }
},
whatsapp: {
allowFrom: ["+15555550123"]
allowFrom: ["+15555550123"],
groups: {
"*": { requireMention: true }
}
},
routing: {
groupChat: {
requireMention: true,
mentionPatterns: ["@clawd", "clawd"]
}
},

View File

@@ -10,7 +10,7 @@ CLAWDIS reads an optional **JSON5** config from `~/.clawdis/clawdis.json` (comme
If the file is missing, CLAWDIS uses safe-ish defaults (embedded Pi agent + per-sender sessions + workspace `~/clawd`). You usually only need a config to:
- restrict who can trigger the bot (`whatsapp.allowFrom`, `telegram.allowFrom`, etc.)
- tune group mention behavior (`routing.groupChat`)
- tune group mention behavior (`whatsapp.groups`, `telegram.groups`, `imessage.groups`, `discord.guilds`)
- customize message prefixes (`messages`)
- set the agents workspace (`agent.workspace`)
- tune the embedded agent (`agent`) and session behavior (`session`)
@@ -86,9 +86,24 @@ Allowlist of E.164 phone numbers that may trigger WhatsApp auto-replies.
}
```
### `whatsapp.groups`
Per-group mention gating for WhatsApp groups. Default group config lives at `whatsapp.groups."*"`.
```json5
{
whatsapp: {
groups: {
"*": { requireMention: true },
"123@g.us": { requireMention: false } // group JID
}
}
}
```
### `routing.groupChat`
Group messages default to **require mention** (either metadata mention or regex patterns). Applies to WhatsApp, Telegram, Discord, and iMessage group chats.
Group mention patterns + history handling shared across surfaces (WhatsApp/iMessage/Telegram/Discord).
```json5
{
@@ -100,6 +115,7 @@ Group messages default to **require mention** (either metadata mention or regex
}
}
```
Mention gating defaults live per provider (`whatsapp.groups`, `telegram.groups`, `imessage.groups`, `discord.guilds`).
### `routing.queue`
@@ -153,7 +169,10 @@ Set `telegram.enabled: false` to disable automatic startup.
telegram: {
enabled: true,
botToken: "your-bot-token",
requireMention: true,
groups: {
"*": { requireMention: true },
"123456789": { requireMention: false } // group chat id
},
allowFrom: ["123456789"],
mediaMaxMb: 5,
proxy: "socks5://localhost:9050",
@@ -163,6 +182,7 @@ Set `telegram.enabled: false` to disable automatic startup.
}
}
```
Mention gating precedence (most specific wins): `telegram.groups.<chatId>.requireMention``telegram.groups."*".requireMention` → default `true`.
### `discord` (bot transport)
@@ -217,6 +237,10 @@ Clawdis spawns `imsg rpc` (JSON-RPC over stdio). No daemon or port required.
cliPath: "imsg",
dbPath: "~/Library/Messages/chat.db",
allowFrom: ["+15555550123", "user@example.com", "chat_id:123"],
groups: {
"*": { requireMention: true },
"123": { requireMention: false } // chat_id for the group
},
includeAttachments: false,
mediaMaxMb: 16,
service: "auto",
@@ -229,6 +253,7 @@ Notes:
- Requires Full Disk Access to the Messages DB.
- The first send will prompt for Messages automation permission.
- Prefer `chat_id:<id>` targets. Use `imsg chats --limit 20` to list chats.
- Group mention gating lives in `imessage.groups` (default at `imessage.groups."*"`).
### `agent.workspace`

View File

@@ -18,7 +18,7 @@ Updated: 2025-12-07
- **Proxy:** optional `telegram.proxy` uses `undici.ProxyAgent` through grammYs `client.baseFetch`.
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `telegram.webhookUrl` is set (otherwise it long-polls).
- **Sessions:** direct chats map to `main`; groups map to `telegram:group:<chatId>`; replies route back to the same surface.
- **Config knobs:** `telegram.botToken`, `requireMention`, `allowFrom`, `mediaMaxMb`, `proxy`, `webhookSecret`, `webhookUrl`.
- **Config knobs:** `telegram.botToken`, `telegram.groups`, `telegram.allowFrom`, `telegram.mediaMaxMb`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`.
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
Open questions

View File

@@ -8,7 +8,7 @@ read_when:
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
## Whats implemented (2025-12-03)
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bots E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Activation is controlled per group (command or UI), not via config.
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bots E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`whatsapp.groups`) and overridden per group via `/activation`.
- Group allowlist bypass: we still enforce `whatsapp.allowFrom` on the participant at inbox ingest, but group JIDs themselves no longer block replies.
- Per-group sessions: session keys look like `whatsapp:group:<jid>` so commands such as `/verbose on` or `/think:high` are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
- Context injection: last N (default 50) group messages are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`.
@@ -21,6 +21,11 @@ Add a `groupChat` block to `~/.clawdis/clawdis.json` so display-name pings work
```json5
{
"whatsapp": {
"groups": {
"*": { "requireMention": true }
}
},
"routing": {
"groupChat": {
"historyLimit": 50,

View File

@@ -17,13 +17,30 @@ Clawdis treats group chats consistently across surfaces: WhatsApp, Telegram, Dis
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
## Mention gating (default)
Group messages require a mention unless overridden per group.
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
```json5
{
whatsapp: {
groups: {
"*": { requireMention: true },
"123@g.us": { requireMention: false }
}
},
telegram: {
groups: {
"*": { requireMention: true },
"123456789": { requireMention: false }
}
},
imessage: {
groups: {
"*": { requireMention: true },
"123": { requireMention: false }
}
},
routing: {
groupChat: {
requireMention: true,
mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"],
historyLimit: 50
}

View File

@@ -22,7 +22,7 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
## When something fails
- `logged out` or status 409515 → relink with `clawdis logout` then `clawdis login`.
- Gateway unreachable → start it: `clawdis gateway --port 18789` (use `--force` if the port is busy).
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure mention rules match (`routing.groupChat`).
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure mention rules match (`routing.groupChat.mentionPatterns` and `whatsapp.groups`).
## Dedicated "health" command
`clawdis health --json` asks the running Gateway for its health snapshot (no direct Baileys socket from the CLI). It reports linked creds, auth age, Baileys connect result/status code, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.

View File

@@ -55,7 +55,7 @@ imsg chats --limit 20
## Group chat behavior
- Group messages set `ChatType=group`, `GroupSubject`, and `GroupMembers`.
- Group activation respects `routing.groupChat.requireMention` and `mentionPatterns`.
- Group activation respects `imessage.groups."*".requireMention` and `routing.groupChat.mentionPatterns`.
- Replies go back to the same `chat_id` (group or direct).
## Troubleshooting

View File

@@ -106,8 +106,11 @@ Example:
```json5
{
whatsapp: { allowFrom: ["+15555550123"] },
routing: { groupChat: { requireMention: true, mentionPatterns: ["@clawd"] } }
whatsapp: {
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } }
},
routing: { groupChat: { mentionPatterns: ["@clawd"] } }
}
```

View File

@@ -54,9 +54,13 @@ Only allow specific phone numbers to trigger your AI. Never use `["*"]` in produ
```json
{
"whatsapp": {
"groups": {
"*": { "requireMention": true }
}
},
"routing": {
"groupChat": {
"requireMention": true,
"mentionPatterns": ["@clawd", "@mybot"]
}
}

View File

@@ -7,7 +7,7 @@ read_when:
Updated: 2025-12-07
Status: ready for bot-mode use with grammY (long-polling by default; webhook supported when configured). Text + media send, mention-gated group replies, and optional proxy support are implemented.
Status: ready for bot-mode use with grammY (long-polling by default; webhook supported when configured). Text + media send, mention-gated group replies with per-group overrides, and optional proxy support are implemented.
## Goals
- Let you talk to Clawdis via a Telegram bot in DMs and groups.
@@ -24,7 +24,7 @@ Status: ready for bot-mode use with grammY (long-polling by default; webhook sup
- The webhook listener currently binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
- If you need a different public port/host, set `telegram.webhookUrl` to the externally reachable URL and use a reverse proxy to forward to `:8787`.
4) Direct chats: user sends the first message; all subsequent turns land in the shared `main` session (default, no extra config).
5) Groups: add the bot, disable privacy mode (or make it admin) so it can read messages; group threads stay on `telegram:group:<chatId>` and require mention/command to trigger replies.
5) Groups: add the bot, disable privacy mode (or make it admin) so it can read messages; group threads stay on `telegram:group:<chatId>` and require mention/command by default (override via `telegram.groups`).
6) Optional allowlist: use `telegram.allowFrom` for direct chats by chat id (`123456789` or `telegram:123456789`).
## Capabilities & limits (Bot API)
@@ -35,9 +35,10 @@ Status: ready for bot-mode use with grammY (long-polling by default; webhook sup
## Planned implementation details
- Library: grammY is the only client for send + gateway (fetch fallback removed); grammY throttler is enabled by default to stay under Bot API limits.
- Inbound normalization: maps Bot API updates to `MsgContext` with `Surface: "telegram"`, `ChatType: direct|group`, `SenderName`, `MediaPath`/`MediaType` when attachments arrive, `Timestamp`, and reply-to metadata (`ReplyToId`, `ReplyToBody`, `ReplyToSender`) when the user replies; reply context is appended to `Body` as a `[Replying to ...]` block; groups require @bot mention by default.
- Inbound normalization: maps Bot API updates to `MsgContext` with `Surface: "telegram"`, `ChatType: direct|group`, `SenderName`, `MediaPath`/`MediaType` when attachments arrive, `Timestamp`, and reply-to metadata (`ReplyToId`, `ReplyToBody`, `ReplyToSender`) when the user replies; reply context is appended to `Body` as a `[Replying to ...]` block; groups require @bot mention by default (override per chat in config).
- Outbound: text and media (photo/video/audio/document) with optional caption; chunked to limits. Typing cue sent best-effort.
- Config: `TELEGRAM_BOT_TOKEN` env or `telegram.botToken` required; `telegram.requireMention`, `telegram.allowFrom`, `telegram.mediaMaxMb`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`, `telegram.webhookPath` supported.
- Config: `TELEGRAM_BOT_TOKEN` env or `telegram.botToken` required; `telegram.groups`, `telegram.allowFrom`, `telegram.mediaMaxMb`, `telegram.proxy`, `telegram.webhookSecret`, `telegram.webhookUrl`, `telegram.webhookPath` supported.
- Mention gating precedence (most specific wins): `telegram.groups.<chatId>.requireMention``telegram.groups."*".requireMention` → default `true`.
Example config:
```json5
@@ -45,7 +46,10 @@ Example config:
telegram: {
enabled: true,
botToken: "123:abc",
requireMention: true,
groups: {
"*": { requireMention: true },
"123456789": { requireMention: false } // group chat id
},
allowFrom: ["123456789"], // direct chat ids allowed (or "*")
mediaMaxMb: 5,
proxy: "socks5://localhost:9050",
@@ -60,7 +64,7 @@ Example config:
## Group etiquette
- Keep privacy mode off if you expect the bot to read all messages; with privacy on, it only sees commands/mentions.
- Make the bot an admin if you need it to send in restricted groups or channels.
- Mention the bot (`@yourbot`) or use commands to trigger; well honor `group.requireMention` by default to avoid noise.
- Mention the bot (`@yourbot`) or use commands to trigger; per-group overrides live in `telegram.groups` if you want always-on behavior.
## Roadmap
- ✅ Design and defaults (this doc)

View File

@@ -29,8 +29,8 @@ cat ~/.clawdis/clawdis.json | jq '.whatsapp.allowFrom'
**Check 2:** For group chats, is mention required?
```bash
# The message must contain a pattern from mentionPatterns
cat ~/.clawdis/clawdis.json | jq '.routing.groupChat'
# The message must match mentionPatterns or explicit mentions; defaults live in whatsapp.groups
cat ~/.clawdis/clawdis.json | jq '.routing.groupChat, .whatsapp.groups'
```
**Check 3:** Check the logs

View File

@@ -113,6 +113,7 @@ WhatsApp requires a real mobile number for verification. VoIP and virtual number
## Config quick map
- `whatsapp.allowFrom` (DM allowlist).
- `whatsapp.groups` (group mention gating defaults/overrides)
- `routing.groupChat.mentionPatterns`
- `routing.groupChat.historyLimit`
- `messages.messagePrefix` (inbound prefix)