diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0ccb8ff..869bda045 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - CLI: restore hidden `gateway-daemon` alias for legacy launchd configs. - Control UI: show skill install progress + per-skill results, hide install once binaries present. (#445) — thanks @pkrmf - Providers/Doctor: surface Discord privileged intent (Message Content) misconfiguration with actionable warnings. +- Providers/Doctor: warn when Telegram config expects unmentioned group messages but Bot API privacy mode is likely enabled; surface WhatsApp login/disconnect hints. ## 2026.1.8 diff --git a/docs/cli/index.md b/docs/cli/index.md index d64ec0faa..ca805f3be 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -203,6 +203,7 @@ Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage). Subcommands: - `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included). - `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes). +- Tip: `providers status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `clawdbot doctor`). - `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode. - `providers remove`: disable by default; pass `--delete` to remove config entries without prompts. - `providers login`: interactive provider login (WhatsApp Web only). diff --git a/docs/providers/telegram.md b/docs/providers/telegram.md index 547e11be3..5026eaefc 100644 --- a/docs/providers/telegram.md +++ b/docs/providers/telegram.md @@ -228,10 +228,11 @@ Outbound Telegram API calls retry on transient network/429 errors with exponenti ## Troubleshooting -**Bot doesn't respond to non-mention messages in group:** -- Check if group is in `telegram.groups` with `requireMention: false` -- Or use `"*": { "requireMention": false }` to enable for all groups -- Test with `/activation always` command (requires config change to persist) +**Bot doesn’t respond to non-mention messages in a group:** +- If you set `telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled. + - BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group) +- `clawdbot providers status` shows a warning when config expects unmentioned group messages. +- Quick test: `/activation always` (session-only; use config for persistence) **Bot not seeing group messages at all:** - If `telegram.groups` is set, the group must be listed or use `"*"` diff --git a/docs/providers/whatsapp.md b/docs/providers/whatsapp.md index 5419719ff..64b877e0b 100644 --- a/docs/providers/whatsapp.md +++ b/docs/providers/whatsapp.md @@ -188,3 +188,16 @@ Recommended for personal numbers: - Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. - Log file: `/tmp/clawdbot/clawdbot-YYYY-MM-DD.log` (configurable). - Troubleshooting guide: [`docs/troubleshooting.md`](/gateway/troubleshooting). + +## Troubleshooting (quick) + +**Not linked / QR login required** +- Symptom: `providers status` shows `linked: false` or warns “Not linked”. +- Fix: run `clawdbot providers login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices). + +**Linked but disconnected / reconnect loop** +- Symptom: `providers status` shows `running, disconnected` or warns “Linked but disconnected”. +- Fix: `clawdbot doctor` (or restart the gateway). If it persists, relink via `providers login` and inspect `clawdbot logs --follow`. + +**Bun runtime** +- WhatsApp uses Baileys; run the gateway with **Node** for WhatsApp. (See Getting Started runtime note.) diff --git a/src/commands/providers.test.ts b/src/commands/providers.test.ts index 1ade6cfc7..93c9333e4 100644 --- a/src/commands/providers.test.ts +++ b/src/commands/providers.test.ts @@ -339,4 +339,42 @@ describe("providers command", () => { expect(lines.join("\n")).toMatch(/Message Content Intent is limited/i); expect(lines.join("\n")).toMatch(/Run: clawdbot doctor/); }); + + it("surfaces Telegram privacy-mode hints when allowUnmentionedGroups is enabled", () => { + const lines = formatGatewayProvidersStatusLines({ + telegramAccounts: [ + { + accountId: "default", + enabled: true, + configured: true, + allowUnmentionedGroups: true, + }, + ], + }); + expect(lines.join("\n")).toMatch(/Warnings:/); + expect(lines.join("\n")).toMatch(/Telegram Bot API privacy mode/i); + }); + + it("surfaces WhatsApp auth/runtime hints when unlinked or disconnected", () => { + const unlinked = formatGatewayProvidersStatusLines({ + whatsappAccounts: [{ accountId: "default", enabled: true, linked: false }], + }); + expect(unlinked.join("\n")).toMatch(/WhatsApp/i); + expect(unlinked.join("\n")).toMatch(/Not linked/i); + + const disconnected = formatGatewayProvidersStatusLines({ + whatsappAccounts: [ + { + accountId: "default", + enabled: true, + linked: true, + running: true, + connected: false, + reconnectAttempts: 5, + lastError: "connection closed", + }, + ], + }); + expect(disconnected.join("\n")).toMatch(/disconnected/i); + }); }); diff --git a/src/commands/providers/status.ts b/src/commands/providers/status.ts index 7c4831c13..1b4206933 100644 --- a/src/commands/providers/status.ts +++ b/src/commands/providers/status.ts @@ -75,6 +75,9 @@ export function formatGatewayProvidersStatusLines( if (typeof account.running === "boolean") { bits.push(account.running ? "running" : "stopped"); } + if (typeof account.connected === "boolean") { + bits.push(account.connected ? "connected" : "disconnected"); + } if (typeof account.mode === "string" && account.mode.length > 0) { bits.push(`mode:${account.mode}`); } @@ -110,6 +113,9 @@ export function formatGatewayProvidersStatusLines( ) { bits.push(`intents:content=${messageContent}`); } + if (account.allowUnmentionedGroups === true) { + bits.push("groups:unmentioned"); + } if (typeof account.baseUrl === "string" && account.baseUrl) { bits.push(`url:${account.baseUrl}`); } diff --git a/src/gateway/server-methods/providers.ts b/src/gateway/server-methods/providers.ts index 3d84e77e6..062391ec5 100644 --- a/src/gateway/server-methods/providers.ts +++ b/src/gateway/server-methods/providers.ts @@ -97,6 +97,17 @@ export const providersHandlers: GatewayRequestHandlers = { ); lastProbeAt = Date.now(); } + const groups = + cfg.telegram?.accounts?.[account.accountId]?.groups ?? cfg.telegram?.groups; + const allowUnmentionedGroups = + Boolean(groups?.["*"] && (groups["*"] as { requireMention?: boolean }).requireMention === false) || + Object.entries(groups ?? {}).some( + ([key, value]) => + key !== "*" && + Boolean(value) && + typeof value === "object" && + (value as { requireMention?: boolean }).requireMention === false, + ); return { accountId: account.accountId, name: account.name, @@ -110,6 +121,7 @@ export const providersHandlers: GatewayRequestHandlers = { lastError: rt?.lastError ?? null, probe: telegramProbe, lastProbeAt, + allowUnmentionedGroups, }; }), ); diff --git a/src/infra/providers-status-issues.ts b/src/infra/providers-status-issues.ts index 8281a0bdf..45aff902f 100644 --- a/src/infra/providers-status-issues.ts +++ b/src/infra/providers-status-issues.ts @@ -1,7 +1,7 @@ export type ProviderStatusIssue = { - provider: "discord"; + provider: "discord" | "telegram" | "whatsapp"; accountId: string; - kind: "intent" | "permissions" | "config"; + kind: "intent" | "permissions" | "config" | "auth" | "runtime"; message: string; fix?: string; }; @@ -21,6 +21,23 @@ type DiscordAccountStatus = { application?: unknown; }; +type TelegramAccountStatus = { + accountId?: unknown; + enabled?: unknown; + configured?: unknown; + allowUnmentionedGroups?: unknown; +}; + +type WhatsAppAccountStatus = { + accountId?: unknown; + enabled?: unknown; + linked?: unknown; + connected?: unknown; + running?: unknown; + reconnectAttempts?: unknown; + lastError?: unknown; +}; + function asString(value: unknown): string | undefined { return typeof value === "string" && value.trim().length > 0 ? value.trim() @@ -57,34 +74,116 @@ function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummar }; } +function readTelegramAccountStatus(value: unknown): TelegramAccountStatus | null { + if (!isRecord(value)) return null; + return { + accountId: value.accountId, + enabled: value.enabled, + configured: value.configured, + allowUnmentionedGroups: value.allowUnmentionedGroups, + }; +} + +function readWhatsAppAccountStatus(value: unknown): WhatsAppAccountStatus | null { + if (!isRecord(value)) return null; + return { + accountId: value.accountId, + enabled: value.enabled, + linked: value.linked, + connected: value.connected, + running: value.running, + reconnectAttempts: value.reconnectAttempts, + lastError: value.lastError, + }; +} + export function collectProvidersStatusIssues( payload: Record, ): ProviderStatusIssue[] { const issues: ProviderStatusIssue[] = []; const discordAccountsRaw = payload.discordAccounts; - if (!Array.isArray(discordAccountsRaw)) return issues; + if (Array.isArray(discordAccountsRaw)) { + for (const entry of discordAccountsRaw) { + const account = readDiscordAccountStatus(entry); + if (!account) continue; + const accountId = asString(account.accountId) ?? "default"; + const enabled = account.enabled !== false; + const configured = account.configured === true; + if (!enabled || !configured) continue; - for (const entry of discordAccountsRaw) { - const account = readDiscordAccountStatus(entry); - if (!account) continue; - const accountId = asString(account.accountId) ?? "default"; - const enabled = account.enabled !== false; - const configured = account.configured === true; - if (!enabled || !configured) continue; + const app = readDiscordApplicationSummary(account.application); + const messageContent = app.intents?.messageContent; + if (messageContent && messageContent !== "enabled") { + issues.push({ + provider: "discord", + accountId, + kind: "intent", + message: `Message Content Intent is ${messageContent}. Bot may not see normal channel messages.`, + fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.", + }); + } + } + } - const app = readDiscordApplicationSummary(account.application); - const messageContent = app.intents?.messageContent; - if (messageContent && messageContent !== "enabled") { - issues.push({ - provider: "discord", - accountId, - kind: "intent", - message: `Message Content Intent is ${messageContent}. Bot may not see normal channel messages.`, - fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.", - }); + const telegramAccountsRaw = payload.telegramAccounts; + if (Array.isArray(telegramAccountsRaw)) { + for (const entry of telegramAccountsRaw) { + const account = readTelegramAccountStatus(entry); + if (!account) continue; + const accountId = asString(account.accountId) ?? "default"; + const enabled = account.enabled !== false; + const configured = account.configured === true; + if (!enabled || !configured) continue; + if (account.allowUnmentionedGroups === true) { + issues.push({ + provider: "telegram", + accountId, + kind: "config", + message: + "Config allows unmentioned group messages (requireMention=false). Telegram Bot API privacy mode will block most group messages unless disabled.", + fix: "In BotFather run /setprivacy → Disable for this bot (then restart the gateway).", + }); + } + } + } + + const whatsappAccountsRaw = payload.whatsappAccounts; + if (Array.isArray(whatsappAccountsRaw)) { + for (const entry of whatsappAccountsRaw) { + const account = readWhatsAppAccountStatus(entry); + if (!account) continue; + const accountId = asString(account.accountId) ?? "default"; + const enabled = account.enabled !== false; + if (!enabled) continue; + const linked = account.linked === true; + const running = account.running === true; + const connected = account.connected === true; + const reconnectAttempts = + typeof account.reconnectAttempts === "number" ? account.reconnectAttempts : null; + const lastError = asString(account.lastError); + + if (!linked) { + issues.push({ + provider: "whatsapp", + accountId, + kind: "auth", + message: "Not linked (no WhatsApp Web session).", + fix: "Run: clawdbot providers login (scan QR on the gateway host).", + }); + continue; + } + + if (running && !connected) { + issues.push({ + provider: "whatsapp", + accountId, + kind: "runtime", + message: `Linked but disconnected${reconnectAttempts != null ? ` (reconnectAttempts=${reconnectAttempts})` : ""}${lastError ? `: ${lastError}` : "."}`, + fix: "Run: clawdbot doctor (or restart the gateway). If it persists, relink via providers login and check logs.", + }); + } } } return issues; } -