From 8cab78abbc8dec493ae0fc63f9828636845eb1f8 Mon Sep 17 00:00:00 2001 From: cpojer Date: Sat, 31 Jan 2026 21:13:13 +0900 Subject: [PATCH] chore: Run `pnpm format:fix`. --- .agent/workflows/update_clawdbot.md | 26 +- .github/FUNDING.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 6 + .github/ISSUE_TEMPLATE/feature_request.md | 4 + .github/actionlint.yaml | 2 +- .pi/extensions/diff.ts | 289 +++-- .pi/extensions/files.ts | 277 ++-- .pi/extensions/prompt-url-widget.ts | 253 ++-- .pi/extensions/redraws.ts | 26 +- .pi/prompts/cl.md | 5 + .pi/prompts/is.md | 1 + .pi/prompts/pr.md | 15 +- .pre-commit-config.yaml | 6 +- AGENTS.md | 17 +- CHANGELOG.md | 78 +- CONTRIBUTING.md | 5 + README.md | 36 +- docker-compose.yml | 18 +- docs.acp.md | 13 +- docs/automation/auth-monitoring.md | 2 + docs/automation/cron-jobs.md | 59 +- docs/automation/cron-vs-heartbeat.md | 64 +- docs/automation/gmail-pubsub.md | 33 +- docs/automation/poll.md | 7 +- docs/automation/webhook.md | 13 +- docs/bedrock.md | 29 +- docs/brave-search.md | 14 +- docs/broadcast-groups.md | 52 +- docs/channels/bluebubbles.md | 86 +- docs/channels/discord.md | 141 +- docs/channels/googlechat.md | 77 +- docs/channels/grammy.md | 5 +- docs/channels/imessage.md | 105 +- docs/channels/index.md | 1 + docs/channels/line.md | 46 +- docs/channels/location.md | 7 + docs/channels/matrix.md | 56 +- docs/channels/mattermost.md | 42 +- docs/channels/msteams.md | 200 ++- docs/channels/nextcloud-talk.md | 52 +- docs/channels/nostr.md | 51 +- docs/channels/signal.md | 58 +- docs/channels/slack.md | 194 +-- docs/channels/telegram.md | 274 ++-- docs/channels/tlon.md | 52 +- docs/channels/troubleshooting.md | 3 + docs/channels/twitch.md | 126 +- docs/channels/whatsapp.md | 77 +- docs/channels/zalo.md | 71 +- docs/channels/zalouser.md | 48 +- docs/cli/acp.md | 9 +- docs/cli/agent.md | 1 + docs/cli/agents.md | 13 +- docs/cli/approvals.md | 1 + docs/cli/browser.md | 2 + docs/cli/channels.md | 3 + docs/cli/configure.md | 2 + docs/cli/cron.md | 1 + docs/cli/dashboard.md | 1 - docs/cli/directory.md | 2 + docs/cli/dns.md | 1 + docs/cli/docs.md | 1 - docs/cli/doctor.md | 2 + docs/cli/gateway.md | 11 + docs/cli/health.md | 1 + docs/cli/hooks.md | 13 + docs/cli/index.md | 110 ++ docs/cli/logs.md | 2 +- docs/cli/memory.md | 4 +- docs/cli/message.md | 10 + docs/cli/models.md | 6 + docs/cli/node.md | 9 +- docs/cli/nodes.md | 4 + docs/cli/onboard.md | 2 + docs/cli/pairing.md | 2 +- docs/cli/plugins.md | 1 + docs/cli/reset.md | 1 - docs/cli/sandbox.md | 22 +- docs/cli/security.md | 1 + docs/cli/sessions.md | 1 - docs/cli/setup.md | 2 +- docs/cli/skills.md | 1 + docs/cli/status.md | 1 + docs/cli/system.md | 4 + docs/cli/tui.md | 2 +- docs/cli/uninstall.md | 1 - docs/cli/update.md | 1 + docs/cli/voicecall.md | 2 +- docs/cli/webhooks.md | 1 + docs/concepts/agent-loop.md | 29 +- docs/concepts/agent-workspace.md | 5 +- docs/concepts/agent.md | 9 +- docs/concepts/architecture.md | 6 + docs/concepts/channel-routing.md | 15 +- docs/concepts/compaction.md | 11 + docs/concepts/context.md | 19 +- docs/concepts/group-messages.md | 37 +- docs/concepts/groups.md | 166 ++- docs/concepts/markdown-formatting.md | 9 +- docs/concepts/memory.md | 38 +- docs/concepts/messages.md | 20 +- docs/concepts/model-failover.md | 19 +- docs/concepts/model-providers.md | 60 +- docs/concepts/models.md | 25 +- docs/concepts/multi-agent.md | 80 +- docs/concepts/oauth.md | 29 +- docs/concepts/presence.md | 3 + docs/concepts/queue.md | 17 +- docs/concepts/retry.md | 20 +- docs/concepts/session-pruning.md | 45 +- docs/concepts/session-tool.md | 29 +- docs/concepts/session.md | 55 +- docs/concepts/sessions.md | 1 + docs/concepts/streaming.md | 11 + docs/concepts/system-prompt.md | 1 + docs/concepts/timezone.md | 9 +- docs/concepts/typebox.md | 77 +- docs/concepts/typing-indicators.md | 16 +- docs/concepts/usage-tracking.md | 4 + docs/date-time.md | 12 +- docs/debug/node-issue.md | 12 +- docs/debugging.md | 4 +- docs/diagnostics/flags.md | 1 + docs/docs.json | 14 +- docs/environment.md | 32 +- .../experiments/onboarding-config-protocol.md | 5 + docs/experiments/plans/cron-add-hardening.md | 5 + .../plans/group-policy-hardening.md | 3 +- docs/experiments/proposals/model-config.md | 3 + docs/experiments/research/memory.md | 21 +- docs/gateway/authentication.md | 9 +- docs/gateway/background-process.md | 20 +- docs/gateway/bonjour.md | 12 +- docs/gateway/bridge-protocol.md | 11 +- docs/gateway/cli-backends.md | 49 +- docs/gateway/configuration-examples.md | 228 ++-- docs/gateway/configuration.md | 1146 ++++++++++------- docs/gateway/discovery.md | 17 +- docs/gateway/doctor.md | 29 + docs/gateway/gateway-lock.md | 7 +- docs/gateway/health.md | 5 + docs/gateway/heartbeat.md | 56 +- docs/gateway/index.md | 35 + docs/gateway/local-models.md | 53 +- docs/gateway/multiple-gateways.md | 10 +- docs/gateway/openai-http-api.md | 21 +- docs/gateway/openresponses-http-api.md | 52 +- docs/gateway/pairing.md | 6 + docs/gateway/protocol.md | 9 +- docs/gateway/remote-gateway-readme.md | 13 +- docs/gateway/remote.md | 10 +- .../sandbox-vs-tool-policy-vs-elevated.md | 24 +- docs/gateway/sandboxing.md | 57 +- docs/gateway/security/formal-verification.md | 6 +- docs/gateway/security/index.md | 132 +- docs/gateway/tailscale.md | 14 +- docs/gateway/tools-invoke-http-api.md | 5 + docs/gateway/troubleshooting.md | 128 +- docs/help/faq.md | 340 +++-- docs/hooks.md | 58 +- docs/index.md | 17 +- docs/install/ansible.md | 2 + docs/install/bun.md | 2 +- docs/install/docker.md | 66 +- docs/install/installer.md | 6 +- docs/install/migrating.md | 1 + docs/install/node.md | 4 +- docs/install/uninstall.md | 20 +- docs/install/updating.md | 14 +- docs/logging.md | 12 +- docs/multi-agent-sandbox-tools.md | 27 +- docs/network.md | 2 + docs/nodes/audio.md | 58 +- docs/nodes/camera.md | 5 +- docs/nodes/images.md | 10 + docs/nodes/index.md | 13 + docs/nodes/location-command.md | 23 +- docs/nodes/media-understanding.md | 181 ++- docs/nodes/talk.md | 34 +- docs/nodes/voicewake.md | 3 + docs/northflank.mdx | 36 +- docs/perplexity.md | 20 +- docs/pi.md | 43 +- docs/platforms/android.md | 14 +- docs/platforms/digitalocean.md | 44 +- docs/platforms/exe-dev.md | 15 +- docs/platforms/gcp.md | 18 +- docs/platforms/hetzner.md | 18 +- docs/platforms/index.md | 2 + docs/platforms/ios.md | 10 +- docs/platforms/linux.md | 14 +- docs/platforms/mac/bundled-gateway.md | 5 + docs/platforms/mac/canvas.md | 3 + docs/platforms/mac/child-process.md | 1 + docs/platforms/mac/dev-setup.md | 12 +- docs/platforms/mac/health.md | 5 + docs/platforms/mac/icon.md | 4 + docs/platforms/mac/logging.md | 5 + docs/platforms/mac/menu-bar.md | 10 + docs/platforms/mac/peekaboo.md | 2 + docs/platforms/mac/permissions.md | 3 + docs/platforms/mac/release.md | 7 + docs/platforms/mac/remote.md | 29 +- docs/platforms/mac/signing.md | 5 +- docs/platforms/mac/skills.md | 5 + docs/platforms/mac/voice-overlay.md | 9 +- docs/platforms/mac/voicewake.md | 12 +- docs/platforms/mac/webchat.md | 1 + docs/platforms/mac/xpc.md | 9 + docs/platforms/macos-vm.md | 15 +- docs/platforms/macos.md | 23 +- docs/platforms/oracle.md | 43 +- docs/platforms/raspberry-pi.md | 49 +- docs/platforms/windows.md | 4 + docs/plugin.md | 108 +- docs/plugins/agent-tools.md | 18 +- docs/plugins/manifest.md | 5 +- docs/plugins/voice-call.md | 68 +- docs/plugins/zalouser.md | 11 +- docs/prose.md | 10 +- docs/providers/anthropic.md | 19 +- docs/providers/claude-max-api-proxy.md | 26 +- docs/providers/deepgram.md | 39 +- docs/providers/github-copilot.md | 3 +- docs/providers/glm.md | 3 +- docs/providers/index.md | 7 +- docs/providers/minimax.md | 56 +- docs/providers/models.md | 7 +- docs/providers/moonshot.md | 41 +- docs/providers/ollama.md | 33 +- docs/providers/openai.md | 5 +- docs/providers/opencode.md | 3 +- docs/providers/openrouter.md | 7 +- docs/providers/qwen.md | 1 + docs/providers/synthetic.md | 63 +- docs/providers/venice.md | 122 +- docs/providers/vercel-ai-gateway.md | 14 +- docs/providers/xiaomi.md | 13 +- docs/providers/zai.md | 3 +- docs/railway.mdx | 43 +- docs/refactor/clawnet.md | 86 +- docs/refactor/exec-host.md | 70 +- docs/refactor/outbound-session-mirroring.md | 12 +- docs/refactor/plugin-sdk.md | 32 +- docs/refactor/strict-config.md | 17 +- docs/reference/AGENTS.default.md | 21 +- docs/reference/RELEASING.md | 32 +- docs/reference/api-usage-costs.md | 20 + docs/reference/rpc.md | 5 + .../session-management-compaction.md | 39 +- docs/reference/templates/AGENTS.dev.md | 27 +- docs/reference/templates/AGENTS.md | 24 +- docs/reference/templates/BOOT.md | 1 + docs/reference/templates/BOOTSTRAP.md | 10 +- docs/reference/templates/HEARTBEAT.md | 2 + docs/reference/templates/IDENTITY.dev.md | 7 + docs/reference/templates/SOUL.dev.md | 4 +- docs/reference/templates/SOUL.md | 9 +- docs/reference/templates/TOOLS.dev.md | 5 +- docs/reference/templates/TOOLS.md | 9 +- docs/reference/templates/USER.dev.md | 3 +- docs/reference/test.md | 3 + docs/reference/transcript-hygiene.md | 11 + docs/render.mdx | 37 +- docs/scripts.md | 1 + docs/security/formal-verification.md | 6 +- docs/start/getting-started.md | 3 + docs/start/hubs.md | 1 + docs/start/lore.md | 37 +- docs/start/onboarding.md | 18 +- docs/start/openclaw.md | 43 +- docs/start/pairing.md | 9 +- docs/start/setup.md | 20 +- docs/start/showcase.md | 52 +- docs/start/wizard.md | 39 +- docs/testing.md | 22 +- docs/token-use.md | 1 + docs/tools/agent-send.md | 1 + docs/tools/browser-linux-troubleshooting.md | 23 +- docs/tools/browser-login.md | 14 +- docs/tools/browser.md | 47 +- docs/tools/chrome-extension.md | 26 +- docs/tools/clawhub.md | 8 +- docs/tools/creating-skills.md | 11 +- docs/tools/elevated.md | 7 + docs/tools/exec-approvals.md | 25 +- docs/tools/exec.md | 27 +- docs/tools/firecrawl.md | 22 +- docs/tools/index.md | 129 +- docs/tools/llm-task.md | 4 +- docs/tools/lobster.md | 6 +- docs/tools/reactions.md | 1 + docs/tools/skills-config.md | 22 +- docs/tools/skills.md | 70 +- docs/tools/slash-commands.md | 11 +- docs/tools/subagents.md | 24 +- docs/tools/thinking.md | 10 + docs/tools/web.md | 81 +- docs/tts.md | 76 +- docs/tui.md | 30 +- docs/vps.md | 1 + docs/web/control-ui.md | 19 +- docs/web/dashboard.md | 3 + docs/web/index.md | 19 +- docs/web/webchat.md | 15 +- extensions/bluebubbles/openclaw.plugin.json | 4 +- extensions/bluebubbles/package.json | 2 +- extensions/bluebubbles/src/actions.ts | 4 +- extensions/bluebubbles/src/attachments.ts | 24 +- extensions/bluebubbles/src/channel.ts | 7 +- extensions/bluebubbles/src/chat.ts | 14 +- extensions/bluebubbles/src/monitor.test.ts | 168 ++- extensions/bluebubbles/src/monitor.ts | 95 +- extensions/bluebubbles/src/probe.ts | 6 +- extensions/bluebubbles/src/reactions.ts | 9 +- extensions/bluebubbles/src/send.ts | 10 +- extensions/bluebubbles/src/targets.ts | 3 +- extensions/copilot-proxy/openclaw.plugin.json | 4 +- extensions/copilot-proxy/package.json | 2 +- extensions/diagnostics-otel/package.json | 12 +- .../diagnostics-otel/src/service.test.ts | 14 +- extensions/diagnostics-otel/src/service.ts | 6 +- extensions/discord/openclaw.plugin.json | 4 +- extensions/discord/package.json | 2 +- extensions/discord/src/channel.ts | 9 +- extensions/google-antigravity-auth/index.ts | 4 +- .../openclaw.plugin.json | 4 +- .../google-antigravity-auth/package.json | 2 +- extensions/google-gemini-cli-auth/index.ts | 4 +- extensions/google-gemini-cli-auth/oauth.ts | 34 +- .../openclaw.plugin.json | 4 +- .../google-gemini-cli-auth/package.json | 2 +- extensions/googlechat/openclaw.plugin.json | 4 +- extensions/googlechat/package.json | 20 +- extensions/googlechat/src/actions.ts | 10 +- extensions/googlechat/src/auth.ts | 3 +- extensions/googlechat/src/channel.ts | 26 +- extensions/googlechat/src/monitor.test.ts | 8 +- extensions/googlechat/src/monitor.ts | 61 +- extensions/googlechat/src/onboarding.ts | 24 +- extensions/googlechat/src/targets.test.ts | 4 +- extensions/imessage/openclaw.plugin.json | 4 +- extensions/imessage/package.json | 2 +- extensions/imessage/src/channel.ts | 5 +- extensions/line/openclaw.plugin.json | 4 +- extensions/line/package.json | 8 +- extensions/line/src/card-command.ts | 6 +- extensions/line/src/channel.logout.test.ts | 37 +- .../line/src/channel.sendPayload.test.ts | 34 +- extensions/line/src/channel.ts | 21 +- extensions/llm-task/README.md | 4 +- extensions/llm-task/package.json | 2 +- extensions/llm-task/src/llm-task-tool.test.ts | 19 +- extensions/llm-task/src/llm-task-tool.ts | 33 +- extensions/lobster/README.md | 21 +- extensions/lobster/SKILL.md | 21 +- extensions/lobster/package.json | 2 +- extensions/lobster/src/lobster-tool.ts | 6 +- extensions/matrix/CHANGELOG.md | 11 + extensions/matrix/openclaw.plugin.json | 4 +- extensions/matrix/package.json | 22 +- .../matrix/src/channel.directory.test.ts | 14 +- extensions/matrix/src/channel.ts | 35 +- .../matrix/src/matrix/actions/messages.ts | 4 +- .../matrix/src/matrix/actions/reactions.ts | 8 +- extensions/matrix/src/matrix/actions/room.ts | 11 +- .../matrix/src/matrix/actions/summary.ts | 5 +- extensions/matrix/src/matrix/client.ts | 6 +- extensions/matrix/src/matrix/client/config.ts | 10 +- .../matrix/src/matrix/client/create-client.ts | 18 +- .../matrix/src/matrix/client/logging.ts | 17 +- .../matrix/src/matrix/client/storage.ts | 9 +- extensions/matrix/src/matrix/credentials.ts | 3 +- extensions/matrix/src/matrix/deps.ts | 4 +- .../matrix/src/matrix/monitor/direct.ts | 11 +- .../matrix/src/matrix/monitor/events.ts | 3 +- .../matrix/src/matrix/monitor/handler.ts | 60 +- extensions/matrix/src/matrix/monitor/index.ts | 21 +- extensions/matrix/src/matrix/monitor/media.ts | 5 +- .../matrix/src/matrix/monitor/room-info.ts | 9 +- extensions/matrix/src/matrix/monitor/rooms.ts | 7 +- extensions/matrix/src/matrix/poll-types.ts | 7 +- .../matrix/src/matrix/send/formatting.ts | 17 +- extensions/matrix/src/matrix/send/media.ts | 10 +- .../matrix/src/matrix/send/targets.test.ts | 13 +- extensions/matrix/src/matrix/send/targets.ts | 21 +- extensions/matrix/src/onboarding.ts | 8 +- extensions/matrix/src/resolve-targets.ts | 5 +- extensions/matrix/src/tool-actions.ts | 3 +- extensions/mattermost/openclaw.plugin.json | 4 +- extensions/mattermost/package.json | 2 +- extensions/mattermost/src/channel.ts | 9 +- .../mattermost/src/mattermost/monitor.ts | 58 +- extensions/memory-core/package.json | 14 +- extensions/memory-lancedb/config.ts | 6 +- extensions/memory-lancedb/index.ts | 55 +- .../memory-lancedb/openclaw.plugin.json | 13 +- extensions/memory-lancedb/package.json | 2 +- extensions/minimax-portal-auth/index.ts | 6 +- extensions/minimax-portal-auth/oauth.ts | 13 +- .../minimax-portal-auth/openclaw.plugin.json | 4 +- extensions/minimax-portal-auth/package.json | 2 +- extensions/msteams/CHANGELOG.md | 10 + extensions/msteams/openclaw.plugin.json | 4 +- extensions/msteams/package.json | 18 +- extensions/msteams/src/attachments/graph.ts | 8 +- .../msteams/src/channel.directory.test.ts | 8 +- extensions/msteams/src/channel.ts | 10 +- .../msteams/src/conversation-store-fs.test.ts | 3 +- extensions/msteams/src/directory-live.ts | 9 +- .../msteams/src/file-consent-helpers.test.ts | 15 +- extensions/msteams/src/graph-chat.ts | 9 +- extensions/msteams/src/graph-upload.ts | 38 +- extensions/msteams/src/media-helpers.test.ts | 14 +- extensions/msteams/src/messenger.ts | 31 +- extensions/msteams/src/monitor-handler.ts | 6 +- .../src/monitor-handler/message-handler.ts | 84 +- extensions/msteams/src/monitor.ts | 6 +- extensions/msteams/src/onboarding.ts | 8 +- extensions/msteams/src/pending-uploads.ts | 4 +- extensions/msteams/src/polls.test.ts | 3 +- extensions/msteams/src/probe.ts | 12 +- extensions/msteams/src/resolve-allowlist.ts | 28 +- extensions/msteams/src/send-context.ts | 6 +- extensions/msteams/src/send.ts | 61 +- .../nextcloud-talk/openclaw.plugin.json | 4 +- extensions/nextcloud-talk/package.json | 2 +- extensions/nextcloud-talk/src/accounts.ts | 4 +- extensions/nextcloud-talk/src/channel.ts | 16 +- extensions/nextcloud-talk/src/format.ts | 22 +- extensions/nextcloud-talk/src/inbound.ts | 39 +- extensions/nextcloud-talk/src/onboarding.ts | 3 +- extensions/nextcloud-talk/src/policy.ts | 16 +- extensions/nextcloud-talk/src/room-info.ts | 4 +- extensions/nostr/CHANGELOG.md | 6 + extensions/nostr/README.md | 32 +- extensions/nostr/openclaw.plugin.json | 4 +- extensions/nostr/package.json | 12 +- extensions/nostr/src/channel.ts | 28 +- extensions/nostr/src/config-schema.ts | 2 +- extensions/nostr/src/metrics.ts | 12 +- extensions/nostr/src/nostr-bus.fuzz.test.ts | 31 +- .../nostr/src/nostr-bus.integration.test.ts | 6 +- extensions/nostr/src/nostr-bus.test.ts | 6 +- extensions/nostr/src/nostr-bus.ts | 75 +- .../nostr/src/nostr-profile-http.test.ts | 11 +- extensions/nostr/src/nostr-profile-http.ts | 10 +- extensions/nostr/src/nostr-profile-import.ts | 6 +- .../nostr/src/nostr-profile.fuzz.test.ts | 11 +- extensions/nostr/src/nostr-profile.test.ts | 10 +- extensions/nostr/src/nostr-profile.ts | 8 +- .../nostr/src/nostr-state-store.test.ts | 3 +- extensions/nostr/src/nostr-state-store.ts | 35 +- extensions/nostr/src/types.test.ts | 6 +- extensions/open-prose/package.json | 2 +- extensions/qwen-portal-auth/index.ts | 6 +- .../qwen-portal-auth/openclaw.plugin.json | 4 +- extensions/signal/openclaw.plugin.json | 4 +- extensions/signal/package.json | 2 +- extensions/slack/openclaw.plugin.json | 4 +- extensions/slack/package.json | 2 +- extensions/slack/src/channel.ts | 20 +- extensions/telegram/openclaw.plugin.json | 4 +- extensions/telegram/package.json | 2 +- extensions/telegram/src/channel.ts | 16 +- extensions/tlon/openclaw.plugin.json | 4 +- extensions/tlon/package.json | 10 +- extensions/tlon/src/channel.ts | 3 +- extensions/tlon/src/monitor/discovery.ts | 10 +- extensions/tlon/src/monitor/history.ts | 4 +- extensions/tlon/src/monitor/index.ts | 52 +- extensions/tlon/src/monitor/utils.ts | 4 +- extensions/tlon/src/onboarding.ts | 4 +- extensions/tlon/src/types.ts | 19 +- extensions/tlon/src/urbit/send.ts | 2 +- extensions/twitch/CHANGELOG.md | 2 + extensions/voice-call/CHANGELOG.md | 14 + extensions/voice-call/README.md | 22 +- extensions/voice-call/index.ts | 56 +- extensions/voice-call/openclaw.plugin.json | 64 +- extensions/voice-call/package.json | 2 +- extensions/voice-call/src/cli.ts | 189 ++- extensions/voice-call/src/config.test.ts | 4 +- extensions/voice-call/src/config.ts | 198 ++- extensions/voice-call/src/core-bridge.ts | 9 +- extensions/voice-call/src/manager.test.ts | 9 +- extensions/voice-call/src/manager.ts | 81 +- extensions/voice-call/src/manager/events.ts | 5 +- extensions/voice-call/src/manager/outbound.ts | 20 +- extensions/voice-call/src/manager/state.ts | 6 +- extensions/voice-call/src/manager/timers.ts | 5 +- extensions/voice-call/src/media-stream.ts | 28 +- extensions/voice-call/src/providers/mock.ts | 16 +- extensions/voice-call/src/providers/plivo.ts | 29 +- .../src/providers/stt-openai-realtime.ts | 8 +- extensions/voice-call/src/providers/telnyx.ts | 15 +- .../voice-call/src/providers/tts-openai.ts | 15 +- .../voice-call/src/providers/twilio.test.ts | 5 +- extensions/voice-call/src/providers/twilio.ts | 49 +- .../voice-call/src/providers/twilio/api.ts | 21 +- .../src/providers/twilio/webhook.ts | 3 +- .../voice-call/src/response-generator.ts | 23 +- extensions/voice-call/src/runtime.ts | 27 +- extensions/voice-call/src/telephony-audio.ts | 10 +- extensions/voice-call/src/telephony-tts.ts | 5 +- extensions/voice-call/src/tunnel.ts | 30 +- .../voice-call/src/webhook-security.test.ts | 21 +- extensions/voice-call/src/webhook-security.ts | 23 +- extensions/voice-call/src/webhook.ts | 70 +- extensions/whatsapp/openclaw.plugin.json | 4 +- extensions/whatsapp/package.json | 2 +- extensions/whatsapp/src/channel.ts | 15 +- extensions/zalo/CHANGELOG.md | 12 + extensions/zalo/README.md | 12 +- extensions/zalo/openclaw.plugin.json | 4 +- extensions/zalo/package.json | 10 +- extensions/zalo/src/channel.directory.test.ts | 18 +- extensions/zalo/src/channel.ts | 10 +- extensions/zalo/src/monitor.ts | 39 +- extensions/zalo/src/monitor.webhook.test.ts | 35 +- extensions/zalo/src/onboarding.ts | 33 +- extensions/zalo/src/send.ts | 26 +- extensions/zalo/src/status-issues.ts | 7 +- extensions/zalouser/CHANGELOG.md | 6 + extensions/zalouser/README.md | 8 +- extensions/zalouser/openclaw.plugin.json | 4 +- extensions/zalouser/package.json | 2 +- extensions/zalouser/src/accounts.ts | 17 +- extensions/zalouser/src/channel.ts | 52 +- extensions/zalouser/src/monitor.ts | 35 +- extensions/zalouser/src/onboarding.ts | 40 +- extensions/zalouser/src/tool.ts | 27 +- extensions/zalouser/src/types.ts | 10 +- extensions/zalouser/src/zca.ts | 10 +- fly.private.toml | 2 +- openclaw.mjs | 4 +- package.json | 38 +- packages/clawdbot/package.json | 8 +- packages/moltbot/package.json | 8 +- pnpm-workspace.yaml | 8 +- ui/src/styles/base.css | 36 +- ui/src/styles/chat/grouped.css | 11 +- ui/src/styles/chat/layout.css | 5 +- ui/src/styles/chat/tool-cards.css | 8 +- ui/src/styles/components.css | 28 +- ui/src/ui/app-channels.ts | 19 +- ui/src/ui/app-chat.ts | 9 +- ui/src/ui/app-gateway.ts | 14 +- ui/src/ui/app-lifecycle.ts | 26 +- ui/src/ui/app-render.helpers.ts | 59 +- ui/src/ui/app-render.ts | 760 +++++------ ui/src/ui/app-scroll.ts | 12 +- ui/src/ui/app-settings.ts | 39 +- ui/src/ui/app-tool-stream.ts | 3 +- ui/src/ui/app-view-state.ts | 5 +- ui/src/ui/app.ts | 57 +- ui/src/ui/assistant-identity.ts | 7 +- ui/src/ui/chat-markdown.browser.test.ts | 4 +- ui/src/ui/chat/copy-as-markdown.ts | 4 +- ui/src/ui/chat/grouped-render.ts | 56 +- ui/src/ui/chat/message-extract.ts | 8 +- ui/src/ui/chat/message-normalizer.test.ts | 14 +- ui/src/ui/chat/message-normalizer.ts | 8 +- ui/src/ui/chat/tool-cards.ts | 65 +- ui/src/ui/chat/tool-helpers.test.ts | 2 +- ui/src/ui/components/resizable-divider.ts | 12 +- ui/src/ui/config-form.browser.test.ts | 24 +- ui/src/ui/controllers/assistant-identity.ts | 5 +- ui/src/ui/controllers/chat.test.ts | 6 +- ui/src/ui/controllers/chat.ts | 15 +- ui/src/ui/controllers/config.test.ts | 10 +- ui/src/ui/controllers/config.ts | 29 +- ui/src/ui/controllers/config/form-utils.ts | 10 +- ui/src/ui/controllers/cron.ts | 9 +- ui/src/ui/controllers/debug.ts | 4 +- ui/src/ui/controllers/devices.ts | 4 +- ui/src/ui/controllers/exec-approval.ts | 5 +- ui/src/ui/controllers/exec-approvals.ts | 9 +- ui/src/ui/controllers/logs.ts | 22 +- ui/src/ui/controllers/nodes.ts | 5 +- ui/src/ui/controllers/presence.ts | 4 +- ui/src/ui/controllers/sessions.ts | 3 +- ui/src/ui/controllers/skills.ts | 16 +- ui/src/ui/focus-mode.browser.test.ts | 8 +- ui/src/ui/format.ts | 5 +- ui/src/ui/icons.ts | 248 +++- ui/src/ui/navigation.browser.test.ts | 8 +- ui/src/ui/navigation.ts | 4 +- ui/src/ui/storage.ts | 21 +- ui/src/ui/theme-transition.ts | 9 +- ui/src/ui/theme.ts | 4 +- ui/src/ui/tool-display.json | 18 +- ui/src/ui/tool-display.ts | 7 +- ui/src/ui/types.ts | 8 +- ui/src/ui/views/channels.config.ts | 48 +- ui/src/ui/views/channels.discord.ts | 16 +- ui/src/ui/views/channels.googlechat.ts | 24 +- ui/src/ui/views/channels.imessage.ts | 16 +- .../ui/views/channels.nostr-profile-form.ts | 46 +- ui/src/ui/views/channels.nostr.ts | 101 +- ui/src/ui/views/channels.signal.ts | 16 +- ui/src/ui/views/channels.slack.ts | 16 +- ui/src/ui/views/channels.telegram.ts | 34 +- ui/src/ui/views/channels.ts | 75 +- ui/src/ui/views/channels.types.ts | 24 +- ui/src/ui/views/channels.whatsapp.ts | 32 +- ui/src/ui/views/chat.ts | 117 +- ui/src/ui/views/config-form.analyze.ts | 20 +- ui/src/ui/views/config-form.node.ts | 234 ++-- ui/src/ui/views/config-form.render.ts | 337 ++++- ui/src/ui/views/config-form.ts | 5 +- ui/src/ui/views/config.browser.test.ts | 62 +- ui/src/ui/views/config.ts | 427 ++++-- ui/src/ui/views/cron.ts | 72 +- ui/src/ui/views/debug.ts | 45 +- ui/src/ui/views/exec-approval.ts | 16 +- ui/src/ui/views/instances.ts | 43 +- ui/src/ui/views/logs.ts | 51 +- ui/src/ui/views/markdown-sidebar.ts | 14 +- ui/src/ui/views/nodes.ts | 278 ++-- ui/src/ui/views/overview.ts | 51 +- ui/src/ui/views/sessions.ts | 36 +- ui/src/ui/views/skills.ts | 82 +- vitest.config.ts | 6 +- 624 files changed, 10729 insertions(+), 7514 deletions(-) diff --git a/.agent/workflows/update_clawdbot.md b/.agent/workflows/update_clawdbot.md index 692ee84e4..04a079aab 100644 --- a/.agent/workflows/update_clawdbot.md +++ b/.agent/workflows/update_clawdbot.md @@ -29,10 +29,12 @@ git log --oneline --left-right main...upstream/main | head -20 ``` This shows: + - `<` = your local commits (ahead) - `>` = upstream commits you're missing (behind) **Decision point:** + - Few local commits, many upstream → **Rebase** (cleaner history) - Many local commits or shared branch → **Merge** (preserves history) @@ -70,12 +72,12 @@ git rebase --abort ### Common Conflict Patterns -| File | Resolution | -|------|------------| -| `package.json` | Take upstream deps, keep local scripts if needed | -| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` | -| `*.patch` files | Usually take upstream version | -| Source files | Merge logic carefully, prefer upstream structure | +| File | Resolution | +| ---------------- | ------------------------------------------------ | +| `package.json` | Take upstream deps, keep local scripts if needed | +| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` | +| `*.patch` files | Usually take upstream version | +| Source files | Merge logic carefully, prefer upstream structure | --- @@ -88,6 +90,7 @@ git merge upstream/main --no-edit ``` Resolve conflicts same as rebase, then: + ```bash git add git commit @@ -170,6 +173,7 @@ pnpm clawdbot agent --message "Verification: macOS app rebuild successful - agen Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging: ### Analyze-Mode Investigation + ```bash # Gather context with parallel agents morph-mcp_warpgrep_codebase_search search_string="Find deprecated FileManager.default and Thread.isMainThread usages in Swift files" repo_path="/Volumes/Main SSD/Developer/clawdis" @@ -179,6 +183,7 @@ morph-mcp_warpgrep_codebase_search search_string="Locate Peekaboo submodule and ### Common Swift 6.2 Fixes **FileManager.default Deprecation:** + ```bash # Search for deprecated usage grep -r "FileManager\.default" src/ apps/ --include="*.swift" @@ -189,6 +194,7 @@ grep -r "FileManager\.default" src/ apps/ --include="*.swift" ``` **Thread.isMainThread Deprecation:** + ```bash # Search for deprecated usage grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift" @@ -199,6 +205,7 @@ grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift" ``` ### Peekaboo Submodule Fixes + ```bash # Check Peekaboo for concurrency issues cd src/canvas-host/a2ui @@ -210,6 +217,7 @@ pnpm canvas:a2ui:bundle ``` ### macOS App Concurrency Fixes + ```bash # Check macOS app for issues grep -r "Thread\.isMainThread\|FileManager\.default" apps/macos/ --include="*.swift" @@ -220,7 +228,9 @@ cd apps/macos && rm -rf .build .swiftpm ``` ### Model Configuration Updates + If upstream introduced new model configurations: + ```bash # Check for OpenRouter API key requirements grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js" @@ -265,6 +275,7 @@ Common issue: `fetch.preconnect` type mismatch. Fix by using `FetchLike` type in ### macOS App Crashes on Launch Usually resource bundle mismatch. Full rebuild required: + ```bash cd apps/macos && rm -rf .build .swiftpm ./scripts/restart-mac.sh @@ -285,12 +296,14 @@ pnpm install 2>&1 | grep -i patch **Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread` **Search-Mode Investigation:** + ```bash # Exhaustive search for deprecated APIs morph-mcp_warpgrep_codebase_search search_string="Find all Swift files using deprecated FileManager.default or Thread.isMainThread" repo_path="/Volumes/Main SSD/Developer/clawdis" ``` **Quick Fix Commands:** + ```bash # Find all affected files find . -name "*.swift" -exec grep -l "FileManager\.default\|Thread\.isMainThread" {} \; @@ -303,6 +316,7 @@ grep -rn "Thread\.isMainThread" --include="*.swift" . ``` **Rebuild After Fixes:** + ```bash # Clean all build artifacts rm -rf apps/macos/.build apps/macos/.swiftpm diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f6fca8c5e..082086ea0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1 @@ -custom: ['https://github.com/sponsors/steipete'] +custom: ["https://github.com/sponsors/steipete"] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 46ee3da04..82b560c47 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,23 +6,29 @@ labels: bug --- ## Summary + What went wrong? ## Steps to reproduce + 1. 2. 3. ## Expected behavior + What did you expect to happen? ## Actual behavior + What actually happened? ## Environment + - Clawdbot version: - OS: - Install method (pnpm/npx/docker/etc): ## Logs or screenshots + Paste relevant logs or add screenshots (redact secrets). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 742bf184e..7b33641dc 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -6,13 +6,17 @@ labels: enhancement --- ## Summary + Describe the problem you are trying to solve or the opportunity you see. ## Proposed solution + What would you like Clawdbot to do? ## Alternatives considered + Any other approaches you have considered? ## Additional context + Links, screenshots, or related issues. diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index bcfdefcdd..e660d2a97 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -12,6 +12,6 @@ paths: .github/workflows/**/*.yml: ignore: # Ignore shellcheck warnings (we run shellcheck separately) - - 'shellcheck reported issue.+' + - "shellcheck reported issue.+" # Ignore intentional if: false for disabled jobs - 'constant expression "false" in condition' diff --git a/.pi/extensions/diff.ts b/.pi/extensions/diff.ts index 599ef15d6..5f673d57f 100644 --- a/.pi/extensions/diff.ts +++ b/.pi/extensions/diff.ts @@ -7,168 +7,179 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; +import { + Container, + Key, + matchesKey, + type SelectItem, + SelectList, + Text, +} from "@mariozechner/pi-tui"; interface FileInfo { - status: string; - statusLabel: string; - file: string; + status: string; + statusLabel: string; + file: string; } export default function (pi: ExtensionAPI) { - pi.registerCommand("diff", { - description: "Show git changes and open in VS Code diff view", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("No UI available", "error"); - return; - } + pi.registerCommand("diff", { + description: "Show git changes and open in VS Code diff view", + handler: async (_args, ctx) => { + if (!ctx.hasUI) { + ctx.ui.notify("No UI available", "error"); + return; + } - // Get changed files from git status - const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd }); + // Get changed files from git status + const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd }); - if (result.code !== 0) { - ctx.ui.notify(`git status failed: ${result.stderr}`, "error"); - return; - } + if (result.code !== 0) { + ctx.ui.notify(`git status failed: ${result.stderr}`, "error"); + return; + } - if (!result.stdout || !result.stdout.trim()) { - ctx.ui.notify("No changes in working tree", "info"); - return; - } + if (!result.stdout || !result.stdout.trim()) { + ctx.ui.notify("No changes in working tree", "info"); + return; + } - // Parse git status output - // Format: XY filename (where XY is two-letter status, then space, then filename) - const lines = result.stdout.split("\n"); - const files: FileInfo[] = []; + // Parse git status output + // Format: XY filename (where XY is two-letter status, then space, then filename) + const lines = result.stdout.split("\n"); + const files: FileInfo[] = []; - for (const line of lines) { - if (line.length < 4) continue; // Need at least "XY f" + for (const line of lines) { + if (line.length < 4) continue; // Need at least "XY f" - const status = line.slice(0, 2); - const file = line.slice(2).trimStart(); + const status = line.slice(0, 2); + const file = line.slice(2).trimStart(); - // Translate status codes to short labels - let statusLabel: string; - if (status.includes("M")) statusLabel = "M"; - else if (status.includes("A")) statusLabel = "A"; - else if (status.includes("D")) statusLabel = "D"; - else if (status.includes("?")) statusLabel = "?"; - else if (status.includes("R")) statusLabel = "R"; - else if (status.includes("C")) statusLabel = "C"; - else statusLabel = status.trim() || "~"; + // Translate status codes to short labels + let statusLabel: string; + if (status.includes("M")) statusLabel = "M"; + else if (status.includes("A")) statusLabel = "A"; + else if (status.includes("D")) statusLabel = "D"; + else if (status.includes("?")) statusLabel = "?"; + else if (status.includes("R")) statusLabel = "R"; + else if (status.includes("C")) statusLabel = "C"; + else statusLabel = status.trim() || "~"; - files.push({ status: statusLabel, statusLabel, file }); - } + files.push({ status: statusLabel, statusLabel, file }); + } - if (files.length === 0) { - ctx.ui.notify("No changes found", "info"); - return; - } + if (files.length === 0) { + ctx.ui.notify("No changes found", "info"); + return; + } - const openSelected = async (fileInfo: FileInfo): Promise => { - try { - // Open in VS Code diff view. - // For untracked files, git difftool won't work, so fall back to just opening the file. - if (fileInfo.status === "?") { - await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); - return; - } + const openSelected = async (fileInfo: FileInfo): Promise => { + try { + // Open in VS Code diff view. + // For untracked files, git difftool won't work, so fall back to just opening the file. + if (fileInfo.status === "?") { + await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); + return; + } - const diffResult = await pi.exec("git", ["difftool", "-y", "--tool=vscode", fileInfo.file], { - cwd: ctx.cwd, - }); - if (diffResult.code !== 0) { - await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error"); - } - }; + const diffResult = await pi.exec( + "git", + ["difftool", "-y", "--tool=vscode", fileInfo.file], + { + cwd: ctx.cwd, + }, + ); + if (diffResult.code !== 0) { + await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd }); + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error"); + } + }; - // Show file picker with SelectList - await ctx.ui.custom((tui, theme, _kb, done) => { - const container = new Container(); + // Show file picker with SelectList + await ctx.ui.custom((tui, theme, _kb, done) => { + const container = new Container(); - // Top border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + // Top border + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - // Title - container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0)); + // Title + container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0)); - // Build select items with colored status - const items: SelectItem[] = files.map((f) => { - let statusColor: string; - switch (f.status) { - case "M": - statusColor = theme.fg("warning", f.status); - break; - case "A": - statusColor = theme.fg("success", f.status); - break; - case "D": - statusColor = theme.fg("error", f.status); - break; - case "?": - statusColor = theme.fg("muted", f.status); - break; - default: - statusColor = theme.fg("dim", f.status); - } - return { - value: f, - label: `${statusColor} ${f.file}`, - }; - }); + // Build select items with colored status + const items: SelectItem[] = files.map((f) => { + let statusColor: string; + switch (f.status) { + case "M": + statusColor = theme.fg("warning", f.status); + break; + case "A": + statusColor = theme.fg("success", f.status); + break; + case "D": + statusColor = theme.fg("error", f.status); + break; + case "?": + statusColor = theme.fg("muted", f.status); + break; + default: + statusColor = theme.fg("dim", f.status); + } + return { + value: f, + label: `${statusColor} ${f.file}`, + }; + }); - const visibleRows = Math.min(files.length, 15); - let currentIndex = 0; + const visibleRows = Math.min(files.length, 15); + let currentIndex = 0; - const selectList = new SelectList(items, visibleRows, { - selectedPrefix: (t) => theme.fg("accent", t), - selectedText: (t) => t, // Keep existing colors - description: (t) => theme.fg("muted", t), - scrollInfo: (t) => theme.fg("dim", t), - noMatch: (t) => theme.fg("warning", t), - }); - selectList.onSelect = (item) => { - void openSelected(item.value as FileInfo); - }; - selectList.onCancel = () => done(); - selectList.onSelectionChange = (item) => { - currentIndex = items.indexOf(item); - }; - container.addChild(selectList); + const selectList = new SelectList(items, visibleRows, { + selectedPrefix: (t) => theme.fg("accent", t), + selectedText: (t) => t, // Keep existing colors + description: (t) => theme.fg("muted", t), + scrollInfo: (t) => theme.fg("dim", t), + noMatch: (t) => theme.fg("warning", t), + }); + selectList.onSelect = (item) => { + void openSelected(item.value as FileInfo); + }; + selectList.onCancel = () => done(); + selectList.onSelectionChange = (item) => { + currentIndex = items.indexOf(item); + }; + container.addChild(selectList); - // Help text - container.addChild( - new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), - ); + // Help text + container.addChild( + new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), + ); - // Bottom border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + // Bottom border + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - return { - render: (w) => container.render(w), - invalidate: () => container.invalidate(), - handleInput: (data) => { - // Add paging with left/right - if (matchesKey(data, Key.left)) { - // Page up - clamp to 0 - currentIndex = Math.max(0, currentIndex - visibleRows); - selectList.setSelectedIndex(currentIndex); - } else if (matchesKey(data, Key.right)) { - // Page down - clamp to last - currentIndex = Math.min(items.length - 1, currentIndex + visibleRows); - selectList.setSelectedIndex(currentIndex); - } else { - selectList.handleInput(data); - } - tui.requestRender(); - }, - }; - }); - }, - }); + return { + render: (w) => container.render(w), + invalidate: () => container.invalidate(), + handleInput: (data) => { + // Add paging with left/right + if (matchesKey(data, Key.left)) { + // Page up - clamp to 0 + currentIndex = Math.max(0, currentIndex - visibleRows); + selectList.setSelectedIndex(currentIndex); + } else if (matchesKey(data, Key.right)) { + // Page down - clamp to last + currentIndex = Math.min(items.length - 1, currentIndex + visibleRows); + selectList.setSelectedIndex(currentIndex); + } else { + selectList.handleInput(data); + } + tui.requestRender(); + }, + }; + }); + }, + }); } diff --git a/.pi/extensions/files.ts b/.pi/extensions/files.ts index 88864f5bd..02aa4b4aa 100644 --- a/.pi/extensions/files.ts +++ b/.pi/extensions/files.ts @@ -7,167 +7,174 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { DynamicBorder } from "@mariozechner/pi-coding-agent"; -import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui"; +import { + Container, + Key, + matchesKey, + type SelectItem, + SelectList, + Text, +} from "@mariozechner/pi-tui"; interface FileEntry { - path: string; - operations: Set<"read" | "write" | "edit">; - lastTimestamp: number; + path: string; + operations: Set<"read" | "write" | "edit">; + lastTimestamp: number; } type FileToolName = "read" | "write" | "edit"; export default function (pi: ExtensionAPI) { - pi.registerCommand("files", { - description: "Show files read/written/edited in this session", - handler: async (_args, ctx) => { - if (!ctx.hasUI) { - ctx.ui.notify("No UI available", "error"); - return; - } + pi.registerCommand("files", { + description: "Show files read/written/edited in this session", + handler: async (_args, ctx) => { + if (!ctx.hasUI) { + ctx.ui.notify("No UI available", "error"); + return; + } - // Get the current branch (path from leaf to root) - const branch = ctx.sessionManager.getBranch(); + // Get the current branch (path from leaf to root) + const branch = ctx.sessionManager.getBranch(); - // First pass: collect tool calls (id -> {path, name}) from assistant messages - const toolCalls = new Map(); + // First pass: collect tool calls (id -> {path, name}) from assistant messages + const toolCalls = new Map(); - for (const entry of branch) { - if (entry.type !== "message") continue; - const msg = entry.message; + for (const entry of branch) { + if (entry.type !== "message") continue; + const msg = entry.message; - if (msg.role === "assistant" && Array.isArray(msg.content)) { - for (const block of msg.content) { - if (block.type === "toolCall") { - const name = block.name; - if (name === "read" || name === "write" || name === "edit") { - const path = block.arguments?.path; - if (path && typeof path === "string") { - toolCalls.set(block.id, { path, name, timestamp: msg.timestamp }); - } - } - } - } - } - } + if (msg.role === "assistant" && Array.isArray(msg.content)) { + for (const block of msg.content) { + if (block.type === "toolCall") { + const name = block.name; + if (name === "read" || name === "write" || name === "edit") { + const path = block.arguments?.path; + if (path && typeof path === "string") { + toolCalls.set(block.id, { path, name, timestamp: msg.timestamp }); + } + } + } + } + } + } - // Second pass: match tool results to get the actual execution timestamp - const fileMap = new Map(); + // Second pass: match tool results to get the actual execution timestamp + const fileMap = new Map(); - for (const entry of branch) { - if (entry.type !== "message") continue; - const msg = entry.message; + for (const entry of branch) { + if (entry.type !== "message") continue; + const msg = entry.message; - if (msg.role === "toolResult") { - const toolCall = toolCalls.get(msg.toolCallId); - if (!toolCall) continue; + if (msg.role === "toolResult") { + const toolCall = toolCalls.get(msg.toolCallId); + if (!toolCall) continue; - const { path, name } = toolCall; - const timestamp = msg.timestamp; + const { path, name } = toolCall; + const timestamp = msg.timestamp; - const existing = fileMap.get(path); - if (existing) { - existing.operations.add(name); - if (timestamp > existing.lastTimestamp) { - existing.lastTimestamp = timestamp; - } - } else { - fileMap.set(path, { - path, - operations: new Set([name]), - lastTimestamp: timestamp, - }); - } - } - } + const existing = fileMap.get(path); + if (existing) { + existing.operations.add(name); + if (timestamp > existing.lastTimestamp) { + existing.lastTimestamp = timestamp; + } + } else { + fileMap.set(path, { + path, + operations: new Set([name]), + lastTimestamp: timestamp, + }); + } + } + } - if (fileMap.size === 0) { - ctx.ui.notify("No files read/written/edited in this session", "info"); - return; - } + if (fileMap.size === 0) { + ctx.ui.notify("No files read/written/edited in this session", "info"); + return; + } - // Sort by most recent first - const files = Array.from(fileMap.values()).sort((a, b) => b.lastTimestamp - a.lastTimestamp); + // Sort by most recent first + const files = Array.from(fileMap.values()).sort((a, b) => b.lastTimestamp - a.lastTimestamp); - const openSelected = async (file: FileEntry): Promise => { - try { - await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd }); - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error"); - } - }; + const openSelected = async (file: FileEntry): Promise => { + try { + await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd }); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error"); + } + }; - // Show file picker with SelectList - await ctx.ui.custom((tui, theme, _kb, done) => { - const container = new Container(); + // Show file picker with SelectList + await ctx.ui.custom((tui, theme, _kb, done) => { + const container = new Container(); - // Top border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + // Top border + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - // Title - container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0)); + // Title + container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0)); - // Build select items with colored operations - const items: SelectItem[] = files.map((f) => { - const ops: string[] = []; - if (f.operations.has("read")) ops.push(theme.fg("muted", "R")); - if (f.operations.has("write")) ops.push(theme.fg("success", "W")); - if (f.operations.has("edit")) ops.push(theme.fg("warning", "E")); - const opsLabel = ops.join(""); - return { - value: f, - label: `${opsLabel} ${f.path}`, - }; - }); + // Build select items with colored operations + const items: SelectItem[] = files.map((f) => { + const ops: string[] = []; + if (f.operations.has("read")) ops.push(theme.fg("muted", "R")); + if (f.operations.has("write")) ops.push(theme.fg("success", "W")); + if (f.operations.has("edit")) ops.push(theme.fg("warning", "E")); + const opsLabel = ops.join(""); + return { + value: f, + label: `${opsLabel} ${f.path}`, + }; + }); - const visibleRows = Math.min(files.length, 15); - let currentIndex = 0; + const visibleRows = Math.min(files.length, 15); + let currentIndex = 0; - const selectList = new SelectList(items, visibleRows, { - selectedPrefix: (t) => theme.fg("accent", t), - selectedText: (t) => t, // Keep existing colors - description: (t) => theme.fg("muted", t), - scrollInfo: (t) => theme.fg("dim", t), - noMatch: (t) => theme.fg("warning", t), - }); - selectList.onSelect = (item) => { - void openSelected(item.value as FileEntry); - }; - selectList.onCancel = () => done(); - selectList.onSelectionChange = (item) => { - currentIndex = items.indexOf(item); - }; - container.addChild(selectList); + const selectList = new SelectList(items, visibleRows, { + selectedPrefix: (t) => theme.fg("accent", t), + selectedText: (t) => t, // Keep existing colors + description: (t) => theme.fg("muted", t), + scrollInfo: (t) => theme.fg("dim", t), + noMatch: (t) => theme.fg("warning", t), + }); + selectList.onSelect = (item) => { + void openSelected(item.value as FileEntry); + }; + selectList.onCancel = () => done(); + selectList.onSelectionChange = (item) => { + currentIndex = items.indexOf(item); + }; + container.addChild(selectList); - // Help text - container.addChild( - new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), - ); + // Help text + container.addChild( + new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0), + ); - // Bottom border - container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); + // Bottom border + container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s))); - return { - render: (w) => container.render(w), - invalidate: () => container.invalidate(), - handleInput: (data) => { - // Add paging with left/right - if (matchesKey(data, Key.left)) { - // Page up - clamp to 0 - currentIndex = Math.max(0, currentIndex - visibleRows); - selectList.setSelectedIndex(currentIndex); - } else if (matchesKey(data, Key.right)) { - // Page down - clamp to last - currentIndex = Math.min(items.length - 1, currentIndex + visibleRows); - selectList.setSelectedIndex(currentIndex); - } else { - selectList.handleInput(data); - } - tui.requestRender(); - }, - }; - }); - }, - }); + return { + render: (w) => container.render(w), + invalidate: () => container.invalidate(), + handleInput: (data) => { + // Add paging with left/right + if (matchesKey(data, Key.left)) { + // Page up - clamp to 0 + currentIndex = Math.max(0, currentIndex - visibleRows); + selectList.setSelectedIndex(currentIndex); + } else if (matchesKey(data, Key.right)) { + // Page down - clamp to last + currentIndex = Math.min(items.length - 1, currentIndex + visibleRows); + selectList.setSelectedIndex(currentIndex); + } else { + selectList.handleInput(data); + } + tui.requestRender(); + }, + }; + }); + }, + }); } diff --git a/.pi/extensions/prompt-url-widget.ts b/.pi/extensions/prompt-url-widget.ts index 20406e937..a988f937d 100644 --- a/.pi/extensions/prompt-url-widget.ts +++ b/.pi/extensions/prompt-url-widget.ts @@ -1,158 +1,171 @@ -import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent"; +import { + DynamicBorder, + type ExtensionAPI, + type ExtensionContext, +} from "@mariozechner/pi-coding-agent"; import { Container, Text } from "@mariozechner/pi-tui"; const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im; const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im; type PromptMatch = { - kind: "pr" | "issue"; - url: string; + kind: "pr" | "issue"; + url: string; }; type GhMetadata = { - title?: string; - author?: { - login?: string; - name?: string | null; - }; + title?: string; + author?: { + login?: string; + name?: string | null; + }; }; function extractPromptMatch(prompt: string): PromptMatch | undefined { - const prMatch = prompt.match(PR_PROMPT_PATTERN); - if (prMatch?.[1]) { - return { kind: "pr", url: prMatch[1].trim() }; - } + const prMatch = prompt.match(PR_PROMPT_PATTERN); + if (prMatch?.[1]) { + return { kind: "pr", url: prMatch[1].trim() }; + } - const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN); - if (issueMatch?.[1]) { - return { kind: "issue", url: issueMatch[1].trim() }; - } + const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN); + if (issueMatch?.[1]) { + return { kind: "issue", url: issueMatch[1].trim() }; + } - return undefined; + return undefined; } async function fetchGhMetadata( - pi: ExtensionAPI, - kind: PromptMatch["kind"], - url: string, + pi: ExtensionAPI, + kind: PromptMatch["kind"], + url: string, ): Promise { - const args = - kind === "pr" ? ["pr", "view", url, "--json", "title,author"] : ["issue", "view", url, "--json", "title,author"]; + const args = + kind === "pr" + ? ["pr", "view", url, "--json", "title,author"] + : ["issue", "view", url, "--json", "title,author"]; - try { - const result = await pi.exec("gh", args); - if (result.code !== 0 || !result.stdout) return undefined; - return JSON.parse(result.stdout) as GhMetadata; - } catch { - return undefined; - } + try { + const result = await pi.exec("gh", args); + if (result.code !== 0 || !result.stdout) return undefined; + return JSON.parse(result.stdout) as GhMetadata; + } catch { + return undefined; + } } function formatAuthor(author?: GhMetadata["author"]): string | undefined { - if (!author) return undefined; - const name = author.name?.trim(); - const login = author.login?.trim(); - if (name && login) return `${name} (@${login})`; - if (login) return `@${login}`; - if (name) return name; - return undefined; + if (!author) return undefined; + const name = author.name?.trim(); + const login = author.login?.trim(); + if (name && login) return `${name} (@${login})`; + if (login) return `@${login}`; + if (name) return name; + return undefined; } export default function promptUrlWidgetExtension(pi: ExtensionAPI) { - const setWidget = (ctx: ExtensionContext, match: PromptMatch, title?: string, authorText?: string) => { - ctx.ui.setWidget("prompt-url", (_tui, thm) => { - const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url); - const authorLine = authorText ? thm.fg("muted", authorText) : undefined; - const urlLine = thm.fg("dim", match.url); + const setWidget = ( + ctx: ExtensionContext, + match: PromptMatch, + title?: string, + authorText?: string, + ) => { + ctx.ui.setWidget("prompt-url", (_tui, thm) => { + const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url); + const authorLine = authorText ? thm.fg("muted", authorText) : undefined; + const urlLine = thm.fg("dim", match.url); - const lines = [titleText]; - if (authorLine) lines.push(authorLine); - lines.push(urlLine); + const lines = [titleText]; + if (authorLine) lines.push(authorLine); + lines.push(urlLine); - const container = new Container(); - container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s))); - container.addChild(new Text(lines.join("\n"), 1, 0)); - return container; - }); - }; + const container = new Container(); + container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s))); + container.addChild(new Text(lines.join("\n"), 1, 0)); + return container; + }); + }; - const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => { - const label = match.kind === "pr" ? "PR" : "Issue"; - const trimmedTitle = title?.trim(); - const fallbackName = `${label}: ${match.url}`; - const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName; - const currentName = pi.getSessionName()?.trim(); - if (!currentName) { - pi.setSessionName(desiredName); - return; - } - if (currentName === match.url || currentName === fallbackName) { - pi.setSessionName(desiredName); - } - }; + const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => { + const label = match.kind === "pr" ? "PR" : "Issue"; + const trimmedTitle = title?.trim(); + const fallbackName = `${label}: ${match.url}`; + const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName; + const currentName = pi.getSessionName()?.trim(); + if (!currentName) { + pi.setSessionName(desiredName); + return; + } + if (currentName === match.url || currentName === fallbackName) { + pi.setSessionName(desiredName); + } + }; - pi.on("before_agent_start", async (event, ctx) => { - if (!ctx.hasUI) return; - const match = extractPromptMatch(event.prompt); - if (!match) { - return; - } + pi.on("before_agent_start", async (event, ctx) => { + if (!ctx.hasUI) return; + const match = extractPromptMatch(event.prompt); + if (!match) { + return; + } - setWidget(ctx, match); - applySessionName(ctx, match); - void fetchGhMetadata(pi, match.kind, match.url).then((meta) => { - const title = meta?.title?.trim(); - const authorText = formatAuthor(meta?.author); - setWidget(ctx, match, title, authorText); - applySessionName(ctx, match, title); - }); - }); + setWidget(ctx, match); + applySessionName(ctx, match); + void fetchGhMetadata(pi, match.kind, match.url).then((meta) => { + const title = meta?.title?.trim(); + const authorText = formatAuthor(meta?.author); + setWidget(ctx, match, title, authorText); + applySessionName(ctx, match, title); + }); + }); - pi.on("session_switch", async (_event, ctx) => { - rebuildFromSession(ctx); - }); + pi.on("session_switch", async (_event, ctx) => { + rebuildFromSession(ctx); + }); - const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => { - if (!content) return ""; - if (typeof content === "string") return content; - return ( - content - .filter((block): block is { type: "text"; text: string } => block.type === "text") - .map((block) => block.text) - .join("\n") ?? "" - ); - }; + const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => { + if (!content) return ""; + if (typeof content === "string") return content; + return ( + content + .filter((block): block is { type: "text"; text: string } => block.type === "text") + .map((block) => block.text) + .join("\n") ?? "" + ); + }; - const rebuildFromSession = (ctx: ExtensionContext) => { - if (!ctx.hasUI) return; + const rebuildFromSession = (ctx: ExtensionContext) => { + if (!ctx.hasUI) return; - const entries = ctx.sessionManager.getEntries(); - const lastMatch = [...entries].reverse().find((entry) => { - if (entry.type !== "message" || entry.message.role !== "user") return false; - const text = getUserText(entry.message.content); - return !!extractPromptMatch(text); - }); + const entries = ctx.sessionManager.getEntries(); + const lastMatch = [...entries].reverse().find((entry) => { + if (entry.type !== "message" || entry.message.role !== "user") return false; + const text = getUserText(entry.message.content); + return !!extractPromptMatch(text); + }); - const content = - lastMatch?.type === "message" && lastMatch.message.role === "user" ? lastMatch.message.content : undefined; - const text = getUserText(content); - const match = text ? extractPromptMatch(text) : undefined; - if (!match) { - ctx.ui.setWidget("prompt-url", undefined); - return; - } + const content = + lastMatch?.type === "message" && lastMatch.message.role === "user" + ? lastMatch.message.content + : undefined; + const text = getUserText(content); + const match = text ? extractPromptMatch(text) : undefined; + if (!match) { + ctx.ui.setWidget("prompt-url", undefined); + return; + } - setWidget(ctx, match); - applySessionName(ctx, match); - void fetchGhMetadata(pi, match.kind, match.url).then((meta) => { - const title = meta?.title?.trim(); - const authorText = formatAuthor(meta?.author); - setWidget(ctx, match, title, authorText); - applySessionName(ctx, match, title); - }); - }; + setWidget(ctx, match); + applySessionName(ctx, match); + void fetchGhMetadata(pi, match.kind, match.url).then((meta) => { + const title = meta?.title?.trim(); + const authorText = formatAuthor(meta?.author); + setWidget(ctx, match, title, authorText); + applySessionName(ctx, match, title); + }); + }; - pi.on("session_start", async (_event, ctx) => { - rebuildFromSession(ctx); - }); + pi.on("session_start", async (_event, ctx) => { + rebuildFromSession(ctx); + }); } diff --git a/.pi/extensions/redraws.ts b/.pi/extensions/redraws.ts index 2db0867f7..a1c45b8fa 100644 --- a/.pi/extensions/redraws.ts +++ b/.pi/extensions/redraws.ts @@ -8,17 +8,17 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent"; import { Text } from "@mariozechner/pi-tui"; export default function (pi: ExtensionAPI) { - pi.registerCommand("tui", { - description: "Show TUI stats", - handler: async (_args, ctx) => { - if (!ctx.hasUI) return; - let redraws = 0; - await ctx.ui.custom((tui, _theme, _keybindings, done) => { - redraws = tui.fullRedraws; - done(undefined); - return new Text("", 0, 0); - }); - ctx.ui.notify(`TUI full redraws: ${redraws}`, "info"); - }, - }); + pi.registerCommand("tui", { + description: "Show TUI stats", + handler: async (_args, ctx) => { + if (!ctx.hasUI) return; + let redraws = 0; + await ctx.ui.custom((tui, _theme, _keybindings, done) => { + redraws = tui.fullRedraws; + done(undefined); + return new Text("", 0, 0); + }); + ctx.ui.notify(`TUI full redraws: ${redraws}`, "info"); + }, + }); } diff --git a/.pi/prompts/cl.md b/.pi/prompts/cl.md index 5b481aee8..6d79ecda6 100644 --- a/.pi/prompts/cl.md +++ b/.pi/prompts/cl.md @@ -1,16 +1,19 @@ --- description: Audit changelog entries before release --- + Audit changelog entries for all commits since the last release. ## Process 1. **Find the last release tag:** + ```bash git tag --sort=-version:refname | head -1 ``` 2. **List all commits since that tag:** + ```bash git log ..HEAD --oneline ``` @@ -42,6 +45,7 @@ Audit changelog entries for all commits since the last release. ## Changelog Format Reference Sections (in order): + - `### Breaking Changes` - API changes requiring migration - `### Added` - New features - `### Changed` - Changes to existing functionality @@ -49,5 +53,6 @@ Sections (in order): - `### Removed` - Removed features Attribution: + - Internal: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/issues/123))` - External: `Added bar ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@user](https://github.com/user))` diff --git a/.pi/prompts/is.md b/.pi/prompts/is.md index f57561f6e..cc8f603ad 100644 --- a/.pi/prompts/is.md +++ b/.pi/prompts/is.md @@ -1,6 +1,7 @@ --- description: Analyze GitHub issues (bugs or feature requests) --- + Analyze GitHub issue(s): $ARGUMENTS For each issue: diff --git a/.pi/prompts/pr.md b/.pi/prompts/pr.md index ac6c940bf..f16236566 100644 --- a/.pi/prompts/pr.md +++ b/.pi/prompts/pr.md @@ -1,13 +1,15 @@ --- description: Review PRs from URLs with structured issue and code analysis --- + You are given one or more GitHub PR URLs: $@ For each PR URL, do the following in order: + 1. Read the PR page in full. Include description, all comments, all commits, and all changed files. 2. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments. 3. Analyze the PR diff. Read all relevant code files in full with no truncation from the current main branch and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior. -4. Check if docs/*.md require modification. This is usually the case when existing features have been changed, or new features have been added. +4. Check if docs/\*.md require modification. This is usually the case when existing features have been changed, or new features have been added. 5. Provide a structured review with these sections: - Good: solid choices or improvements - Bad: concrete issues, regressions, missing tests, or risks @@ -18,16 +20,17 @@ For each PR URL, do the following in order: Output format per PR: PR: Good: + - ... -Bad: + Bad: - ... -Ugly: + Ugly: - ... -Questions or Assumptions: + Questions or Assumptions: - ... -Change summary: + Change summary: - ... -Tests: + Tests: - ... If no issues are found, say so under Bad and Ugly. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d3744ac60..e946d18c1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -51,9 +51,9 @@ repos: rev: v0.11.0 hooks: - id: shellcheck - args: [--severity=error] # Only fail on errors, not warnings/info + args: [--severity=error] # Only fail on errors, not warnings/info # Exclude vendor and scripts with embedded code or known issues - exclude: '^(vendor/|scripts/e2e/)' + exclude: "^(vendor/|scripts/e2e/)" # GitHub Actions linting - repo: https://github.com/rhysd/actionlint @@ -67,7 +67,7 @@ repos: hooks: - id: zizmor args: [--persona=regular, --min-severity=medium, --min-confidence=medium] - exclude: '^(vendor/|Swabble/)' + exclude: "^(vendor/|Swabble/)" # Project checks (same commands as CI) - repo: local diff --git a/AGENTS.md b/AGENTS.md index 405858d2b..4f99bdeca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,10 @@ # Repository Guidelines + - Repo: https://github.com/openclaw/openclaw - GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n". ## Project Structure & Module Organization + - Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, web provider in `src/provider-web.ts`, infra in `src/infra`, media pipeline in `src/media`). - Tests: colocated `*.test.ts`. - Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`. @@ -16,6 +18,7 @@ - When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage. ## Docs Linking (Mintlify) + - Docs are hosted on Mintlify (docs.openclaw.ai). - Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`). - Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`). @@ -26,6 +29,7 @@ - Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”. ## exe.dev VM ops (general) + - Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set). - SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops. - Update: `sudo npm i -g openclaw@latest` (global install needs root on `/usr/lib/node_modules`). @@ -36,6 +40,7 @@ - Verify: `openclaw channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/openclaw-gateway.log`. ## Build, Test, and Development Commands + - Runtime baseline: Node **22+** (keep Node + Bun paths working). - Install deps: `pnpm install` - Pre-commit hooks: `prek install` (runs same checks as CI) @@ -49,6 +54,7 @@ - Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage` ## Coding Style & Naming Conventions + - Language: TypeScript (ESM). Prefer strict typing; avoid `any`. - Formatting/linting via Oxlint and Oxfmt; run `pnpm lint` before commits. - Add brief code comments for tricky or non-obvious logic. @@ -57,11 +63,13 @@ - Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys. ## Release Channels (Naming) + - stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`. - beta: prerelease tags `vYYYY.M.D-beta.N`, npm dist-tag `beta` (may ship without macOS app). - dev: moving head on `main` (no tag; git checkout main). ## Testing Guidelines + - Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements). - Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`. - Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic. @@ -72,6 +80,7 @@ - Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available. ## Commit & Pull Request Guidelines + - Create commits with `scripts/committer "" `; avoid manual `git add`/`git commit` so staging stays scoped. - Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`). - Group related changes; avoid bundling unrelated refactors. @@ -90,23 +99,28 @@ - After merging a PR: run `bun scripts/update-clawtributors.ts` if the contributor is missing, then commit the regenerated README. ## Shorthand Commands + - `sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`. ### PR Workflow (Review vs Land) + - **Review mode (PR link only):** read `gh pr view/diff`; **do not** switch branches; **do not** change code. - **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm lint && pnpm build && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: contributor needs to be in git graph after this! ## Security & Configuration Tips + - Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out. - Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable. - Environment variables: see `~/.profile`. - Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples. - - Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them. +- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them. ## Troubleshooting + - Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`). ## Agent-Specific Notes + - Vocabulary: "makeup" = "mac app". - Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`. - Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`. @@ -155,6 +169,7 @@ - Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step. ## NPM + 1Password (publish/verify) + - Use the 1password skill; all `op` commands must run inside a fresh tmux session. - Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on). - OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`. diff --git a/CHANGELOG.md b/CHANGELOG.md index de8dfabd3..e1926234f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Docs: https://docs.openclaw.ai ## 2026.1.30 ### Changes + - CLI: add `completion` command (Zsh/Bash/PowerShell/Fish) and auto-setup during postinstall/onboarding. - CLI: add per-agent `models status` (`--agent` filter). (#4780) Thanks @jlowin. - Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12. @@ -16,6 +17,7 @@ Docs: https://docs.openclaw.ai - Docs: add pi/pi-dev docs and update OpenClaw branding + install links. ### Fixes + - Security: restrict local path extraction in media parser to prevent LFI. (#4880) - Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren. - Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos. @@ -37,6 +39,7 @@ Docs: https://docs.openclaw.ai ## 2026.1.29 ### Changes + - Rebrand: rename the npm package/CLI to `openclaw`, add a `openclaw` compatibility shim, and move extensions to the `@openclaw/*` scope. - Onboarding: strengthen security warning copy for beta + access control expectations. - Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub. @@ -98,10 +101,13 @@ Docs: https://docs.openclaw.ai - Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD. - Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99. - Docs: update exe.dev install instructions. (#https://github.com/openclaw/openclaw/pull/3047) Thanks @zackerthescar. + ### Breaking + - **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed). ### Fixes + - Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796) - Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R. - Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald. @@ -154,6 +160,7 @@ Docs: https://docs.openclaw.ai ## 2026.1.24-3 ### Fixes + - Slack: fix image downloads failing due to missing Authorization header on cross-origin redirects. (#1936) Thanks @sanderhelgesen. - Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie. - Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie. @@ -162,16 +169,19 @@ Docs: https://docs.openclaw.ai ## 2026.1.24-2 ### Fixes + - Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install). ## 2026.1.24-1 ### Fixes + - Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install). ## 2026.1.24 ### Highlights + - Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.openclaw.ai/providers/ollama https://docs.openclaw.ai/providers/venice - Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg. - TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.openclaw.ai/tts @@ -179,6 +189,7 @@ Docs: https://docs.openclaw.ai - Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.openclaw.ai/channels/telegram ### Changes + - Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg. - TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.openclaw.ai/tts - TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.openclaw.ai/tts @@ -197,6 +208,7 @@ Docs: https://docs.openclaw.ai - Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido. ### Fixes + - Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589. - Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent. - Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg. @@ -240,11 +252,13 @@ Docs: https://docs.openclaw.ai ## 2026.1.23-1 ### Fixes + - Packaging: include dist/tts output in npm tarball (fixes missing dist/tts/tts.js). ## 2026.1.23 ### Highlights + - TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.openclaw.ai/tts - Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.openclaw.ai/gateway/tools-invoke-http-api - Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.openclaw.ai/gateway/heartbeat @@ -252,6 +266,7 @@ Docs: https://docs.openclaw.ai - Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.openclaw.ai/channels/tlon ### Changes + - Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.openclaw.ai/multi-agent-sandbox-tools - Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.openclaw.ai/bedrock - CLI: add `openclaw system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.openclaw.ai/cli/system @@ -266,6 +281,7 @@ Docs: https://docs.openclaw.ai - Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.openclaw.ai/gateway/heartbeat ### Fixes + - Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518) - Heartbeat: accept plugin channel ids for heartbeat target validation + UI hints. - Messaging/Sessions: mirror outbound sends into target session keys (threads + dmScope), create session entries on send, and normalize session key casing. (#1520, commit 4b6cdd1d3) @@ -304,6 +320,7 @@ Docs: https://docs.openclaw.ai ## 2026.1.22 ### Changes + - Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer. - Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren. - Slack: add chat-type reply threading overrides via `replyToModeByChatType`. (#1442) Thanks @stefangalescu. @@ -311,6 +328,7 @@ Docs: https://docs.openclaw.ai - Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link. ### Fixes + - BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell. - Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky. - Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla. @@ -341,12 +359,14 @@ Docs: https://docs.openclaw.ai ## 2026.1.21-2 ### Fixes + - Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.openclaw.ai/cli/agents https://docs.openclaw.ai/web/control-ui - Slack: remove deprecated `filetype` field from `files.uploadV2` to eliminate API warnings. (#1447) ## 2026.1.21 ### Changes + - Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.openclaw.ai/tools/lobster - Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.openclaw.ai/tools/lobster - Heartbeat: allow running heartbeats in an explicit session key. (#1256) Thanks @zknicker. @@ -369,10 +389,12 @@ Docs: https://docs.openclaw.ai - Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky. ### Breaking + - **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.openclaw.ai/web/control-ui#insecure-http - **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert. ### Fixes + - Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman. - Gateway: keep auto bind loopback-first and add explicit tailnet binding to avoid Tailscale taking over local UI. (#1380) - Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early. @@ -395,6 +417,7 @@ Docs: https://docs.openclaw.ai ## 2026.1.20 ### Changes + - Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.openclaw.ai/web/control-ui - Control UI: drop the legacy list view. (#1345) https://docs.openclaw.ai/web/control-ui - TUI: add syntax highlighting for code blocks. (#1200) https://docs.openclaw.ai/tui @@ -473,9 +496,11 @@ Docs: https://docs.openclaw.ai - Swabble: use the tagged Commander Swift package release. ### Breaking + - **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `openclaw doctor --fix` to repair, then update plugins (`openclaw plugins update`) if you use any. ### Fixes + - Discovery: shorten Bonjour DNS-SD service type to `_moltbot-gw._tcp` and update discovery clients/docs. - Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry. - Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244) @@ -575,20 +600,23 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.16-2 ### Changes + - CLI: stamp build commit into dist metadata so banners show the commit in npm installs. - CLI: close memory manager after memory commands to avoid hanging processes. (#1127) — thanks @NicholasSpisak. ## 2026.1.16-1 ### Highlights + - Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.openclaw.ai/hooks - Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.openclaw.ai/nodes/media-understanding - Plugins: add Zalo Personal plugin (`@openclaw/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.openclaw.ai/plugins/zalouser - Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.openclaw.ai/providers/vercel-ai-gateway -- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.openclaw.ai/concepts/session +- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.openclaw.ai/concepts/session - Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.openclaw.ai/tools/web ### Breaking + - **BREAKING:** `openclaw message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan. - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. - **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`. @@ -597,6 +625,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - **BREAKING:** `openclaw plugins install ` now copies into `~/.openclaw/extensions` (use `--link` to keep path-based loading). ### Changes + - Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO. - Plugins: add bundled Antigravity + Gemini CLI OAuth + Copilot Proxy provider plugins. (#1066) — thanks @ItzR3NO. - Tools: improve `web_fetch` extraction using Readability (with fallback). @@ -632,6 +661,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Plugins: add zip installs and `--link` to avoid copying local paths. ### Fixes + - macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash. - Verbose: wrap tool summaries/output in markdown only for markdown-capable channels. - Tools: include provider/session context in elevated exec denial errors. @@ -688,17 +718,20 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.15 ### Highlights + - Plugins: add provider auth registry + `openclaw models auth login` for plugin-driven OAuth/API key flows. - Browser: improve remote CDP/Browserless support (auth passthrough, `wss` upgrade, timeouts, clearer errors). - Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf. - Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs). ### Breaking + - **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702) - **BREAKING:** Microsoft Teams is now a plugin; install `@openclaw/msteams` via `openclaw plugins install @openclaw/msteams`. - **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow. ### Changes + - UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow. - CLI: set process titles to `openclaw-` for clearer process listings. - CLI/macOS: sync remote SSH target/identity to config and let `gateway status` auto-infer SSH targets (ssh-config aware). @@ -738,6 +771,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Discord: allow emoji/sticker uploads + channel actions in config defaults. (#870) — thanks @JDIVE. ### Fixes + - Messages: make `/stop` clear queued followups and pending session lane work for a hard abort. - Messages: make `/stop` abort active sub-agent runs spawned from the requester session and report how many were stopped. - WhatsApp: report linked status consistently in channel status. (#1050) — thanks @YuriNachos. @@ -774,12 +808,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.14-1 ### Highlights + - Web search: `web_search`/`web_fetch` tools (Brave API) + first-time setup in onboarding/configure. - Browser control: Chrome extension relay takeover mode + remote browser control support. - Plugins: channel plugins (gateway HTTP hooks) + Zalo plugin + onboarding install flow. (#854) — thanks @longmaba. - Security: expanded `openclaw security audit` (+ `--fix`), detect-secrets CI scan, and a `SECURITY.md` reporting policy. ### Changes + - Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics. - Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors. - Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging. @@ -796,6 +832,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Browser: add Chrome extension relay takeover mode (toolbar button), plus `openclaw browser extension install/path` and remote browser control (standalone server + token auth). ### Fixes + - Sessions: refactor session store updates to lock + mutate per-entry, add chat.inject, and harden subagent cleanup flow. (#944) — thanks @tyler6204. - Browser: add tests for snapshot labels/efficient query params and labeled image responses. - Google: downgrade unsigned thinking blocks before send to avoid missing signature errors. @@ -817,6 +854,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.14 ### Changes + - Usage: add MiniMax coding plan usage tracking. - Auth: label Claude Code CLI auth options. (#915) — thanks @SeanZoR. - Docs: standardize Claude Code CLI naming across docs and prompts. (follow-up to #915) @@ -824,14 +862,16 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Config: add `channels..configWrites` gating for channel-initiated config writes; migrate Slack channel IDs. ### Fixes - - Mac: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor. - - UI: use application-defined WebSocket close code (browser compatibility). (#918) — thanks @rahthakor. + +- Mac: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor. +- UI: use application-defined WebSocket close code (browser compatibility). (#918) — thanks @rahthakor. - TUI: render picker overlays via the overlay stack so /models and /settings display. (#921) — thanks @grizzdank. - TUI: add a bright spinner + elapsed time in the status line for send/stream/run states. - TUI: show LLM error messages (rate limits, auth, etc.) instead of `(no output)`. - Gateway/Dev: ensure `pnpm gateway:dev` always uses the dev profile config + state (`~/.openclaw-dev`). #### Agents / Auth / Tools / Sandbox + - Agents: make user time zone and 24-hour time explicit in the system prompt. (#859) — thanks @CashWilliams. - Agents: strip downgraded tool call text without eating adjacent replies and filter thinking-tag leaks. (#905) — thanks @erikpr1994. - Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli. @@ -844,6 +884,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Google: downgrade unsigned thinking blocks before send to avoid missing signature errors. #### macOS / Apps + - macOS: ensure launchd log directory exists with a test-only override. (#909) — thanks @roshanasingh4. - macOS: format ConnectionsStore config to satisfy SwiftFormat lint. (#852) — thanks @mneves75. - macOS: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor. @@ -863,12 +904,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.13 ### Fixes + - Postinstall: treat already-applied pnpm patches as no-ops to avoid npm/bun install failures. - Packaging: pin `@mariozechner/pi-ai` to 0.45.7 and refresh patched dependency to match npm resolution. ## 2026.1.12-2 ### Fixes + - Packaging: include `dist/memory/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/memory/index.js`). - Agents: persist sub-agent registry across gateway restarts and resume announce flow safely. (#831) — thanks @roshanasingh4. - Agents: strip invalid Gemini thought signatures from OpenRouter history to avoid 400s. (#841, #845) — thanks @MatthieuBizien. @@ -876,11 +919,13 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.12-1 ### Fixes + - Packaging: include `dist/channels/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/channels/registry.js`). ## 2026.1.12 ### Highlights + - **BREAKING:** rename chat “providers” (Slack/Telegram/WhatsApp/…) to **channels** across CLI/RPC/config; legacy config keys auto-migrate on load (and are written back as `channels.*`). - Memory: add vector search for agent memories (Markdown-only) with SQLite index, chunking, lazy sync + file watch, and per-agent enablement/fallback. - Plugins: restore full voice-call plugin parity (Telnyx/Twilio, streaming, inbound policies, tools/CLI). @@ -889,6 +934,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Agents: add compaction mode config with optional safeguard summarization and per-agent model fallbacks. (#700) — thanks @thewilloftheshadow; (#583) — thanks @mitschabaude-bot. ### New & Improved + - Memory: add custom OpenAI-compatible embedding endpoints; support OpenAI/local `node-llama-cpp` embeddings with per-agent overrides and provider metadata in tools/CLI. (#819) — thanks @mukhtharcm. - Memory: new `openclaw memory` CLI plus `memory_search`/`memory_get` tools with snippets + line ranges; index stored under `~/.openclaw/memory/{agentId}.sqlite` with watch-on-by-default. - Agents: strengthen memory recall guidance; make workspace bootstrap truncation configurable (default 20k) with warnings; add default sub-agent model config. @@ -902,9 +948,11 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal. ### Installer + - Install: run `openclaw doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected. ### Fixes + - Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds. - Tools: apply global tool allow/deny even when agent-specific tool policy is set. - Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles. (#822) — thanks @sebslight; (#705) — thanks @TAGOOZ. @@ -929,6 +977,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Connections UI: polish multi-account account cards. (#816) — thanks @steipete. ### Maintenance + - Dependencies: bump Pi packages to 0.45.3 and refresh patched pi-ai. - Testing: update Vitest + browser-playwright to 4.0.17. - Docs: add Amazon Bedrock provider notes and link from models/FAQ. @@ -936,12 +985,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.11 ### Highlights + - Plugins are now first-class: loader + CLI management, plus the new Voice Call plugin. - Config: modular `$include` support for split config files. (#731) — thanks @pasogott. - Agents/Pi: reserve compaction headroom so pre-compaction memory writes can run before auto-compaction. - Agents: automatic pre-compaction memory flush turn to store durable memories before compaction. ### Changes + - CLI/Onboarding: simplify MiniMax auth choice to a single M2.1 option. - CLI: configure section selection now loops until Continue. - Docs: explain MiniMax vs MiniMax Lightning (speed vs cost) and restore LM Studio example. @@ -977,6 +1028,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - macOS: remove the attach-only gateway setting; local mode now always manages launchd while still attaching to an existing gateway if present. ### Installer + - Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests. - Postinstall: skip pnpm patch fallback when the new patcher is active. - Installer tests: add root+non-root docker smokes, CI workflow to fetch openclaw.ai scripts and run install sh/cli with onboarding skipped. @@ -985,6 +1037,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm). ### Fixes + - Models/Onboarding: configure MiniMax (minimax.io) via Anthropic-compatible `/anthropic` endpoint by default (keep `minimax-api` as a legacy alias). - Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete. - CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete. @@ -1024,12 +1077,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.10 ### Highlights + - CLI: `openclaw status` now table-based + shows OS/update/gateway/daemon/agents/sessions; `status --all` adds a full read-only debug report (tables, log tails, Tailscale summary, and scan progress via OSC-9 + spinner). - CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe. - CLI: add `openclaw update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa. - Gateway: add OpenAI-compatible `/v1/chat/completions` HTTP endpoint (auth, SSE streaming, per-agent routing). (#680). ### Changes + - Onboarding/Models: add first-class Z.AI (GLM) auth choice (`zai-api-key`) + `--zai-api-key` flag. - CLI/Onboarding: add OpenRouter API key auth option in configure/onboard. (#703) — thanks @mteam88. - Agents: add human-delay pacing between block replies (modes: off/natural/custom, per-agent configurable). (#446) — thanks @tony-freedomology. @@ -1043,6 +1098,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo. ### Fixes + - Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesn’t leak partial output. - CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe). - CLI/Gateway: clarify that `openclaw gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures. @@ -1114,10 +1170,10 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Agents: repair session transcripts by dropping duplicate tool results across the whole history (unblocks Anthropic-compatible APIs after retries). - Tests/Live: reset the gateway session between model runs to avoid cross-provider transcript incompatibilities (notably OpenAI Responses reasoning replay rules). - ## 2026.1.9 ### Highlights + - Microsoft Teams provider: polling, attachments, outbound CLI send, per-channel policy. - Models/Auth expansion: OpenCode Zen + MiniMax API onboarding; token auth profiles + auth order; OAuth health in doctor/status. - CLI/Gateway UX: message subcommands, gateway discover/status/SSH, /config + /debug, sandbox CLI. @@ -1126,10 +1182,12 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX. ### Breaking + - CLI: `openclaw message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured. - Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`. ### New Features and Changes + - Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff. - Models/Auth: setup-token + token auth profiles; `openclaw models auth order {get,set,clear}`; per-agent auth candidates in `/model status`; OAuth expiry checks in doctor/status. - Agent/System: claude-cli runner; `session_status` tool (and sandbox allow); adaptive context pruning default; system prompt messaging guidance + no auto self-update; eligible skills list injection; sub-agent context trimmed. @@ -1151,6 +1209,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Apps/Branding: refreshed iOS/Android/macOS icons (#521) — thanks @fishfisher. ### Fixes + - Packaging: include MS Teams send module in npm tarball. - Sandbox/Browser: auto-start CDP endpoint; proxy CDP out of container for attachOnly; relax Bun fetch typing; align sandbox list output with config images. - Agents/Runtime: gate heartbeat prompt to default sessions; /stop aborts between tool calls; require explicit system-event session keys; guard small context windows; fix model fallback stringification; sessions_spawn inherits provider; failover on billing/credits; respect auth cooldown ordering; restore Anthropic OAuth tool dispatch + tool-name bypass; avoid OpenAI invalid reasoning replay; harden Gmail hook model defaults. @@ -1168,7 +1227,8 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag. ### Maintenance -- Dependencies: bump pi-* stack to 0.42.2. + +- Dependencies: bump pi-\* stack to 0.42.2. - Dependencies: Pi 0.40.0 bump (#543) — thanks @mcinteerj. - Build: Docker build cache layer (#605) — thanks @zknicker. @@ -1177,6 +1237,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic ## 2026.1.8 ### Highlights + - Security: DMs locked down by default across providers; pairing-first + allowlist guidance. - Sandbox: per-agent scope defaults + workspace access controls; tool/session isolation tuned. - Agent loop: compaction, pruning, streaming, and error handling hardened. @@ -1185,6 +1246,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - CLI/Gateway/Doctor: daemon/logs/status, auth migration, and diagnostics significantly expanded. ### Breaking + - **SECURITY (update ASAP):** inbound DMs are now **locked down by default** on Telegram/WhatsApp/Signal/iMessage/Discord/Slack. - Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots). - New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`). @@ -1199,6 +1261,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; move `login/logout` to `providers login/logout` (top-level aliases hidden); use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops. ### Fixes + - **CLI/Gateway/Doctor:** daemon runtime selection + improved logs/status/health/errors; auth/password handling for local CLI; richer close/timeout details; auto-migrate legacy config/sessions/state; integrity checks + repair prompts; `--yes`/`--non-interactive`; `--deep` gateway scans; better restart/service hints. - **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking. - **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification. @@ -1210,13 +1273,15 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - **Docs:** new FAQ/ClawHub/config examples/showcase entries and clarified auth, sandbox, and systemd docs. ### Maintenance + - Skills additions (Himalaya email, CodexBar, 1Password). -- Dependency refreshes (pi-* stack, Slack SDK, discord-api-types, file-type, zod, Biome, Vite). +- Dependency refreshes (pi-\* stack, Slack SDK, discord-api-types, file-type, zod, Biome, Vite). - Refactors: centralized group allowlist/mention policy; lint/import cleanup; switch tsx → bun for TS execution. ## 2026.1.5 ### Highlights + - Models: add image-specific model config (`agent.imageModel` + fallbacks) and scan support. - Agent tools: new `image` tool routed to the image model (when configured). - Config: default model shorthands (`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`). @@ -1224,6 +1289,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic - Bun: optional local install/build workflow without maintaining a Bun lockfile (see `docs/bun.md`). ### Fixes + - Control UI: render Markdown in tool result cards. - Control UI: prevent overlapping action buttons in Discord guild rules on narrow layouts. - Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60058f304..1bf0c961c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,7 @@ Welcome to the lobster tank! 🦞 ## Quick Links + - **GitHub:** https://github.com/openclaw/openclaw - **Discord:** https://discord.gg/qkhbAGHRBT - **X/Twitter:** [@steipete](https://x.com/steipete) / [@openclaw](https://x.com/openclaw) @@ -19,11 +20,13 @@ Welcome to the lobster tank! 🦞 - GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes) ## How to Contribute + 1. **Bugs & small fixes** → Open a PR! 2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first 3. **Questions** → Discord #setup-help ## Before You PR + - Test locally with your OpenClaw instance - Run tests: `pnpm tsgo && pnpm format && pnpm lint && pnpm build && pnpm test` - Keep PRs focused (one thing per PR) @@ -34,6 +37,7 @@ Welcome to the lobster tank! 🦞 Built with Codex, Claude, or other AI tools? **Awesome - just mark it!** Please include in your PR: + - [ ] Mark as AI-assisted in the PR title or description - [ ] Note the degree of testing (untested / lightly tested / fully tested) - [ ] Include prompts or session logs if possible (super helpful!) @@ -44,6 +48,7 @@ AI PRs are first-class citizens here. We just want transparency so reviewers kno ## Current Focus & Roadmap 🗺 We are currently prioritizing: + - **Stability**: Fixing edge cases in channel connections (WhatsApp/Telegram). - **UX**: Improving the onboarding wizard and error messages. - **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience. diff --git a/README.md b/README.md index 582695d07..d375461ec 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ MIT License

-**OpenClaw** is a *personal AI assistant* you run on your own devices. +**OpenClaw** is a _personal AI assistant_ you run on your own devices. It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant. If you want a personal, single-user assistant that feels local, fast, and always-on, this is it. @@ -30,6 +30,7 @@ Works with npm, pnpm, or bun. New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started) **Subscriptions (OAuth):** + - **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max) - **[OpenAI](https://openai.com/)** (ChatGPT/Codex) @@ -109,6 +110,7 @@ OpenClaw connects to real messaging surfaces. Treat inbound DMs as **untrusted i Full security guide: [Security](https://docs.openclaw.ai/gateway/security) Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack: + - **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message. - Approve with: `openclaw pairing approve ` (then the sender is added to a local allowlist store). - Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`). @@ -133,6 +135,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies. ## Everything we built so far ### Core platform + - [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui). - [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor). - [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming. @@ -140,16 +143,19 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies. - [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio). ### Channels + - [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [iMessage](https://docs.openclaw.ai/channels/imessage) (imsg), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat). - [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels). ### Apps + nodes + - [macOS app](https://docs.openclaw.ai/platforms/macos): menu bar control plane, [Voice Wake](https://docs.openclaw.ai/nodes/voicewake)/PTT, [Talk Mode](https://docs.openclaw.ai/nodes/talk) overlay, [WebChat](https://docs.openclaw.ai/web/webchat), debug tools, [remote gateway](https://docs.openclaw.ai/gateway/remote) control. - [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour pairing. - [Android node](https://docs.openclaw.ai/platforms/android): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, optional SMS. - [macOS node mode](https://docs.openclaw.ai/nodes): system.run/notify + canvas/camera exposure. ### Tools + automation + - [Browser control](https://docs.openclaw.ai/tools/browser): dedicated openclaw Chrome/Chromium, snapshots, actions, uploads, profiles. - [Canvas](https://docs.openclaw.ai/platforms/mac/canvas): [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot. - [Nodes](https://docs.openclaw.ai/nodes): camera snap/clip, screen record, [location.get](https://docs.openclaw.ai/nodes/location-command), notifications. @@ -157,12 +163,14 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies. - [Skills platform](https://docs.openclaw.ai/tools/skills): bundled, managed, and workspace skills with install gating + UI. ### Runtime + safety + - [Channel routing](https://docs.openclaw.ai/concepts/channel-routing), [retry policy](https://docs.openclaw.ai/concepts/retry), and [streaming/chunking](https://docs.openclaw.ai/concepts/streaming). - [Presence](https://docs.openclaw.ai/concepts/presence), [typing indicators](https://docs.openclaw.ai/concepts/typing-indicators), and [usage tracking](https://docs.openclaw.ai/concepts/usage-tracking). - [Models](https://docs.openclaw.ai/concepts/models), [model failover](https://docs.openclaw.ai/concepts/model-failover), and [session pruning](https://docs.openclaw.ai/concepts/session-pruning). - [Security](https://docs.openclaw.ai/gateway/security) and [troubleshooting](https://docs.openclaw.ai/channels/troubleshooting). ### Ops + packaging + - [Control UI](https://docs.openclaw.ai/web) + [WebChat](https://docs.openclaw.ai/web/webchat) served directly from the Gateway. - [Tailscale Serve/Funnel](https://docs.openclaw.ai/gateway/tailscale) or [SSH tunnels](https://docs.openclaw.ai/gateway/remote) with token/password auth. - [Nix mode](https://docs.openclaw.ai/install/nix) for declarative config; [Docker](https://docs.openclaw.ai/install/docker)-based installs. @@ -205,6 +213,7 @@ OpenClaw can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (pu - `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth). Notes: + - `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (OpenClaw enforces this). - Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`. - Funnel refuses to start unless `gateway.auth.mode: "password"` is set. @@ -218,7 +227,7 @@ It’s perfectly fine to run the Gateway on a small Linux instance. Clients (mac - **Gateway host** runs the exec tool and channel connections by default. - **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`. -In short: exec runs where the Gateway lives; device actions run where the device lives. + In short: exec runs where the Gateway lives; device actions run where the device lives. Details: [Remote access](https://docs.openclaw.ai/gateway/remote) · [Nodes](https://docs.openclaw.ai/nodes) · [Security](https://docs.openclaw.ai/gateway/security) @@ -237,7 +246,7 @@ Elevated bash (host permissions) is separate from macOS TCC: Details: [Nodes](https://docs.openclaw.ai/nodes) · [macOS app](https://docs.openclaw.ai/platforms/macos) · [Gateway protocol](https://docs.openclaw.ai/concepts/architecture) -## Agent to Agent (sessions_* tools) +## Agent to Agent (sessions\_\* tools) - Use these to coordinate work across sessions without jumping between chat surfaces. - `sessions_list` — discover active sessions (agents) and their metadata. @@ -307,8 +316,8 @@ Minimal `~/.openclaw/openclaw.json` (model + defaults): ```json5 { agent: { - model: "anthropic/claude-opus-4-5" - } + model: "anthropic/claude-opus-4-5", + }, } ``` @@ -337,9 +346,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker { channels: { telegram: { - botToken: "123456:ABCDEF" - } - } + botToken: "123456:ABCDEF", + }, + }, } ``` @@ -356,9 +365,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker { channels: { discord: { - token: "1234abcd" - } - } + token: "1234abcd", + }, + }, } ``` @@ -386,14 +395,15 @@ Browser control (optional): { browser: { enabled: true, - color: "#FF4500" - } + color: "#FF4500", + }, } ``` ## Docs Use these when you’re past the onboarding flow and want the deeper reference. + - [Start with the docs index for navigation and “what’s where.”](https://docs.openclaw.ai) - [Read the architecture overview for the gateway + protocol model.](https://docs.openclaw.ai/concepts/architecture) - [Use the full configuration reference when you need every key and example.](https://docs.openclaw.ai/gateway/configuration) diff --git a/docker-compose.yml b/docker-compose.yml index 8aaed5788..e1be94210 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,19 +12,19 @@ services: - ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw - ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace ports: - - '${OPENCLAW_GATEWAY_PORT:-18789}:18789' - - '${OPENCLAW_BRIDGE_PORT:-18790}:18790' + - "${OPENCLAW_GATEWAY_PORT:-18789}:18789" + - "${OPENCLAW_BRIDGE_PORT:-18790}:18790" init: true restart: unless-stopped command: [ - 'node', - 'dist/index.js', + "node", + "dist/index.js", "gateway", - '--bind', - '${OPENCLAW_GATEWAY_BIND:-lan}', - '--port', - '${OPENCLAW_GATEWAY_PORT:-18789}', + "--bind", + "${OPENCLAW_GATEWAY_BIND:-lan}", + "--port", + "${OPENCLAW_GATEWAY_PORT:-18789}", ] openclaw-cli: @@ -42,4 +42,4 @@ services: stdin_open: true tty: true init: true - entrypoint: ['node', 'dist/index.js'] + entrypoint: ["node", "dist/index.js"] diff --git a/docs.acp.md b/docs.acp.md index bdb63d29d..00950c9a5 100644 --- a/docs.acp.md +++ b/docs.acp.md @@ -84,9 +84,12 @@ To target a specific Gateway or agent: "command": "openclaw", "args": [ "acp", - "--url", "wss://gateway-host:18789", - "--token", "", - "--session", "agent:design:main" + "--url", + "wss://gateway-host:18789", + "--token", + "", + "--session", + "agent:design:main" ], "env": {} } @@ -112,7 +115,7 @@ By default each ACP session is mapped to a dedicated Gateway session key: You can override or reuse sessions in two ways: -1) CLI defaults +1. CLI defaults ```bash openclaw acp --session agent:main:main @@ -120,7 +123,7 @@ openclaw acp --session-label "support inbox" openclaw acp --reset-session ``` -2) ACP metadata per session +2. ACP metadata per session ```json { diff --git a/docs/automation/auth-monitoring.md b/docs/automation/auth-monitoring.md index ed33312b2..b5834923e 100644 --- a/docs/automation/auth-monitoring.md +++ b/docs/automation/auth-monitoring.md @@ -4,6 +4,7 @@ read_when: - Setting up auth expiry monitoring or alerts - Automating Claude Code / Codex OAuth refresh checks --- + # Auth monitoring OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for @@ -16,6 +17,7 @@ openclaw models status --check ``` Exit codes: + - `0`: OK - `1`: expired or missing credentials - `2`: expiring soon (within 24h) diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index ab2932e01..6f96feeb3 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -5,6 +5,7 @@ read_when: - Wiring automation that should run with or alongside heartbeats - Deciding between heartbeat and cron for scheduled tasks --- + # Cron jobs (Gateway scheduler) > **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each. @@ -12,10 +13,11 @@ read_when: Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at the right time, and can optionally deliver output back to a chat. -If you want *“run this every morning”* or *“poke the agent in 20 minutes”*, +If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_, cron is the mechanism. ## TL;DR + - Cron runs **inside the Gateway** (not inside the model). - Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules. - Two execution styles: @@ -24,18 +26,19 @@ cron is the mechanism. - Wakeups are first-class: a job can request “wake now” vs “next heartbeat”. ## Beginner-friendly overview + Think of a cron job as: **when** to run + **what** to do. -1) **Choose a schedule** +1. **Choose a schedule** - One-shot reminder → `schedule.kind = "at"` (CLI: `--at`) - Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"` - If your ISO timestamp omits a timezone, it is treated as **UTC**. -2) **Choose where it runs** +2. **Choose where it runs** - `sessionTarget: "main"` → run during the next heartbeat with main context. - `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:`. -3) **Choose the payload** +3. **Choose the payload** - Main session → `payload.kind = "systemEvent"` - Isolated session → `payload.kind = "agentTurn"` @@ -44,7 +47,9 @@ Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store ## Concepts ### Jobs + A cron job is a stored record with: + - a **schedule** (when it should run), - a **payload** (what it should do), - optional **delivery** (where output should be sent). @@ -56,7 +61,9 @@ In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibi Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`. ### Schedules + Cron supports three schedule kinds: + - `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC. - `every`: fixed interval (ms). - `cron`: 5-field cron expression with optional IANA timezone. @@ -67,6 +74,7 @@ local timezone is used. ### Main vs isolated execution #### Main session jobs (system events) + Main jobs enqueue a system event and optionally wake the heartbeat runner. They must use `payload.kind = "systemEvent"`. @@ -77,9 +85,11 @@ This is the best fit when you want the normal heartbeat prompt + main-session co See [Heartbeat](/gateway/heartbeat). #### Isolated jobs (dedicated cron sessions) + Isolated jobs run a dedicated agent turn in session `cron:`. Key behaviors: + - Prompt is prefixed with `[cron: ]` for traceability. - Each run starts a **fresh session id** (no prior conversation carry-over). - A summary is posted to the main session (prefix `Cron`, configurable). @@ -90,11 +100,14 @@ Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spa your main chat history. ### Payload shapes (what runs) + Two payload kinds are supported: + - `systemEvent`: main-session only, routed through the heartbeat prompt. - `agentTurn`: isolated-session only, runs a dedicated agent turn. Common `agentTurn` fields: + - `message`: required text prompt. - `model` / `thinking`: optional overrides (see below). - `timeoutSeconds`: optional timeout override. @@ -104,12 +117,15 @@ Common `agentTurn` fields: - `bestEffortDeliver`: avoid failing the job if delivery fails. Isolation options (only for `session=isolated`): + - `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main. - `postToMainMode`: `summary` (default) or `full`. - `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000). ### Model and thinking overrides + Isolated jobs (`agentTurn`) can override the model and thinking level: + - `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`) - `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only) @@ -118,12 +134,15 @@ session model. We recommend model overrides only for isolated jobs to avoid unexpected context shifts. Resolution priority: + 1. Job payload override (highest) 2. Hook-specific defaults (e.g., `hooks.gmail.model`) 3. Agent config default ### Delivery (channel + target) + Isolated jobs can deliver output to a channel. The job payload can specify: + - `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last` - `to`: channel-specific recipient target @@ -131,15 +150,18 @@ If `channel` or `to` is omitted, cron can fall back to the main session’s “l (the last place the agent replied). Delivery notes: + - If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted. - Use `deliver: true` when you want last-route delivery without an explicit `to`. - Use `deliver: false` to keep output internal even if a `to` is present. Target format reminders: + - Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:`, `user:`) to avoid ambiguity. - Telegram topics should use the `:topic:` form (see below). #### Telegram delivery targets (topics / forum threads) + Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode the topic/thread into the `to` field: @@ -148,9 +170,11 @@ the topic/thread into the `to` field: - `-1001234567890:123` (shorthand: numeric suffix) Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted: + - `telegram:group:-1001234567890:topic:123` ## Storage & history + - Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON). - Run history: `~/.openclaw/cron/runs/.jsonl` (JSONL, auto-pruned). - Override store path: `cron.store` in config. @@ -162,18 +186,20 @@ Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted: cron: { enabled: true, // default true store: "~/.openclaw/cron/jobs.json", - maxConcurrentRuns: 1 // default 1 - } + maxConcurrentRuns: 1, // default 1 + }, } ``` Disable cron entirely: + - `cron.enabled: false` (config) - `OPENCLAW_SKIP_CRON=1` (env) ## CLI quickstart One-shot reminder (UTC ISO, auto-delete after success): + ```bash openclaw cron add \ --name "Send reminder" \ @@ -185,6 +211,7 @@ openclaw cron add \ ``` One-shot reminder (main session, wake immediately): + ```bash openclaw cron add \ --name "Calendar check" \ @@ -195,6 +222,7 @@ openclaw cron add \ ``` Recurring isolated job (deliver to WhatsApp): + ```bash openclaw cron add \ --name "Morning status" \ @@ -208,6 +236,7 @@ openclaw cron add \ ``` Recurring isolated job (deliver to a Telegram topic): + ```bash openclaw cron add \ --name "Nightly summary (topic)" \ @@ -221,7 +250,8 @@ openclaw cron add \ ``` Isolated job with model and thinking override: -```bash + +````bash openclaw cron add \ --name "Deep analysis" \ --cron "0 6 * * 1" \ @@ -242,15 +272,17 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes # Switch or clear the agent on an existing job openclaw cron edit --agent ops openclaw cron edit --clear-agent -``` -``` +```` + +```` Manual run (debug): ```bash openclaw cron run --force -``` +```` Edit an existing job (patch fields): + ```bash openclaw cron edit \ --message "Updated prompt" \ @@ -259,28 +291,33 @@ openclaw cron edit \ ``` Run history: + ```bash openclaw cron runs --id --limit 50 ``` Immediate system event without creating a job: + ```bash openclaw system event --mode now --text "Next heartbeat: check battery." ``` ## Gateway API surface + - `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove` - `cron.run` (force or due), `cron.runs` -For immediate system events without a job, use [`openclaw system event`](/cli/system). + For immediate system events without a job, use [`openclaw system event`](/cli/system). ## Troubleshooting ### “Nothing runs” + - Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`. - Check the Gateway is running continuously (cron runs inside the Gateway process). - For `cron` schedules: confirm timezone (`--tz`) vs the host timezone. ### Telegram delivers to the wrong place + - For forum topics, use `-100…:topic:` so it’s explicit and unambiguous. - If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal; cron delivery accepts them and still parses topic IDs correctly. diff --git a/docs/automation/cron-vs-heartbeat.md b/docs/automation/cron-vs-heartbeat.md index 01c34e389..f01e03129 100644 --- a/docs/automation/cron-vs-heartbeat.md +++ b/docs/automation/cron-vs-heartbeat.md @@ -5,20 +5,21 @@ read_when: - Setting up background monitoring or notifications - Optimizing token usage for periodic checks --- + # Cron vs Heartbeat: When to Use Each Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case. ## Quick Decision Guide -| Use Case | Recommended | Why | -|----------|-------------|-----| -| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware | -| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed | -| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness | -| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model | -| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing | -| Background project health check | Heartbeat | Piggybacks on existing cycle | +| Use Case | Recommended | Why | +| ------------------------------------ | ------------------- | ---------------------------------------- | +| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware | +| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed | +| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness | +| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model | +| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing | +| Background project health check | Heartbeat | Piggybacks on existing cycle | ## Heartbeat: Periodic Awareness @@ -59,12 +60,12 @@ The agent reads this on each heartbeat and handles all items in one turn. agents: { defaults: { heartbeat: { - every: "30m", // interval - target: "last", // where to deliver alerts - activeHours: { start: "08:00", end: "22:00" } // optional - } - } - } + every: "30m", // interval + target: "last", // where to deliver alerts + activeHours: { start: "08:00", end: "22:00" }, // optional + }, + }, + }, } ``` @@ -157,8 +158,10 @@ The most efficient setup uses **both**: ### Example: Efficient automation setup **HEARTBEAT.md** (checked every 30 min): + ```md # Heartbeat checklist + - Scan inbox for urgent emails - Check calendar for events in next 2h - Review any pending tasks @@ -166,6 +169,7 @@ The most efficient setup uses **both**: ``` **Cron jobs** (precise timing): + ```bash # Daily morning briefing at 7am openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver @@ -177,7 +181,6 @@ openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated - openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now ``` - ## Lobster: Deterministic workflows with approvals Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals. @@ -191,8 +194,8 @@ Use it when the task is more than a single agent turn, and you want a resumable ### How it pairs with heartbeat and cron -- **Heartbeat/cron** decide *when* a run happens. -- **Lobster** defines *what steps* happen once the run starts. +- **Heartbeat/cron** decide _when_ a run happens. +- **Lobster** defines _what steps_ happen once the run starts. For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster. For ad-hoc workflows, call Lobster directly. @@ -210,17 +213,18 @@ See [Lobster](/tools/lobster) for full usage and examples. Both heartbeat and cron can interact with the main session, but differently: -| | Heartbeat | Cron (main) | Cron (isolated) | -|---|---|---|---| -| Session | Main | Main (via system event) | `cron:` | -| History | Shared | Shared | Fresh each run | -| Context | Full | Full | None (starts clean) | -| Model | Main session model | Main session model | Can override | -| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main | +| | Heartbeat | Cron (main) | Cron (isolated) | +| ------- | ------------------------------- | ------------------------ | ---------------------- | +| Session | Main | Main (via system event) | `cron:` | +| History | Shared | Shared | Fresh each run | +| Context | Full | Full | None (starts clean) | +| Model | Main session model | Main session model | Can override | +| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main | ### When to use main session cron Use `--session main` with `--system-event` when you want: + - The reminder/event to appear in main session context - The agent to handle it during the next heartbeat with full context - No separate isolated run @@ -237,6 +241,7 @@ openclaw cron add \ ### When to use isolated cron Use `--session isolated` when you want: + - A clean slate without prior context - Different model or thinking settings - Output delivered directly to a channel (summary still posts to main by default) @@ -255,13 +260,14 @@ openclaw cron add \ ## Cost Considerations -| Mechanism | Cost Profile | -|-----------|--------------| -| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size | -| Cron (main) | Adds event to next heartbeat (no isolated turn) | -| Cron (isolated) | Full agent turn per job; can use cheaper model | +| Mechanism | Cost Profile | +| --------------- | ------------------------------------------------------- | +| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size | +| Cron (main) | Adds event to next heartbeat (no isolated turn) | +| Cron (isolated) | Full agent turn per job; can use cheaper model | **Tips**: + - Keep `HEARTBEAT.md` small to minimize token overhead. - Batch similar checks into heartbeat instead of multiple cron jobs. - Use `target: "none"` on heartbeat if you only want internal processing. diff --git a/docs/automation/gmail-pubsub.md b/docs/automation/gmail-pubsub.md index 3998fd4fe..fe4a799be 100644 --- a/docs/automation/gmail-pubsub.md +++ b/docs/automation/gmail-pubsub.md @@ -26,8 +26,8 @@ Example hook config (enable Gmail preset mapping): enabled: true, token: "OPENCLAW_HOOK_TOKEN", path: "/hooks", - presets: ["gmail"] - } + presets: ["gmail"], + }, } ``` @@ -47,15 +47,14 @@ that sets `deliver` + optional `channel`/`to`: wakeMode: "now", name: "Gmail", sessionKey: "hook:gmail:{{messages[0].id}}", - messageTemplate: - "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}", + messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}", model: "openai/gpt-5.2-mini", deliver: true, - channel: "last" + channel: "last", // to: "+15551234567" - } - ] - } + }, + ], + }, } ``` @@ -73,13 +72,14 @@ To set a default model and thinking level specifically for Gmail hooks, add hooks: { gmail: { model: "openrouter/meta-llama/llama-3.3-70b-instruct:free", - thinking: "off" - } - } + thinking: "off", + }, + }, } ``` Notes: + - Per-hook `model`/`thinking` in the mapping still overrides these defaults. - Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts). - If `agents.defaults.models` is set, the Gmail model must be in the allowlist. @@ -99,6 +99,7 @@ openclaw webhooks gmail setup \ ``` Defaults: + - Uses Tailscale Funnel for the public push endpoint. - Writes `hooks.gmail` config for `openclaw webhooks gmail run`. - Enables the Gmail hook preset (`hooks.presets: ["gmail"]`). @@ -117,6 +118,7 @@ Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale` via Homebrew; on Linux install them manually first. Gateway auto-start (recommended): + - When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts `gog gmail watch serve` on boot and auto-renews the watch. - Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself). @@ -131,7 +133,7 @@ openclaw webhooks gmail run ## One-time setup -1) Select the GCP project **that owns the OAuth client** used by `gog`. +1. Select the GCP project **that owns the OAuth client** used by `gog`. ```bash gcloud auth login @@ -140,19 +142,19 @@ gcloud config set project Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client. -2) Enable APIs: +2. Enable APIs: ```bash gcloud services enable gmail.googleapis.com pubsub.googleapis.com ``` -3) Create a topic: +3. Create a topic: ```bash gcloud pubsub topics create gog-gmail-watch ``` -4) Allow Gmail push to publish: +4. Allow Gmail push to publish: ```bash gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \ @@ -189,6 +191,7 @@ gog gmail watch serve \ ``` Notes: + - `--token` protects the push endpoint (`x-gog-token` or `?token=`). - `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main). - `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw. diff --git a/docs/automation/poll.md b/docs/automation/poll.md index f7a37af86..e8f541ced 100644 --- a/docs/automation/poll.md +++ b/docs/automation/poll.md @@ -4,10 +4,11 @@ read_when: - Adding or modifying poll support - Debugging poll sends from the CLI or gateway --- + # Polls - ## Supported channels + - WhatsApp (web channel) - Discord - MS Teams (Adaptive Cards) @@ -33,6 +34,7 @@ openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv ``` Options: + - `--channel`: `whatsapp` (default), `discord`, or `msteams` - `--poll-multi`: allow selecting multiple options - `--poll-duration-hours`: Discord-only (defaults to 24 when omitted) @@ -42,6 +44,7 @@ Options: Method: `poll` Params: + - `to` (string, required) - `question` (string, required) - `options` (string[], required) @@ -51,11 +54,13 @@ Params: - `idempotencyKey` (string, required) ## Channel differences + - WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`. - Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count. - MS Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored. ## Agent tool (Message) + Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`). Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select. diff --git a/docs/automation/webhook.md b/docs/automation/webhook.md index 7926b9d40..6fb823030 100644 --- a/docs/automation/webhook.md +++ b/docs/automation/webhook.md @@ -16,18 +16,20 @@ Gateway can expose a small HTTP webhook endpoint for external triggers. hooks: { enabled: true, token: "shared-secret", - path: "/hooks" - } + path: "/hooks", + }, } ``` Notes: + - `hooks.token` is required when `hooks.enabled=true`. - `hooks.path` defaults to `/hooks`. ## Auth Every request must include the hook token. Prefer headers: + - `Authorization: Bearer ` (recommended) - `x-openclaw-token: ` - `?token=` (deprecated; logs a warning and will be removed in a future major release) @@ -37,6 +39,7 @@ Every request must include the hook token. Prefer headers: ### `POST /hooks/wake` Payload: + ```json { "text": "System line", "mode": "now" } ``` @@ -45,12 +48,14 @@ Payload: - `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check. Effect: + - Enqueues a system event for the **main** session - If `mode=now`, triggers an immediate heartbeat ### `POST /hooks/agent` Payload: + ```json { "message": "Run this", @@ -78,6 +83,7 @@ Payload: - `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds. Effect: + - Runs an **isolated** agent turn (own session key) - Always posts a summary into the **main** session - If `wakeMode=now`, triggers an immediate heartbeat @@ -89,6 +95,7 @@ turn arbitrary payloads into `wake` or `agent` actions, with optional templates code transforms. Mapping options (summary): + - `hooks.presets: ["gmail"]` enables the built-in Gmail mapping. - `hooks.mappings` lets you define `match`, `action`, and templates in config. - `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic. @@ -99,7 +106,7 @@ Mapping options (summary): - `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook (dangerous; only for trusted internal sources). - `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`. -See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow. + See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow. ## Responses diff --git a/docs/bedrock.md b/docs/bedrock.md index 251050ca4..78e3225f3 100644 --- a/docs/bedrock.md +++ b/docs/bedrock.md @@ -4,6 +4,7 @@ read_when: - You want to use Amazon Bedrock models with OpenClaw - You need AWS credential/region setup for model calls --- + # Amazon Bedrock OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse** @@ -34,13 +35,14 @@ Config options live under `models.bedrockDiscovery`: providerFilter: ["anthropic", "amazon"], refreshInterval: 3600, defaultContextWindow: 32000, - defaultMaxTokens: 4096 - } - } + defaultMaxTokens: 4096, + }, + }, } ``` Notes: + - `enabled` defaults to `true` when AWS credentials are present. - `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`. - `providerFilter` matches Bedrock provider names (for example `anthropic`). @@ -50,7 +52,7 @@ Notes: ## Setup (manual) -1) Ensure AWS credentials are available on the **gateway host**: +1. Ensure AWS credentials are available on the **gateway host**: ```bash export AWS_ACCESS_KEY_ID="AKIA..." @@ -63,7 +65,7 @@ export AWS_PROFILE="your-profile" export AWS_BEARER_TOKEN_BEDROCK="..." ``` -2) Add a Bedrock provider and model to your config (no `apiKey` required): +2. Add a Bedrock provider and model to your config (no `apiKey` required): ```json5 { @@ -81,17 +83,17 @@ export AWS_BEARER_TOKEN_BEDROCK="..." input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, contextWindow: 200000, - maxTokens: 8192 - } - ] - } - } + maxTokens: 8192, + }, + ], + }, + }, }, agents: { defaults: { - model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" } - } - } + model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" }, + }, + }, } ``` @@ -112,6 +114,7 @@ export AWS_REGION=us-east-1 ``` **Required IAM permissions** for the EC2 instance role: + - `bedrock:InvokeModel` - `bedrock:InvokeModelWithResponseStream` - `bedrock:ListFoundationModels` (for automatic discovery) diff --git a/docs/brave-search.md b/docs/brave-search.md index e60116a84..045366547 100644 --- a/docs/brave-search.md +++ b/docs/brave-search.md @@ -11,9 +11,9 @@ OpenClaw uses Brave Search as the default provider for `web_search`. ## Get an API key -1) Create a Brave Search API account at https://brave.com/search/api/ -2) In the dashboard, choose the **Data for Search** plan and generate an API key. -3) Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment. +1. Create a Brave Search API account at https://brave.com/search/api/ +2. In the dashboard, choose the **Data for Search** plan and generate an API key. +3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment. ## Config example @@ -25,10 +25,10 @@ OpenClaw uses Brave Search as the default provider for `web_search`. provider: "brave", apiKey: "BRAVE_API_KEY_HERE", maxResults: 5, - timeoutSeconds: 30 - } - } - } + timeoutSeconds: 30, + }, + }, + }, } ``` diff --git a/docs/broadcast-groups.md b/docs/broadcast-groups.md index 0b5b6df16..de0e03499 100644 --- a/docs/broadcast-groups.md +++ b/docs/broadcast-groups.md @@ -22,7 +22,9 @@ Broadcast groups are evaluated after channel allowlists and group activation rul ## Use Cases ### 1. Specialized Agent Teams + Deploy multiple agents with atomic, focused responsibilities: + ``` Group: "Development Team" Agents: @@ -35,6 +37,7 @@ Agents: Each agent processes the same message and provides its specialized perspective. ### 2. Multi-Language Support + ``` Group: "International Support" Agents: @@ -44,6 +47,7 @@ Agents: ``` ### 3. Quality Assurance Workflows + ``` Group: "Customer Support" Agents: @@ -52,6 +56,7 @@ Agents: ``` ### 4. Task Automation + ``` Group: "Project Management" Agents: @@ -65,6 +70,7 @@ Agents: ### Basic Setup Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids: + - group chats: group JID (e.g. `120363403215116621@g.us`) - DMs: E.164 phone number (e.g. `+15551234567`) @@ -83,7 +89,9 @@ Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer Control how agents process messages: #### Parallel (Default) + All agents process simultaneously: + ```json { "broadcast": { @@ -94,7 +102,9 @@ All agents process simultaneously: ``` #### Sequential + Agents process in order (one waits for previous to finish): + ```json { "broadcast": { @@ -152,7 +162,7 @@ Agents process in order (one waits for previous to finish): 4. **If not in broadcast list**: - Normal routing applies (first matching binding) -Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change *which agents run* when a message is eligible for processing. +Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing. ### Session Isolation @@ -166,6 +176,7 @@ Each agent in a broadcast group maintains completely separate: - **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered This allows each agent to have: + - Different personalities - Different tool access (e.g., read-only vs. read-write) - Different models (e.g., opus vs. sonnet) @@ -176,6 +187,7 @@ This allows each agent to have: In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`: **Alfred's context:** + ``` Session: agent:alfred:whatsapp:group:120363403215116621@g.us History: [user message, alfred's previous responses] @@ -184,8 +196,9 @@ Tools: read, write, exec ``` **Bärbel's context:** + ``` -Session: agent:baerbel:whatsapp:group:120363403215116621@g.us +Session: agent:baerbel:whatsapp:group:120363403215116621@g.us History: [user message, baerbel's previous responses] Workspace: /Users/pascal/openclaw-baerbel/ Tools: read only @@ -230,10 +243,10 @@ Give agents only the tools they need: { "agents": { "reviewer": { - "tools": { "allow": ["read", "exec"] } // Read-only + "tools": { "allow": ["read", "exec"] } // Read-only }, "fixer": { - "tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write + "tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write } } } @@ -242,6 +255,7 @@ Give agents only the tools they need: ### 4. Monitor Performance With many agents, consider: + - Using `"strategy": "parallel"` (default) for speed - Limiting broadcast groups to 5-10 agents - Using faster models for simpler agents @@ -260,6 +274,7 @@ Result: Agent A and C respond, Agent B logs error ### Providers Broadcast groups currently work with: + - ✅ WhatsApp (implemented) - 🚧 Telegram (planned) - 🚧 Discord (planned) @@ -272,7 +287,10 @@ Broadcast groups work alongside existing routing: ```json { "bindings": [ - { "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, "agentId": "alfred" } + { + "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, + "agentId": "alfred" + } ], "broadcast": { "GROUP_B": ["agent1", "agent2"] @@ -290,11 +308,13 @@ Broadcast groups work alongside existing routing: ### Agents Not Responding **Check:** + 1. Agent IDs exist in `agents.list` 2. Peer ID format is correct (e.g., `120363403215116621@g.us`) 3. Agents are not in deny lists **Debug:** + ```bash tail -f ~/.openclaw/logs/gateway.log | grep broadcast ``` @@ -308,6 +328,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast ### Performance Issues **If slow with many agents:** + - Reduce number of agents per group - Use lighter models (sonnet instead of opus) - Check sandbox startup time @@ -329,9 +350,21 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast }, "agents": { "list": [ - { "id": "code-formatter", "workspace": "~/agents/formatter", "tools": { "allow": ["read", "write"] } }, - { "id": "security-scanner", "workspace": "~/agents/security", "tools": { "allow": ["read", "exec"] } }, - { "id": "test-coverage", "workspace": "~/agents/testing", "tools": { "allow": ["read", "exec"] } }, + { + "id": "code-formatter", + "workspace": "~/agents/formatter", + "tools": { "allow": ["read", "write"] } + }, + { + "id": "security-scanner", + "workspace": "~/agents/security", + "tools": { "allow": ["read", "exec"] } + }, + { + "id": "test-coverage", + "workspace": "~/agents/testing", + "tools": { "allow": ["read", "exec"] } + }, { "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } } ] } @@ -340,6 +373,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast **User sends:** Code snippet **Responses:** + - code-formatter: "Fixed indentation and added type hints" - security-scanner: "⚠️ SQL injection vulnerability in line 12" - test-coverage: "Coverage is 45%, missing tests for error cases" @@ -381,7 +415,6 @@ interface OpenClawConfig { - `strategy` (optional): How to process agents - `"parallel"` (default): All agents process simultaneously - `"sequential"`: Agents process in array order - - `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID - Value: Array of agent IDs that should process messages @@ -395,6 +428,7 @@ interface OpenClawConfig { ## Future Enhancements Planned features: + - [ ] Shared context mode (agents see each other's responses) - [ ] Agent coordination (agents can signal each other) - [ ] Dynamic agent selection (choose agents based on message content) diff --git a/docs/channels/bluebubbles.md b/docs/channels/bluebubbles.md index 31db99d81..2033db10e 100644 --- a/docs/channels/bluebubbles.md +++ b/docs/channels/bluebubbles.md @@ -5,11 +5,13 @@ read_when: - Troubleshooting webhook pairing - Configuring iMessage on macOS --- + # BlueBubbles (macOS REST) Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel. ## Overview + - Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)). - Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync. - OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`). @@ -20,6 +22,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R - Advanced features: edit, unsend, reply threading, message effects, group management. ## Quick start + 1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)). 2. In the BlueBubbles config, enable the web API and set a password. 3. Run `openclaw onboard` and select BlueBubbles, or configure manually: @@ -30,21 +33,24 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R enabled: true, serverUrl: "http://192.168.1.100:1234", password: "example-password", - webhookPath: "/bluebubbles-webhook" - } - } + webhookPath: "/bluebubbles-webhook", + }, + }, } ``` 4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=`). 5. Start the gateway; it will register the webhook handler and start pairing. ## Onboarding + BlueBubbles is available in the interactive setup wizard: + ``` openclaw onboard ``` The wizard prompts for: + - **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`) - **Password** (required): API password from BlueBubbles Server settings - **Webhook path** (optional): Defaults to `/bluebubbles-webhook` @@ -52,12 +58,15 @@ The wizard prompts for: - **Allow list**: Phone numbers, emails, or chat targets You can also add BlueBubbles via CLI: + ``` openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password ``` ## Access control (DMs + groups) + DMs: + - Default: `channels.bluebubbles.dmPolicy = "pairing"`. - Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: @@ -66,16 +75,20 @@ DMs: - Pairing is the default token exchange. Details: [Pairing](/start/pairing) Groups: + - `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`). - `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. ### Mention gating (groups) + BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior: + - Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions. - When `requireMention` is enabled for a group, the agent only responds when mentioned. - Control commands from authorized senders bypass mention gating. Per-group configuration: + ```json5 { channels: { @@ -83,20 +96,22 @@ Per-group configuration: groupPolicy: "allowlist", groupAllowFrom: ["+15555550123"], groups: { - "*": { requireMention: true }, // default for all groups - "iMessage;-;chat123": { requireMention: false } // override for specific group - } - } - } + "*": { requireMention: true }, // default for all groups + "iMessage;-;chat123": { requireMention: false }, // override for specific group + }, + }, + }, } ``` ### Command gating + - Control commands (e.g., `/config`, `/model`) require authorization. - Uses `allowFrom` and `groupAllowFrom` to determine command authorization. - Authorized senders can run control commands even without mentioning in groups. ## Typing + read receipts + - **Typing indicators**: Sent automatically before and during response generation. - **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`). - **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable). @@ -105,13 +120,14 @@ Per-group configuration: { channels: { bluebubbles: { - sendReadReceipts: false // disable read receipts - } - } + sendReadReceipts: false, // disable read receipts + }, + }, } ``` ## Advanced actions + BlueBubbles supports advanced message actions when enabled in config: ```json5 @@ -119,24 +135,25 @@ BlueBubbles supports advanced message actions when enabled in config: channels: { bluebubbles: { actions: { - reactions: true, // tapbacks (default: true) - edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe) - unsend: true, // unsend messages (macOS 13+) - reply: true, // reply threading by message GUID - sendWithEffect: true, // message effects (slam, loud, etc.) - renameGroup: true, // rename group chats - setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe) - addParticipant: true, // add participants to groups + reactions: true, // tapbacks (default: true) + edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe) + unsend: true, // unsend messages (macOS 13+) + reply: true, // reply threading by message GUID + sendWithEffect: true, // message effects (slam, loud, etc.) + renameGroup: true, // rename group chats + setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe) + addParticipant: true, // add participants to groups removeParticipant: true, // remove participants from groups - leaveGroup: true, // leave group chats - sendAttachment: true // send attachments/media - } - } - } + leaveGroup: true, // leave group chats + sendAttachment: true, // send attachments/media + }, + }, + }, } ``` Available actions: + - **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`) - **edit**: Edit a sent message (`messageId`, `text`) - **unsend**: Unsend a message (`messageId`) @@ -151,39 +168,47 @@ Available actions: - Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos. ### Message IDs (short vs full) -OpenClaw may surface *short* message IDs (e.g., `1`, `2`) to save tokens. + +OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens. + - `MessageSid` / `ReplyToId` can be short IDs. - `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs. - Short IDs are in-memory; they can expire on restart or cache eviction. - Actions accept short or full `messageId`, but short IDs will error if no longer available. Use full IDs for durable automations and storage: + - Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}` - Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads See [Configuration](/gateway/configuration) for template variables. ## Block streaming + Control whether responses are sent as a single message or streamed in blocks: + ```json5 { channels: { bluebubbles: { - blockStreaming: true // enable block streaming (default behavior) - } - } + blockStreaming: true, // enable block streaming (default behavior) + }, + }, } ``` ## Media + limits + - Inbound attachments are downloaded and stored in the media cache. - Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB). - Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars). ## Configuration reference + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.bluebubbles.enabled`: Enable/disable the channel. - `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL. - `channels.bluebubbles.password`: API password. @@ -204,11 +229,14 @@ Provider options: - `channels.bluebubbles.accounts`: Multi-account configuration. Related global options: + - `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`). - `messages.responsePrefix`. ## Addressing / delivery targets + Prefer `chat_guid` for stable routing: + - `chat_guid:iMessage;-;+15555550123` (preferred for groups) - `chat_id:123` - `chat_identifier:...` @@ -216,12 +244,14 @@ Prefer `chat_guid` for stable routing: - If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled. ## Security + - Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted. - Keep the API password and webhook endpoint secret (treat them like credentials). - Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. See [Gateway security](/gateway/security#reverse-proxy-configuration). - Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN. ## Troubleshooting + - If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`. - Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles `. - Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it. diff --git a/docs/channels/discord.md b/docs/channels/discord.md index e3a73d8f6..2092c0c17 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -3,41 +3,45 @@ summary: "Discord bot support status, capabilities, and configuration" read_when: - Working on Discord channel features --- -# Discord (Bot API) +# Discord (Bot API) Status: ready for DM and guild text channels via the official Discord bot gateway. ## Quick setup (beginner) -1) Create a Discord bot and copy the bot token. -2) In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups). -3) Set the token for OpenClaw: + +1. Create a Discord bot and copy the bot token. +2. In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups). +3. Set the token for OpenClaw: - Env: `DISCORD_BOT_TOKEN=...` - Or config: `channels.discord.token: "..."`. - If both are set, config takes precedence (env fallback is default-account only). -4) Invite the bot to your server with message permissions (create a private server if you just want DMs). -5) Start the gateway. -6) DM access is pairing by default; approve the pairing code on first contact. +4. Invite the bot to your server with message permissions (create a private server if you just want DMs). +5. Start the gateway. +6. DM access is pairing by default; approve the pairing code on first contact. Minimal config: + ```json5 { channels: { discord: { enabled: true, - token: "YOUR_BOT_TOKEN" - } - } + token: "YOUR_BOT_TOKEN", + }, + }, } ``` ## Goals + - Talk to OpenClaw via Discord DMs or guild channels. - Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent::discord:channel:` (display names use `discord:#`). - Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`. - Keep routing deterministic: replies always go back to the channel they arrived on. ## How it works + 1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token. 2. Invite the bot to your server with the permissions required to read/send messages where you want to use it. 3. Configure OpenClaw with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback). @@ -64,12 +68,14 @@ Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy. ## Config writes + By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with: + ```json5 { - channels: { discord: { configWrites: false } } + channels: { discord: { configWrites: false } }, } ``` @@ -78,28 +84,34 @@ Disable with: This is the “Discord Developer Portal” setup for running OpenClaw in a server (guild) channel like `#help`. ### 1) Create the Discord app + bot user + 1. Discord Developer Portal → **Applications** → **New Application** 2. In your app: - **Bot** → **Add Bot** - Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`) ### 2) Enable the gateway intents OpenClaw needs + Discord blocks “privileged intents” unless you explicitly enable them. In **Bot** → **Privileged Gateway Intents**, enable: + - **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages) - **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds) You usually do **not** need **Presence Intent**. ### 3) Generate an invite URL (OAuth2 URL Generator) + In your app: **OAuth2** → **URL Generator** **Scopes** + - ✅ `bot` - ✅ `applications.commands` (required for native commands) **Bot Permissions** (minimal baseline) + - ✅ View Channels - ✅ Send Messages - ✅ Read Message History @@ -113,6 +125,7 @@ Avoid **Administrator** unless you’re debugging and fully trust the bot. Copy the generated URL, open it, pick your server, and install the bot. ### 4) Get the ids (guild/user/channel) + Discord uses numeric ids everywhere; OpenClaw config prefers ids. 1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode** @@ -124,7 +137,9 @@ Discord uses numeric ids everywhere; OpenClaw config prefers ids. ### 5) Configure OpenClaw #### Token + Set the bot token via env var (recommended on servers): + - `DISCORD_BOT_TOKEN=...` Or via config: @@ -134,15 +149,16 @@ Or via config: channels: { discord: { enabled: true, - token: "YOUR_BOT_TOKEN" - } - } + token: "YOUR_BOT_TOKEN", + }, + }, } ``` Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. #### Allowlist + channel routing + Example “single server, only allow me, only allow #help”: ```json5 @@ -152,26 +168,27 @@ Example “single server, only allow me, only allow #help”: enabled: true, dm: { enabled: false }, guilds: { - "YOUR_GUILD_ID": { + YOUR_GUILD_ID: { users: ["YOUR_USER_ID"], requireMention: true, channels: { - help: { allow: true, requireMention: true } - } - } + help: { allow: true, requireMention: true }, + }, + }, }, retry: { attempts: 3, minDelayMs: 500, maxDelayMs: 30000, - jitter: 0.1 - } - } - } + jitter: 0.1, + }, + }, + }, } ``` Notes: + - `requireMention: true` means the bot only replies when mentioned (recommended for shared channels). - `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages. - Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`. @@ -182,11 +199,13 @@ Notes: - Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels..users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`. ### 6) Verify it works + 1. Start the gateway. 2. In your server channel, send: `@Krill hello` (or whatever your bot name is). 3. If nothing happens: check **Troubleshooting** below. ### Troubleshooting + - First: run `openclaw doctor` and `openclaw channels status --probe` (actionable warnings + quick audits). - **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway. - **Bot connects but never replies in a guild channel**: @@ -204,6 +223,7 @@ Notes: - **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`). ## Capabilities & limits + - DMs and guild text channels (threads are treated as separate channels; voice not supported). - Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17). - Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. @@ -213,6 +233,7 @@ Notes: - Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags. ## Retry policy + Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry). ## Config @@ -227,9 +248,9 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after guilds: { "*": { channels: { - general: { allow: true } - } - } + general: { allow: true }, + }, + }, }, mediaMaxMb: 8, actions: { @@ -250,7 +271,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after channels: true, voiceStatus: true, events: true, - moderation: false + moderation: false, }, replyToMode: "off", dm: { @@ -258,7 +279,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after policy: "pairing", // pairing | allowlist | open | disabled allowFrom: ["123456789012345678", "steipete"], groupEnabled: false, - groupChannels: ["openclaw-dm"] + groupChannels: ["openclaw-dm"], }, guilds: { "*": { requireMention: true }, @@ -274,13 +295,13 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after requireMention: true, users: ["987654321098765432"], skills: ["search", "docs"], - systemPrompt: "Keep answers short." - } - } - } - } - } - } + systemPrompt: "Keep answers short.", + }, + }, + }, + }, + }, + }, } ``` @@ -327,6 +348,7 @@ ack reaction after the bot replies. - `moderation` (timeout/kick/ban, default `false`) Reaction notifications use `guilds..reactionNotifications`: + - `off`: no reaction events. - `own`: reactions on the bot's own messages (default). - `all`: all reactions on all messages. @@ -334,40 +356,45 @@ Reaction notifications use `guilds..reactionNotifications`: ### Tool action defaults -| Action group | Default | Notes | -| --- | --- | --- | -| reactions | enabled | React + list reactions + emojiList | -| stickers | enabled | Send stickers | -| emojiUploads | enabled | Upload emojis | -| stickerUploads | enabled | Upload stickers | -| polls | enabled | Create polls | -| permissions | enabled | Channel permission snapshot | -| messages | enabled | Read/send/edit/delete | -| threads | enabled | Create/list/reply | -| pins | enabled | Pin/unpin/list | -| search | enabled | Message search (preview feature) | -| memberInfo | enabled | Member info | -| roleInfo | enabled | Role list | -| channelInfo | enabled | Channel info + list | -| channels | enabled | Channel/category management | -| voiceStatus | enabled | Voice state lookup | -| events | enabled | List/create scheduled events | -| roles | disabled | Role add/remove | -| moderation | disabled | Timeout/kick/ban | +| Action group | Default | Notes | +| -------------- | -------- | ---------------------------------- | +| reactions | enabled | React + list reactions + emojiList | +| stickers | enabled | Send stickers | +| emojiUploads | enabled | Upload emojis | +| stickerUploads | enabled | Upload stickers | +| polls | enabled | Create polls | +| permissions | enabled | Channel permission snapshot | +| messages | enabled | Read/send/edit/delete | +| threads | enabled | Create/list/reply | +| pins | enabled | Pin/unpin/list | +| search | enabled | Message search (preview feature) | +| memberInfo | enabled | Member info | +| roleInfo | enabled | Role list | +| channelInfo | enabled | Channel info + list | +| channels | enabled | Channel/category management | +| voiceStatus | enabled | Voice state lookup | +| events | enabled | List/create scheduled events | +| roles | disabled | Role add/remove | +| moderation | disabled | Timeout/kick/ban | + - `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag. ## Reply tags + To request a threaded reply, the model can include one tag in its output: + - `[[reply_to_current]]` — reply to the triggering Discord message. - `[[reply_to:]]` — reply to a specific message id from context/history. -Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids. + Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids. Behavior is controlled by `channels.discord.replyToMode`: + - `off`: ignore tags. - `first`: only the first outbound chunk/attachment is a reply. - `all`: every outbound chunk/attachment is a reply. Allowlist matching notes: + - `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`. - Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported. - Use `*` to allow any sender/channel. @@ -379,12 +406,15 @@ Allowlist matching notes: and logs the mapping; unresolved entries are kept as typed. Native command notes: + - The registered commands mirror OpenClaw’s chat commands. - Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules). - Slash commands may still be visible in Discord UI to users who aren’t allowlisted; OpenClaw enforces allowlists on execution and replies “not authorized”. ## Tool actions + The agent can call `discord` with actions like: + - `react` / `reactions` (add or list reactions) - `sticker`, `poll`, `permissions` - `readMessages`, `sendMessage`, `editMessage`, `deleteMessage` @@ -399,6 +429,7 @@ Discord message ids are surfaced in the injected context (`[discord message id: Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`. ## Safety & ops + - Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions. - Only grant the bot permissions it needs (typically Read/Send Messages). - If the bot is stuck or rate limited, restart the gateway (`openclaw gateway --force`) after confirming no other processes own the Discord session. diff --git a/docs/channels/googlechat.md b/docs/channels/googlechat.md index 3e721503a..4d2698878 100644 --- a/docs/channels/googlechat.md +++ b/docs/channels/googlechat.md @@ -3,26 +3,28 @@ summary: "Google Chat app support status, capabilities, and configuration" read_when: - Working on Google Chat channel features --- + # Google Chat (Chat API) Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only). ## Quick setup (beginner) -1) Create a Google Cloud project and enable the **Google Chat API**. + +1. Create a Google Cloud project and enable the **Google Chat API**. - Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials) - Enable the API if it is not already enabled. -2) Create a **Service Account**: +2. Create a **Service Account**: - Press **Create Credentials** > **Service Account**. - Name it whatever you want (e.g., `openclaw-chat`). - Leave permissions blank (press **Continue**). - Leave principals with access blank (press **Done**). -3) Create and download the **JSON Key**: +3. Create and download the **JSON Key**: - In the list of service accounts, click on the one you just created. - Go to the **Keys** tab. - Click **Add Key** > **Create new key**. - Select **JSON** and press **Create**. -4) Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`). -5) Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat): +4. Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`). +5. Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat): - Fill in the **Application info**: - **App name**: (e.g. `OpenClaw`) - **Avatar URL**: (e.g. `https://openclaw.ai/logo.png`) @@ -31,44 +33,51 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only). - Under **Functionality**, check **Join spaces and group conversations**. - Under **Connection settings**, select **HTTP endpoint URL**. - Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`. - - *Tip: Run `openclaw status` to find your gateway's public URL.* + - _Tip: Run `openclaw status` to find your gateway's public URL._ - Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**. - Enter your email address (e.g. `user@example.com`) in the text box. - Click **Save** at the bottom. -6) **Enable the app status**: +6. **Enable the app status**: - After saving, **refresh the page**. - Look for the **App status** section (usually near the top or bottom after saving). - Change the status to **Live - available to users**. - Click **Save** again. -7) Configure OpenClaw with the service account path + webhook audience: +7. Configure OpenClaw with the service account path + webhook audience: - Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json` - Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`. -8) Set the webhook audience type + value (matches your Chat app config). -9) Start the gateway. Google Chat will POST to your webhook path. +8. Set the webhook audience type + value (matches your Chat app config). +9. Start the gateway. Google Chat will POST to your webhook path. ## Add to Google Chat + Once the gateway is running and your email is added to the visibility list: -1) Go to [Google Chat](https://chat.google.com/). -2) Click the **+** (plus) icon next to **Direct Messages**. -3) In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console. - - **Note**: The bot will *not* appear in the "Marketplace" browse list because it is a private app. You must search for it by name. -4) Select your bot from the results. -5) Click **Add** or **Chat** to start a 1:1 conversation. -6) Send "Hello" to trigger the assistant! + +1. Go to [Google Chat](https://chat.google.com/). +2. Click the **+** (plus) icon next to **Direct Messages**. +3. In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console. + - **Note**: The bot will _not_ appear in the "Marketplace" browse list because it is a private app. You must search for it by name. +4. Select your bot from the results. +5. Click **Add** or **Chat** to start a 1:1 conversation. +6. Send "Hello" to trigger the assistant! ## Public URL (Webhook-only) + Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network. ### Option A: Tailscale Funnel (Recommended) + Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`. 1. **Check what address your gateway is bound to:** + ```bash ss -tlnp | grep 18789 ``` + Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`). 2. **Expose the dashboard to the tailnet only (port 8443):** + ```bash # If bound to localhost (127.0.0.1 or 0.0.0.0): tailscale serve --bg --https 8443 http://127.0.0.1:18789 @@ -78,6 +87,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook ``` 3. **Expose only the webhook path publicly:** + ```bash # If bound to localhost (127.0.0.1 or 0.0.0.0): tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat @@ -106,16 +116,21 @@ Use the public URL (without `:8443`) in the Google Chat app config. > Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`. ### Option B: Reverse Proxy (Caddy) + If you use a reverse proxy like Caddy, only proxy the specific path: + ```caddy your-domain.com { reverse_proxy /googlechat* localhost:18789 } ``` + With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw. ### Option C: Cloudflare Tunnel + Configure your tunnel's ingress rules to only route the webhook path: + - **Path**: `/googlechat` -> `http://localhost:18789/googlechat` - **Default Rule**: HTTP 404 (Not Found) @@ -133,15 +148,18 @@ Configure your tunnel's ingress rules to only route the webhook path: 5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name. ## Targets + Use these identifiers for delivery and allowlists: + - Direct messages: `users/` or `users/` (email addresses are accepted). - Spaces: `spaces/`. ## Config highlights + ```json5 { channels: { - "googlechat": { + googlechat: { enabled: true, serviceAccountFile: "/path/to/service-account.json", audienceType: "app-url", @@ -150,7 +168,7 @@ Use these identifiers for delivery and allowlists: botUser: "users/1234567890", // optional; helps mention detection dm: { policy: "pairing", - allowFrom: ["users/1234567890", "name@example.com"] + allowFrom: ["users/1234567890", "name@example.com"], }, groupPolicy: "allowlist", groups: { @@ -158,18 +176,19 @@ Use these identifiers for delivery and allowlists: allow: true, requireMention: true, users: ["users/1234567890"], - systemPrompt: "Short answers only." - } + systemPrompt: "Short answers only.", + }, }, actions: { reactions: true }, typingIndicator: "message", - mediaMaxMb: 20 - } - } + mediaMaxMb: 20, + }, + }, } ``` Notes: + - Service account credentials can also be passed inline with `serviceAccount` (JSON string). - Default webhook path is `/googlechat` if `webhookPath` isn’t set. - Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled. @@ -179,22 +198,29 @@ Notes: ## Troubleshooting ### 405 Method Not Allowed + If Google Cloud Logs Explorer shows errors like: + ``` status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed ``` This means the webhook handler isn't registered. Common causes: + 1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with: + ```bash openclaw config get channels.googlechat ``` + If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)). 2. **Plugin not enabled**: Check plugin status: + ```bash openclaw plugins list | grep googlechat ``` + If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config. 3. **Gateway not restarted**: After adding config, restart the gateway: @@ -203,18 +229,21 @@ This means the webhook handler isn't registered. Common causes: ``` Verify the channel is running: + ```bash openclaw channels status # Should show: Google Chat default: enabled, configured, ... ``` ### Other issues + - Check `openclaw channels status --probe` for auth errors or missing audience config. - If no messages arrive, confirm the Chat app's webhook URL + event subscriptions. - If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`. - Use `openclaw logs --follow` while sending a test message to see if requests reach the gateway. Related docs: + - [Gateway configuration](/gateway/configuration) - [Security](/gateway/security) - [Reactions](/tools/reactions) diff --git a/docs/channels/grammy.md b/docs/channels/grammy.md index 89e5beed2..f4648c52b 100644 --- a/docs/channels/grammy.md +++ b/docs/channels/grammy.md @@ -3,15 +3,17 @@ summary: "Telegram Bot API integration via grammY with setup notes" read_when: - Working on Telegram or grammY pathways --- + # grammY Integration (Telegram Bot API) - # Why grammY + - TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter. - Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods. - Extensible: proxy support via custom fetch, session middleware (optional), type-safe context. # What we shipped + - **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default. - **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`. - **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`. @@ -22,6 +24,7 @@ read_when: - **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome. Open questions + - Optional grammY plugins (throttler) if we hit Bot API 429s. - Add more structured media tests (stickers, voice notes). - Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway). diff --git a/docs/channels/imessage.md b/docs/channels/imessage.md index 28814c978..0ef602246 100644 --- a/docs/channels/imessage.md +++ b/docs/channels/imessage.md @@ -4,73 +4,82 @@ read_when: - Setting up iMessage support - Debugging iMessage send/receive --- -# iMessage (imsg) +# iMessage (imsg) Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio). ## Quick setup (beginner) -1) Ensure Messages is signed in on this Mac. -2) Install `imsg`: + +1. Ensure Messages is signed in on this Mac. +2. Install `imsg`: - `brew install steipete/tap/imsg` -3) Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`. -4) Start the gateway and approve any macOS prompts (Automation + Full Disk Access). +3. Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`. +4. Start the gateway and approve any macOS prompts (Automation + Full Disk Access). Minimal config: + ```json5 { channels: { imessage: { enabled: true, cliPath: "/usr/local/bin/imsg", - dbPath: "/Users//Library/Messages/chat.db" - } - } + dbPath: "/Users//Library/Messages/chat.db", + }, + }, } ``` ## What it is + - iMessage channel backed by `imsg` on macOS. - Deterministic routing: replies always go back to iMessage. - DMs share the agent's main session; groups are isolated (`agent::imessage:group:`). - If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below). ## Config writes + By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with: + ```json5 { - channels: { imessage: { configWrites: false } } + channels: { imessage: { configWrites: false } }, } ``` ## Requirements + - macOS with Messages signed in. - Full Disk Access for OpenClaw + `imsg` (Messages DB access). - Automation permission when sending. - `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`). ## Setup (fast path) -1) Ensure Messages is signed in on this Mac. -2) Configure iMessage and start the gateway. + +1. Ensure Messages is signed in on this Mac. +2. Configure iMessage and start the gateway. ### Dedicated bot macOS user (for isolated identity) + If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user. -1) Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`). +1. Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`). - Apple may require a phone number for verification / 2FA. -2) Create a macOS user (example: `openclawhome`) and sign into it. -3) Open Messages in that macOS user and sign into iMessage using the bot Apple ID. -4) Enable Remote Login (System Settings → General → Sharing → Remote Login). -5) Install `imsg`: +2. Create a macOS user (example: `openclawhome`) and sign into it. +3. Open Messages in that macOS user and sign into iMessage using the bot Apple ID. +4. Enable Remote Login (System Settings → General → Sharing → Remote Login). +5. Install `imsg`: - `brew install steipete/tap/imsg` -6) Set up SSH so `ssh @localhost true` works without a password. -7) Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user. +6. Set up SSH so `ssh @localhost true` works without a password. +7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user. -First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the *bot macOS user*. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry. +First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry. Example wrapper (`chmod +x`). Replace `` with your actual macOS username: + ```bash #!/usr/bin/env bash set -euo pipefail @@ -82,6 +91,7 @@ exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T @local ``` Example config: + ```json5 { channels: { @@ -92,20 +102,22 @@ Example config: name: "Bot", enabled: true, cliPath: "/path/to/imsg-bot", - dbPath: "/Users//Library/Messages/chat.db" - } - } - } - } + dbPath: "/Users//Library/Messages/chat.db", + }, + }, + }, + }, } ``` For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map. ### Remote/SSH variant (optional) + If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. OpenClaw only needs stdio. Example wrapper: + ```bash #!/usr/bin/env bash exec ssh -T gateway-host imsg "$@" @@ -117,20 +129,22 @@ exec ssh -T gateway-host imsg "$@" { channels: { imessage: { - cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac - remoteHost: "user@gateway-host", // for SCP file transfer - includeAttachments: true - } - } + cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac + remoteHost: "user@gateway-host", // for SCP file transfer + includeAttachments: true, + }, + }, } ``` If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability. #### Remote Mac via Tailscale (example) + If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back. Architecture: + ``` ┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐ │ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │ @@ -144,6 +158,7 @@ Architecture: ``` Concrete config example (Tailscale hostname): + ```json5 { channels: { @@ -152,19 +167,21 @@ Concrete config example (Tailscale hostname): cliPath: "~/.openclaw/scripts/imsg-ssh", remoteHost: "bot@mac-mini.tailnet-1234.ts.net", includeAttachments: true, - dbPath: "/Users/bot/Library/Messages/chat.db" - } - } + dbPath: "/Users/bot/Library/Messages/chat.db", + }, + }, } ``` Example wrapper (`~/.openclaw/scripts/imsg-ssh`): + ```bash #!/usr/bin/env bash exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@" ``` Notes: + - Ensure the Mac is signed in to Messages, and Remote Login is enabled. - Use SSH keys so `ssh bot@mac-mini.tailnet-1234.ts.net` works without prompts. - `remoteHost` should match the SSH target so SCP can fetch attachments. @@ -172,7 +189,9 @@ Notes: Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.openclaw/openclaw.json` (it often contains tokens). ## Access control (DMs + groups) + DMs: + - Default: `channels.imessage.dmPolicy = "pairing"`. - Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: @@ -181,23 +200,28 @@ DMs: - Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing) Groups: + - `channels.imessage.groupPolicy = open | allowlist | disabled`. - `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. - Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata. - Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`. ## How it works (behavior) + - `imsg` streams message events; the gateway normalizes them into the shared channel envelope. - Replies always route back to the same chat id or handle. ## Group-ish threads (`is_group=false`) + Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier. If you explicitly configure a `chat_id` under `channels.imessage.groups`, OpenClaw treats that thread as a “group” for: + - session isolation (separate `agent::imessage:group:` session key) - group allowlisting / mention gating behavior Example: + ```json5 { channels: { @@ -205,39 +229,47 @@ Example: groupPolicy: "allowlist", groupAllowFrom: ["+15555550123"], groups: { - "42": { "requireMention": false } - } - } - } + "42": { requireMention: false }, + }, + }, + }, } ``` + This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing). ## Media + limits + - Optional attachment ingestion via `channels.imessage.includeAttachments`. - Media cap via `channels.imessage.mediaMaxMb`. ## Limits + - Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000). - Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. - Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16). ## Addressing / delivery targets + Prefer `chat_id` for stable routing: + - `chat_id:123` (preferred) - `chat_guid:...` - `chat_identifier:...` - direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com` List chats: + ``` imsg chats --limit 20 ``` ## Configuration reference (iMessage) + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.imessage.enabled`: enable/disable channel startup. - `channels.imessage.cliPath`: path to `imsg`. - `channels.imessage.dbPath`: Messages DB path. @@ -257,5 +289,6 @@ Provider options: - `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking. Related global options: + - `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`). - `messages.responsePrefix`. diff --git a/docs/channels/index.md b/docs/channels/index.md index ea1b1bc8a..75f2ace67 100644 --- a/docs/channels/index.md +++ b/docs/channels/index.md @@ -4,6 +4,7 @@ read_when: - You want to choose a chat channel for OpenClaw - You need a quick overview of supported messaging platforms --- + # Chat Channels OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway. diff --git a/docs/channels/line.md b/docs/channels/line.md index 7ce70c183..73bff62c1 100644 --- a/docs/channels/line.md +++ b/docs/channels/line.md @@ -32,12 +32,12 @@ openclaw plugins install ./extensions/line ## Setup -1) Create a LINE Developers account and open the Console: +1. Create a LINE Developers account and open the Console: https://developers.line.biz/console/ -2) Create (or pick) a Provider and add a **Messaging API** channel. -3) Copy the **Channel access token** and **Channel secret** from the channel settings. -4) Enable **Use webhook** in the Messaging API settings. -5) Set the webhook URL to your gateway endpoint (HTTPS required): +2. Create (or pick) a Provider and add a **Messaging API** channel. +3. Copy the **Channel access token** and **Channel secret** from the channel settings. +4. Enable **Use webhook** in the Messaging API settings. +5. Set the webhook URL to your gateway endpoint (HTTPS required): ``` https://gateway-host/line/webhook @@ -58,9 +58,9 @@ Minimal config: enabled: true, channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN", channelSecret: "LINE_CHANNEL_SECRET", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` @@ -76,9 +76,9 @@ Token/secret files: channels: { line: { tokenFile: "/path/to/line-token.txt", - secretFile: "/path/to/line-secret.txt" - } - } + secretFile: "/path/to/line-secret.txt", + }, + }, } ``` @@ -92,11 +92,11 @@ Multiple accounts: marketing: { channelAccessToken: "...", channelSecret: "...", - webhookPath: "/line/marketing" - } - } - } - } + webhookPath: "/line/marketing", + }, + }, + }, + }, } ``` @@ -148,11 +148,13 @@ messages. title: "Office", address: "123 Main St", latitude: 35.681236, - longitude: 139.767125 + longitude: 139.767125, }, flexMessage: { altText: "Status card", - contents: { /* Flex payload */ } + contents: { + /* Flex payload */ + }, }, templateMessage: { type: "confirm", @@ -160,10 +162,10 @@ messages. confirmLabel: "Yes", confirmData: "yes", cancelLabel: "No", - cancelData: "no" - } - } - } + cancelData: "no", + }, + }, + }, } ``` diff --git a/docs/channels/location.md b/docs/channels/location.md index 14f1c4af9..511e4aa32 100644 --- a/docs/channels/location.md +++ b/docs/channels/location.md @@ -8,15 +8,18 @@ read_when: # Channel location parsing OpenClaw normalizes shared locations from chat channels into: + - human-readable text appended to the inbound body, and - structured fields in the auto-reply context payload. Currently supported: + - **Telegram** (location pins + venues + live locations) - **WhatsApp** (locationMessage + liveLocationMessage) - **Matrix** (`m.location` with `geo_uri`) ## Text formatting + Locations are rendered as friendly lines without brackets: - Pin: @@ -27,13 +30,16 @@ Locations are rendered as friendly lines without brackets: - `🛰 Live location: 48.858844, 2.294351 ±12m` If the channel includes a caption/comment, it is appended on the next line: + ``` 📍 48.858844, 2.294351 ±12m Meet here ``` ## Context fields + When a location is present, these fields are added to `ctx`: + - `LocationLat` (number) - `LocationLon` (number) - `LocationAccuracy` (number, meters; optional) @@ -43,6 +49,7 @@ When a location is present, these fields are added to `ctx`: - `LocationIsLive` (boolean) ## Channel notes + - **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`. - **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line. - **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false. diff --git a/docs/channels/matrix.md b/docs/channels/matrix.md index 4ad6b9e00..9ceeaa5ad 100644 --- a/docs/channels/matrix.md +++ b/docs/channels/matrix.md @@ -3,6 +3,7 @@ summary: "Matrix support status, capabilities, and configuration" read_when: - Working on Matrix channel features --- + # Matrix (plugin) Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user** @@ -36,13 +37,13 @@ Details: [Plugins](/plugin) ## Setup -1) Install the Matrix plugin: +1. Install the Matrix plugin: - From npm: `openclaw plugins install @openclaw/matrix` - From a local checkout: `openclaw plugins install ./extensions/matrix` -2) Create a Matrix account on a homeserver: +2. Create a Matrix account on a homeserver: - Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/) - Or host it yourself. -3) Get an access token for the bot account: +3. Get an access token for the bot account: - Use the Matrix login API with `curl` at your home server: ```bash @@ -63,14 +64,15 @@ Details: [Plugins](/plugin) - Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`, and reuses it on next start. -4) Configure credentials: + +4. Configure credentials: - Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`) - Or config: `channels.matrix.*` - If both are set, config takes precedence. - With access token: user ID is fetched automatically via `/whoami`. - When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`). -5) Restart the gateway (or finish onboarding). -6) Start a DM with the bot or invite it to a room from any Matrix client +5. Restart the gateway (or finish onboarding). +6. Start a DM with the bot or invite it to a room from any Matrix client (Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE, so set `channels.matrix.encryption: true` and verify the device. @@ -83,9 +85,9 @@ Minimal config (access token, user ID auto-fetched): enabled: true, homeserver: "https://matrix.example.org", accessToken: "syt_***", - dm: { policy: "pairing" } - } - } + dm: { policy: "pairing" }, + }, + }, } ``` @@ -99,9 +101,9 @@ E2EE config (end to end encryption enabled): homeserver: "https://matrix.example.org", accessToken: "syt_***", encryption: true, - dm: { policy: "pairing" } - } - } + dm: { policy: "pairing" }, + }, + }, } ``` @@ -159,11 +161,11 @@ Once verified, the bot can decrypt messages in encrypted rooms. groupPolicy: "allowlist", groups: { "!roomId:example.org": { allow: true }, - "#alias:example.org": { allow: true } + "#alias:example.org": { allow: true }, }, - groupAllowFrom: ["@owner:example.org"] - } - } + groupAllowFrom: ["@owner:example.org"], + }, + }, } ``` @@ -187,17 +189,17 @@ Once verified, the bot can decrypt messages in encrypted rooms. ## Capabilities -| Feature | Status | -|---------|--------| -| Direct messages | ✅ Supported | -| Rooms | ✅ Supported | -| Threads | ✅ Supported | -| Media | ✅ Supported | -| E2EE | ✅ Supported (crypto module required) | -| Reactions | ✅ Supported (send/read via tools) | -| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) | -| Location | ✅ Supported (geo URI; altitude ignored) | -| Native commands | ✅ Supported | +| Feature | Status | +| --------------- | ------------------------------------------------------------------------------------- | +| Direct messages | ✅ Supported | +| Rooms | ✅ Supported | +| Threads | ✅ Supported | +| Media | ✅ Supported | +| E2EE | ✅ Supported (crypto module required) | +| Reactions | ✅ Supported (send/read via tools) | +| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) | +| Location | ✅ Supported (geo URI; altitude ignored) | +| Native commands | ✅ Supported | ## Configuration reference (Matrix) diff --git a/docs/channels/mattermost.md b/docs/channels/mattermost.md index 6ed4ba5a0..168e2a1e4 100644 --- a/docs/channels/mattermost.md +++ b/docs/channels/mattermost.md @@ -12,14 +12,17 @@ Mattermost is a self-hostable team messaging platform; see the official site at [mattermost.com](https://mattermost.com) for product details and downloads. ## Plugin required + Mattermost ships as a plugin and is not bundled with the core install. Install via CLI (npm registry): + ```bash openclaw plugins install @openclaw/mattermost ``` Local checkout (when running from a git repo): + ```bash openclaw plugins install ./extensions/mattermost ``` @@ -30,12 +33,14 @@ OpenClaw will offer the local install path automatically. Details: [Plugins](/plugin) ## Quick setup -1) Install the Mattermost plugin. -2) Create a Mattermost bot account and copy the **bot token**. -3) Copy the Mattermost **base URL** (e.g., `https://chat.example.com`). -4) Configure OpenClaw and start the gateway. + +1. Install the Mattermost plugin. +2. Create a Mattermost bot account and copy the **bot token**. +3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`). +4. Configure OpenClaw and start the gateway. Minimal config: + ```json5 { channels: { @@ -43,13 +48,14 @@ Minimal config: enabled: true, botToken: "mm-token", baseUrl: "https://chat.example.com", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` ## Environment variables (default account) + Set these on the gateway host if you prefer env vars: - `MATTERMOST_BOT_TOKEN=...` @@ -58,6 +64,7 @@ Set these on the gateway host if you prefer env vars: Env vars apply only to the **default** account (`default`). Other accounts must use config values. ## Chat modes + Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`: - `oncall` (default): respond only when @mentioned in channels. @@ -65,22 +72,25 @@ Mattermost responds to DMs automatically. Channel behavior is controlled by `cha - `onchar`: respond when a message starts with a trigger prefix. Config example: + ```json5 { channels: { mattermost: { chatmode: "onchar", - oncharPrefixes: [">", "!"] - } - } + oncharPrefixes: [">", "!"], + }, + }, } ``` Notes: + - `onchar` still responds to explicit @mentions. - `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred. ## Access control (DMs) + - Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code). - Approve via: - `openclaw pairing list mattermost` @@ -88,11 +98,13 @@ Notes: - Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`. ## Channels (groups) + - Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated). - Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`). - Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated). ## Targets for outbound delivery + Use these target formats with `openclaw message send` or cron/webhooks: - `channel:` for a channel @@ -102,6 +114,7 @@ Use these target formats with `openclaw message send` or cron/webhooks: Bare IDs are treated as channels. ## Multi-account + Mattermost supports multiple accounts under `channels.mattermost.accounts`: ```json5 @@ -110,14 +123,15 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`: mattermost: { accounts: { default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" }, - alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" } - } - } - } + alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }, + }, + }, + }, } ``` ## Troubleshooting + - No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`. - Auth errors: check the bot token, base URL, and whether the account is enabled. - Multi-account issues: env vars only apply to the `default` account. diff --git a/docs/channels/msteams.md b/docs/channels/msteams.md index af8638746..cd0932808 100644 --- a/docs/channels/msteams.md +++ b/docs/channels/msteams.md @@ -3,16 +3,17 @@ summary: "Microsoft Teams bot support status, capabilities, and configuration" read_when: - Working on MS Teams channel features --- + # Microsoft Teams (plugin) > "Abandon all hope, ye who enter here." - Updated: 2026-01-21 Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards. ## Plugin required + Microsoft Teams ships as a plugin and is not bundled with the core install. **Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin. @@ -20,11 +21,13 @@ Microsoft Teams ships as a plugin and is not bundled with the core install. Explainable: keeps core installs lighter and lets MS Teams dependencies update independently. Install via CLI (npm registry): + ```bash openclaw plugins install @openclaw/msteams ``` Local checkout (when running from a git repo): + ```bash openclaw plugins install ./extensions/msteams ``` @@ -35,13 +38,15 @@ OpenClaw will offer the local install path automatically. Details: [Plugins](/plugin) ## Quick setup (beginner) -1) Install the Microsoft Teams plugin. -2) Create an **Azure Bot** (App ID + client secret + tenant ID). -3) Configure OpenClaw with those credentials. -4) Expose `/api/messages` (port 3978 by default) via a public URL or tunnel. -5) Install the Teams app package and start the gateway. + +1. Install the Microsoft Teams plugin. +2. Create an **Azure Bot** (App ID + client secret + tenant ID). +3. Configure OpenClaw with those credentials. +4. Expose `/api/messages` (port 3978 by default) via a public URL or tunnel. +5. Install the Teams app package and start the gateway. Minimal config: + ```json5 { channels: { @@ -50,53 +55,61 @@ Minimal config: appId: "", appPassword: "", tenantId: "", - webhook: { port: 3978, path: "/api/messages" } - } - } + webhook: { port: 3978, path: "/api/messages" }, + }, + }, } ``` + Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated). ## Goals + - Talk to OpenClaw via Teams DMs, group chats, or channels. - Keep routing deterministic: replies always go back to the channel they arrived on. - Default to safe channel behavior (mentions required unless configured otherwise). ## Config writes + By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with: + ```json5 { - channels: { msteams: { configWrites: false } } + channels: { msteams: { configWrites: false } }, } ``` ## Access control (DMs + groups) **DM access** + - Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved. - `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves names to IDs via Microsoft Graph when credentials allow. **Group access** + - Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset. - `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`). - Set `groupPolicy: "open"` to allow any member (still mention‑gated by default). - To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`. Example: + ```json5 { channels: { msteams: { groupPolicy: "allowlist", - groupAllowFrom: ["user@org.com"] - } - } + groupAllowFrom: ["user@org.com"], + }, + }, } ``` **Teams + channel allowlist** + - Scope group/channel replies by listing teams and channels under `channels.msteams.teams`. - Keys can be team IDs or names; channel keys can be conversation IDs or names. - When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated). @@ -105,6 +118,7 @@ Example: and logs the mapping; unresolved entries are kept as typed. Example: + ```json5 { channels: { @@ -113,16 +127,17 @@ Example: teams: { "My Team": { channels: { - "General": { requireMention: true } - } - } - } - } - } + General: { requireMention: true }, + }, + }, + }, + }, + }, } ``` ## How it works + 1. Install the Microsoft Teams plugin. 2. Create an **Azure Bot** (App ID + secret + tenant ID). 3. Build a **Teams app package** that references the bot and includes the RSC permissions below. @@ -139,14 +154,14 @@ Before configuring OpenClaw, you need to create an Azure Bot resource. 1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot) 2. Fill in the **Basics** tab: - | Field | Value | - |-------|-------| - | **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) | - | **Subscription** | Select your Azure subscription | - | **Resource group** | Create new or use existing | - | **Pricing tier** | **Free** for dev/testing | - | **Type of App** | **Single Tenant** (recommended - see note below) | - | **Creation type** | **Create new Microsoft App ID** | + | Field | Value | + | ------------------ | -------------------------------------------------------- | + | **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) | + | **Subscription** | Select your Azure subscription | + | **Resource group** | Create new or use existing | + | **Pricing tier** | **Free** for dev/testing | + | **Type of App** | **Single Tenant** (recommended - see note below) | + | **Creation type** | **Create new Microsoft App ID** | > **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots. @@ -178,6 +193,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource. Teams can't reach `localhost`. Use a tunnel for local development: **Option A: ngrok** + ```bash ngrok http 3978 # Copy the https URL, e.g., https://abc123.ngrok.io @@ -185,6 +201,7 @@ ngrok http 3978 ``` **Option B: Tailscale Funnel** + ```bash tailscale funnel 3978 # Use your Tailscale funnel URL as the messaging endpoint @@ -207,16 +224,19 @@ This is often easier than hand-editing JSON manifests. ## Testing the Bot **Option A: Azure Web Chat (verify webhook first)** + 1. In Azure Portal → your Azure Bot resource → **Test in Web Chat** 2. Send a message - you should see a response 3. This confirms your webhook endpoint works before Teams setup **Option B: Teams (after app installation)** + 1. Install the Teams app (sideload or org catalog) 2. Find the bot in Teams and send a DM 3. Check gateway logs for incoming activity ## Setup (minimal text-only) + 1. **Install the Microsoft Teams plugin** - From npm: `openclaw plugins install @openclaw/msteams` - From a local checkout: `openclaw plugins install ./extensions/msteams` @@ -236,6 +256,7 @@ This is often easier than hand-editing JSON manifests. - Zip all three files together: `manifest.json`, `outline.png`, `color.png`. 4. **Configure OpenClaw** + ```json { "msteams": { @@ -261,14 +282,17 @@ This is often easier than hand-editing JSON manifests. - The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials. ## History context + - `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt. - Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50). - DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms[""].historyLimit`. ## Current Teams RSC Permissions (Manifest) + These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed. **For channels (team scope):** + - `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention - `ChannelMessage.Send.Group` (Application) - `Member.Read.Group` (Application) @@ -278,9 +302,11 @@ These are the **existing resourceSpecific permissions** in our Teams app manifes - `TeamSettings.Read.Group` (Application) **For group chats:** + - `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention ## Example Teams Manifest (redacted) + Minimal, valid example with the required fields. Replace IDs and URLs. ```json @@ -330,6 +356,7 @@ Minimal, valid example with the required fields. Replace IDs and URLs. ``` ### Manifest caveats (must-have fields) + - `bots[].botId` **must** match the Azure Bot App ID. - `webApplicationInfo.id` **must** match the Azure Bot App ID. - `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`). @@ -352,34 +379,40 @@ To update an already-installed Teams app (e.g., to add RSC permissions): ## Capabilities: RSC only vs Graph ### With **Teams RSC only** (app installed, no Graph API permissions) + Works: + - Read channel message **text** content. - Send channel message **text** content. - Receive **personal (DM)** file attachments. Does NOT work: + - Channel/group **image or file contents** (payload only includes HTML stub). - Downloading attachments stored in SharePoint/OneDrive. - Reading message history (beyond the live webhook event). ### With **Teams RSC + Microsoft Graph Application permissions** + Adds: + - Downloading hosted contents (images pasted into messages). - Downloading file attachments stored in SharePoint/OneDrive. - Reading channel/chat message history via Graph. ### RSC vs Graph API -| Capability | RSC Permissions | Graph API | -|------------|-----------------|-----------| -| **Real-time messages** | Yes (via webhook) | No (polling only) | -| **Historical messages** | No | Yes (can query history) | -| **Setup complexity** | App manifest only | Requires admin consent + token flow | -| **Works offline** | No (must be running) | Yes (query anytime) | +| Capability | RSC Permissions | Graph API | +| ----------------------- | -------------------- | ----------------------------------- | +| **Real-time messages** | Yes (via webhook) | No (polling only) | +| **Historical messages** | No | Yes (can query history) | +| **Setup complexity** | App manifest only | Requires admin consent + token flow | +| **Works offline** | No (must be running) | Yes (query anytime) | **Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent). ## Graph-enabled media + history (required for channels) + If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent. 1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**: @@ -392,7 +425,9 @@ If you need images/files in **channels** or want to fetch **message history**, y ## Known Limitations ### Webhook timeouts + Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see: + - Gateway timeouts - Teams retrying the message (causing duplicates) - Dropped replies @@ -400,12 +435,15 @@ Teams delivers messages via HTTP webhook. If processing takes too long (e.g., sl OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues. ### Formatting + Teams markdown is more limited than Slack or Discord: -- Basic formatting works: **bold**, *italic*, `code`, links + +- Basic formatting works: **bold**, _italic_, `code`, links - Complex markdown (tables, nested lists) may not render correctly - Adaptive Cards are supported for polls and arbitrary card sends (see below) ## Configuration + Key settings (see `/gateway/configuration` for shared channel patterns): - `channels.msteams.enabled`: enable/disable the channel. @@ -430,6 +468,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns): - `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)). ## Routing & Sessions + - Session keys follow the standard agent format (see [/concepts/session](/concepts/session)): - Direct messages share the main session (`agent::`). - Channel/group messages use conversation id: @@ -440,12 +479,13 @@ Key settings (see `/gateway/configuration` for shared channel patterns): Teams recently introduced two channel UI styles over the same underlying data model: -| Style | Description | Recommended `replyStyle` | -|-------|-------------|--------------------------| -| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) | -| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` | +| Style | Description | Recommended `replyStyle` | +| ------------------------ | --------------------------------------------------------- | ------------------------ | +| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) | +| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` | **The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`: + - `thread` in a Threads-style channel → replies appear nested awkwardly - `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread @@ -471,6 +511,7 @@ Teams recently introduced two channel UI styles over the same underlying data mo ## Attachments & Images **Current limitations:** + - **DMs:** Images and file attachments work via Teams bot file APIs. - **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments. @@ -481,11 +522,11 @@ By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Overri Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup: -| Context | How files are sent | Setup needed | -|---------|-------------------|--------------| -| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box | -| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions | -| **Images (any context)** | Base64-encoded inline | Works out of the box | +| Context | How files are sent | Setup needed | +| ------------------------ | -------------------------------------------- | ----------------------------------------------- | +| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box | +| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions | +| **Images (any context)** | Base64-encoded inline | Works out of the box | ### Why group chats need SharePoint @@ -500,6 +541,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do 2. **Grant admin consent** for the tenant. 3. **Get your SharePoint site ID:** + ```bash # Via Graph Explorer or curl with a valid token: curl -H "Authorization: Bearer $TOKEN" \ @@ -518,35 +560,36 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do channels: { msteams: { // ... other config ... - sharePointSiteId: "contoso.sharepoint.com,guid1,guid2" - } - } + sharePointSiteId: "contoso.sharepoint.com,guid1,guid2", + }, + }, } ``` ### Sharing behavior -| Permission | Sharing behavior | -|------------|------------------| -| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) | -| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) | +| Permission | Sharing behavior | +| --------------------------------------- | --------------------------------------------------------- | +| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) | +| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) | Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing. ### Fallback behavior -| Scenario | Result | -|----------|--------| -| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link | -| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only | -| Personal chat + file | FileConsentCard flow (works without SharePoint) | -| Any context + image | Base64-encoded inline (works without SharePoint) | +| Scenario | Result | +| ------------------------------------------------- | -------------------------------------------------- | +| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link | +| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only | +| Personal chat + file | FileConsentCard flow (works without SharePoint) | +| Any context + image | Base64-encoded inline (works without SharePoint) | ### Files stored location Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library. ## Polls (Adaptive Cards) + OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API). - CLI: `openclaw message poll --channel msteams --target conversation: ...` @@ -555,11 +598,13 @@ OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API) - Polls do not auto-post result summaries yet (inspect the store file if needed). ## Adaptive Cards (arbitrary) + Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI. The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional. **Agent tool:** + ```json { "action": "send", @@ -568,12 +613,13 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid "card": { "type": "AdaptiveCard", "version": "1.5", - "body": [{"type": "TextBlock", "text": "Hello!"}] + "body": [{ "type": "TextBlock", "text": "Hello!" }] } } ``` **CLI:** + ```bash openclaw message send --channel msteams \ --target "conversation:19:abc...@thread.tacv2" \ @@ -586,14 +632,15 @@ See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema an MSTeams targets use prefixes to distinguish between users and conversations: -| Target type | Format | Example | -|-------------|--------|---------| -| User (by ID) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` | -| User (by name) | `user:` | `user:John Smith` (requires Graph API) | -| Group/channel | `conversation:` | `conversation:19:abc123...@thread.tacv2` | -| Group/channel (raw) | `` | `19:abc123...@thread.tacv2` (if contains `@thread`) | +| Target type | Format | Example | +| ------------------- | -------------------------------- | --------------------------------------------------- | +| User (by ID) | `user:` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` | +| User (by name) | `user:` | `user:John Smith` (requires Graph API) | +| Group/channel | `conversation:` | `conversation:19:abc123...@thread.tacv2` | +| Group/channel (raw) | `` | `19:abc123...@thread.tacv2` (if contains `@thread`) | **CLI examples:** + ```bash # Send to a user by ID openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello" @@ -610,6 +657,7 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread. ``` **Agent tool examples:** + ```json { "action": "send", @@ -624,13 +672,18 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread. "action": "send", "channel": "msteams", "target": "conversation:19:abc...@thread.tacv2", - "card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]} + "card": { + "type": "AdaptiveCard", + "version": "1.5", + "body": [{ "type": "TextBlock", "text": "Hello" }] + } } ``` Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name. ## Proactive messaging + - Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point. - See `/gateway/configuration` for `dmPolicy` and allowlist gating. @@ -639,6 +692,7 @@ Note: Without the `user:` prefix, names default to group/team resolution. Always The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead: **Team URL:** + ``` https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=... └────────────────────────────┘ @@ -646,6 +700,7 @@ https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?gro ``` **Channel URL:** + ``` https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=... └─────────────────────────┘ @@ -653,6 +708,7 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr ``` **For config:** + - Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`) - Channel ID = path segment after `/channel/` (URL-decoded) - **Ignore** the `groupId` query parameter @@ -661,15 +717,16 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr Bots have limited support in private channels: -| Feature | Standard Channels | Private Channels | -|---------|-------------------|------------------| -| Bot installation | Yes | Limited | -| Real-time messages (webhook) | Yes | May not work | -| RSC permissions | Yes | May behave differently | -| @mentions | Yes | If bot is accessible | -| Graph API history | Yes | Yes (with permissions) | +| Feature | Standard Channels | Private Channels | +| ---------------------------- | ----------------- | ---------------------- | +| Bot installation | Yes | Limited | +| Real-time messages (webhook) | Yes | May not work | +| RSC permissions | Yes | May behave differently | +| @mentions | Yes | If bot is accessible | +| Graph API history | Yes | Yes (with permissions) | **Workarounds if private channels don't work:** + 1. Use standard channels for bot interactions 2. Use DMs - users can always message the bot directly 3. Use Graph API for historical access (requires `ChannelMessage.Read.All`) @@ -698,6 +755,7 @@ Bots have limited support in private channels: 4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats ## References + - [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide - [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps - [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) diff --git a/docs/channels/nextcloud-talk.md b/docs/channels/nextcloud-talk.md index 0da464c37..337058613 100644 --- a/docs/channels/nextcloud-talk.md +++ b/docs/channels/nextcloud-talk.md @@ -3,19 +3,23 @@ summary: "Nextcloud Talk support status, capabilities, and configuration" read_when: - Working on Nextcloud Talk channel features --- + # Nextcloud Talk (plugin) Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported. ## Plugin required + Nextcloud Talk ships as a plugin and is not bundled with the core install. Install via CLI (npm registry): + ```bash openclaw plugins install @openclaw/nextcloud-talk ``` Local checkout (when running from a git repo): + ```bash openclaw plugins install ./extensions/nextcloud-talk ``` @@ -26,18 +30,20 @@ OpenClaw will offer the local install path automatically. Details: [Plugins](/plugin) ## Quick setup (beginner) -1) Install the Nextcloud Talk plugin. -2) On your Nextcloud server, create a bot: + +1. Install the Nextcloud Talk plugin. +2. On your Nextcloud server, create a bot: ```bash ./occ talk:bot:install "OpenClaw" "" "" --feature reaction ``` -3) Enable the bot in the target room settings. -4) Configure OpenClaw: +3. Enable the bot in the target room settings. +4. Configure OpenClaw: - Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret` - Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only) -5) Restart the gateway (or finish onboarding). +5. Restart the gateway (or finish onboarding). Minimal config: + ```json5 { channels: { @@ -45,19 +51,21 @@ Minimal config: enabled: true, baseUrl: "https://cloud.example.com", botSecret: "shared-secret", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` ## Notes + - Bots cannot initiate DMs. The user must message the bot first. - Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy. - Media uploads are not supported by the bot API; media is sent as URLs. - The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms). ## Access control (DMs) + - Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code. - Approve via: - `openclaw pairing list nextcloud-talk` @@ -65,35 +73,41 @@ Minimal config: - Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`. ## Rooms (groups) + - Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated). - Allowlist rooms with `channels.nextcloud-talk.rooms`: + ```json5 { channels: { "nextcloud-talk": { rooms: { - "room-token": { requireMention: true } - } - } - } + "room-token": { requireMention: true }, + }, + }, + }, } ``` + - To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`. ## Capabilities -| Feature | Status | -|---------|--------| -| Direct messages | Supported | -| Rooms | Supported | -| Threads | Not supported | -| Media | URL-only | -| Reactions | Supported | + +| Feature | Status | +| --------------- | ------------- | +| Direct messages | Supported | +| Rooms | Supported | +| Threads | Not supported | +| Media | URL-only | +| Reactions | Supported | | Native commands | Not supported | ## Configuration reference (Nextcloud Talk) + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.nextcloud-talk.enabled`: enable/disable channel startup. - `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL. - `channels.nextcloud-talk.botSecret`: bot shared secret. diff --git a/docs/channels/nostr.md b/docs/channels/nostr.md index 639c51244..0eafb5df6 100644 --- a/docs/channels/nostr.md +++ b/docs/channels/nostr.md @@ -4,6 +4,7 @@ read_when: - You want OpenClaw to receive DMs via Nostr - You're setting up decentralized messaging --- + # Nostr **Status:** Optional plugin (disabled by default). @@ -40,14 +41,14 @@ Restart the Gateway after installing or enabling plugins. ## Quick setup -1) Generate a Nostr keypair (if needed): +1. Generate a Nostr keypair (if needed): ```bash # Using nak nak key generate ``` -2) Add to config: +2. Add to config: ```json { @@ -59,25 +60,25 @@ nak key generate } ``` -3) Export the key: +3. Export the key: ```bash export NOSTR_PRIVATE_KEY="nsec1..." ``` -4) Restart the Gateway. +4. Restart the Gateway. ## Configuration reference -| Key | Type | Default | Description | -| --- | --- | --- | --- | -| `privateKey` | string | required | Private key in `nsec` or hex format | -| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) | -| `dmPolicy` | string | `pairing` | DM access policy | -| `allowFrom` | string[] | `[]` | Allowed sender pubkeys | -| `enabled` | boolean | `true` | Enable/disable channel | -| `name` | string | - | Display name | -| `profile` | object | - | NIP-01 profile metadata | +| Key | Type | Default | Description | +| ------------ | -------- | ------------------------------------------- | ----------------------------------- | +| `privateKey` | string | required | Private key in `nsec` or hex format | +| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) | +| `dmPolicy` | string | `pairing` | DM access policy | +| `allowFrom` | string[] | `[]` | Allowed sender pubkeys | +| `enabled` | boolean | `true` | Enable/disable channel | +| `name` | string | - | Display name | +| `profile` | object | - | NIP-01 profile metadata | ## Profile metadata @@ -149,11 +150,7 @@ Defaults: `relay.damus.io` and `nos.lol`. "channels": { "nostr": { "privateKey": "${NOSTR_PRIVATE_KEY}", - "relays": [ - "wss://relay.damus.io", - "wss://relay.primal.net", - "wss://nostr.wine" - ] + "relays": ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"] } } } @@ -168,12 +165,12 @@ Tips: ## Protocol support -| NIP | Status | Description | -| --- | --- | --- | +| NIP | Status | Description | +| ------ | --------- | ------------------------------------- | | NIP-01 | Supported | Basic event format + profile metadata | -| NIP-04 | Supported | Encrypted DMs (`kind:4`) | -| NIP-17 | Planned | Gift-wrapped DMs | -| NIP-44 | Planned | Versioned encryption | +| NIP-04 | Supported | Encrypted DMs (`kind:4`) | +| NIP-17 | Planned | Gift-wrapped DMs | +| NIP-44 | Planned | Versioned encryption | ## Testing @@ -197,10 +194,10 @@ docker run -p 7777:7777 ghcr.io/hoytech/strfry ### Manual test -1) Note the bot pubkey (npub) from logs. -2) Open a Nostr client (Damus, Amethyst, etc.). -3) DM the bot pubkey. -4) Verify the response. +1. Note the bot pubkey (npub) from logs. +2. Open a Nostr client (Damus, Amethyst, etc.). +3. DM the bot pubkey. +4. Verify the response. ## Troubleshooting diff --git a/docs/channels/signal.md b/docs/channels/signal.md index 5f6c33de5..10a693332 100644 --- a/docs/channels/signal.md +++ b/docs/channels/signal.md @@ -4,19 +4,21 @@ read_when: - Setting up Signal support - Debugging Signal send/receive --- -# Signal (signal-cli) +# Signal (signal-cli) Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE. ## Quick setup (beginner) -1) Use a **separate Signal number** for the bot (recommended). -2) Install `signal-cli` (Java required). -3) Link the bot device and start the daemon: + +1. Use a **separate Signal number** for the bot (recommended). +2. Install `signal-cli` (Java required). +3. Link the bot device and start the daemon: - `signal-cli link -n "OpenClaw"` -4) Configure OpenClaw and start the gateway. +4. Configure OpenClaw and start the gateway. Minimal config: + ```json5 { channels: { @@ -25,39 +27,45 @@ Minimal config: account: "+15551234567", cliPath: "signal-cli", dmPolicy: "pairing", - allowFrom: ["+15557654321"] - } - } + allowFrom: ["+15557654321"], + }, + }, } ``` ## What it is + - Signal channel via `signal-cli` (not embedded libsignal). - Deterministic routing: replies always go back to Signal. - DMs share the agent's main session; groups are isolated (`agent::signal:group:`). ## Config writes + By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with: + ```json5 { - channels: { signal: { configWrites: false } } + channels: { signal: { configWrites: false } }, } ``` ## The number model (important) + - The gateway connects to a **Signal device** (the `signal-cli` account). - If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection). - For "I text the bot and it replies," use a **separate bot number**. ## Setup (fast path) -1) Install `signal-cli` (Java required). -2) Link a bot account: + +1. Install `signal-cli` (Java required). +2. Link a bot account: - `signal-cli link -n "OpenClaw"` then scan the QR in Signal. -3) Configure Signal and start the gateway. +3. Configure Signal and start the gateway. Example: + ```json5 { channels: { @@ -66,15 +74,16 @@ Example: account: "+15551234567", cliPath: "signal-cli", dmPolicy: "pairing", - allowFrom: ["+15557654321"] - } - } + allowFrom: ["+15557654321"], + }, + }, } ``` Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. ## External daemon mode (httpUrl) + If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it: ```json5 @@ -82,16 +91,18 @@ If you want to manage `signal-cli` yourself (slow JVM cold starts, container ini channels: { signal: { httpUrl: "http://127.0.0.1:8080", - autoStart: false - } - } + autoStart: false, + }, + }, } ``` This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`. ## Access control (DMs + groups) + DMs: + - Default: `channels.signal.dmPolicy = "pairing"`. - Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: @@ -101,15 +112,18 @@ DMs: - UUID-only senders (from `sourceUuid`) are stored as `uuid:` in `channels.signal.allowFrom`. Groups: + - `channels.signal.groupPolicy = open | allowlist | disabled`. - `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set. ## How it works (behavior) + - `signal-cli` runs as a daemon; the gateway reads events via SSE. - Inbound messages are normalized into the shared channel envelope. - Replies always route back to the same number or group. ## Media + limits + - Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000). - Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. - Attachments supported (base64 fetched from `signal-cli`). @@ -118,17 +132,20 @@ Groups: - Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50). ## Typing + read receipts + - **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running. - **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs. - Signal-cli does not expose read receipts for groups. ## Reactions (message tool) + - Use `message action=react` with `channel=signal`. - Targets: sender E.164 or UUID (use `uuid:` from pairing output; bare UUID works too). - `messageId` is the Signal timestamp for the message you’re reacting to. - Group reactions require `targetAuthor` or `targetAuthorUuid`. Examples: + ``` message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥 message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true @@ -136,6 +153,7 @@ message action=react channel=signal target=signal:group: targetAuthor=u ``` Config: + - `channels.signal.actions.reactions`: enable/disable reaction actions (default true). - `channels.signal.reactionLevel`: `off | ack | minimal | extensive`. - `off`/`ack` disables agent reactions (message tool `react` will error). @@ -143,15 +161,18 @@ Config: - Per-account overrides: `channels.signal.accounts..actions.reactions`, `channels.signal.accounts..reactionLevel`. ## Delivery targets (CLI/cron) + - DMs: `signal:+15551234567` (or plain E.164). - UUID DMs: `uuid:` (or bare UUID). - Groups: `signal:group:`. - Usernames: `username:` (if supported by your Signal account). ## Configuration reference (Signal) + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.signal.enabled`: enable/disable channel startup. - `channels.signal.account`: E.164 for the bot account. - `channels.signal.cliPath`: path to `signal-cli`. @@ -174,6 +195,7 @@ Provider options: - `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB). Related global options: + - `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions). - `messages.groupChat.mentionPatterns` (global fallback). - `messages.responsePrefix`. diff --git a/docs/channels/slack.md b/docs/channels/slack.md index cc11d9395..bffc476bb 100644 --- a/docs/channels/slack.md +++ b/docs/channels/slack.md @@ -8,38 +8,41 @@ read_when: "Setting up Slack or debugging Slack socket/HTTP mode" ## Socket mode (default) ### Quick setup (beginner) -1) Create a Slack app and enable **Socket Mode**. -2) Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`). -3) Set tokens for OpenClaw and start the gateway. + +1. Create a Slack app and enable **Socket Mode**. +2. Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`). +3. Set tokens for OpenClaw and start the gateway. Minimal config: + ```json5 { channels: { slack: { enabled: true, appToken: "xapp-...", - botToken: "xoxb-..." - } - } + botToken: "xoxb-...", + }, + }, } ``` ### Setup -1) Create a Slack app (From scratch) in https://api.slack.com/apps. -2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`). -3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`). -4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`). -5) **Event Subscriptions** → enable events and subscribe to: + +1. Create a Slack app (From scratch) in https://api.slack.com/apps. +2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`). +3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`). +4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`). +5. **Event Subscriptions** → enable events and subscribe to: - `message.*` (includes edits/deletes/thread broadcasts) - `app_mention` - `reaction_added`, `reaction_removed` - `member_joined_channel`, `member_left_channel` - `channel_rename` - `pin_added`, `pin_removed` -6) Invite the bot to channels you want it to read. -7) Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off). -8) App Home → enable the **Messages Tab** so users can DM the bot. +6. Invite the bot to channels you want it to read. +7. Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off). +8. App Home → enable the **Messages Tab** so users can DM the bot. Use the manifest below so scopes and events stay in sync. @@ -48,6 +51,7 @@ Multi-account support: use `channels.slack.accounts` with per-account tokens and ### OpenClaw config (minimal) Set tokens via env vars (recommended): + - `SLACK_APP_TOKEN=xapp-...` - `SLACK_BOT_TOKEN=xoxb-...` @@ -59,13 +63,14 @@ Or via config: slack: { enabled: true, appToken: "xapp-...", - botToken: "xoxb-..." - } - } + botToken: "xoxb-...", + }, + }, } ``` ### User token (optional) + OpenClaw can use a Slack user token (`xoxp-...`) for read operations (history, pins, reactions, emoji, member info). By default this stays read-only: reads prefer the user token when present, and writes still use the bot token unless @@ -76,20 +81,7 @@ User tokens are configured in the config file (no env var support). For multi-account, set `channels.slack.accounts..userToken`. Example with bot + app + user tokens: -```json5 -{ - channels: { - slack: { - enabled: true, - appToken: "xapp-...", - botToken: "xoxb-...", - userToken: "xoxp-..." - } - } -} -``` -Example with userTokenReadOnly explicitly set (allow user token writes): ```json5 { channels: { @@ -98,13 +90,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes): appToken: "xapp-...", botToken: "xoxb-...", userToken: "xoxp-...", - userTokenReadOnly: false - } - } + }, + }, +} +``` + +Example with userTokenReadOnly explicitly set (allow user token writes): + +```json5 +{ + channels: { + slack: { + enabled: true, + appToken: "xapp-...", + botToken: "xoxb-...", + userToken: "xoxp-...", + userTokenReadOnly: false, + }, + }, } ``` #### Token usage + - Read operations (history, reactions list, pins list, emoji list, member info, search) prefer the user token when configured, otherwise the bot token. - Write operations (send/edit/delete messages, add/remove reactions, pin/unpin, @@ -112,25 +120,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes): no bot token is available, OpenClaw falls back to the user token. ### History context + - `channels.slack.historyLimit` (or `channels.slack.accounts.*.historyLimit`) controls how many recent channel/group messages are wrapped into the prompt. - Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50). ## HTTP mode (Events API) + Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments). HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL. ### Setup -1) Create a Slack app and **disable Socket Mode** (optional if you only use HTTP). -2) **Basic Information** → copy the **Signing Secret**. -3) **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`). -4) **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`). -5) **Interactivity & Shortcuts** → enable and set the same **Request URL**. -6) **Slash Commands** → set the same **Request URL** for your command(s). + +1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP). +2. **Basic Information** → copy the **Signing Secret**. +3. **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`). +4. **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`). +5. **Interactivity & Shortcuts** → enable and set the same **Request URL**. +6. **Slash Commands** → set the same **Request URL** for your command(s). Example request URL: `https://gateway-host/slack/events` ### OpenClaw config (minimal) + ```json5 { channels: { @@ -139,9 +151,9 @@ Example request URL: mode: "http", botToken: "xoxb-...", signingSecret: "your-signing-secret", - webhookPath: "/slack/events" - } - } + webhookPath: "/slack/events", + }, + }, } ``` @@ -149,6 +161,7 @@ Multi-account HTTP mode: set `channels.slack.accounts..mode = "http"` and pr `webhookPath` per account so each Slack app can point to its own URL. ### Manifest (optional) + Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the user scopes if you plan to configure a user token. @@ -243,11 +256,13 @@ user scopes if you plan to configure a user token. If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`. ## Scopes (current vs optional) + Slack's Conversations API is type-scoped: you only need the scopes for the conversation types you actually touch (channels, groups, im, mpim). See https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview. ### Bot token scopes (required) + - `chat:write` (send/update/delete messages via `chat.postMessage`) https://docs.slack.dev/reference/methods/chat.postMessage - `im:write` (open DMs via `conversations.open` for user DMs) @@ -270,6 +285,7 @@ https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overvie https://docs.slack.dev/messaging/working-with-files/#upload ### User token scopes (optional, read-only by default) + Add these under **User Token Scopes** if you configure `channels.slack.userToken`. - `channels:history`, `groups:history`, `im:history`, `mpim:history` @@ -281,6 +297,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken - `search:read` ### Not needed today (but likely future) + - `mpim:write` (only if we add group-DM open/DM start via `conversations.open`) - `groups:write` (only if we add private-channel management: create/rename/invite/archive) - `chat:write.public` (only if we want to post to channels the bot isn't in) @@ -290,6 +307,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken - `files:read` (only if we start listing/reading file metadata) ## Config + Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens: ```json @@ -340,6 +358,7 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens: ``` Tokens can also be supplied via env vars: + - `SLACK_BOT_TOKEN` - `SLACK_APP_TOKEN` @@ -348,94 +367,105 @@ Ack reactions are controlled globally via `messages.ackReaction` + ack reaction after the bot replies. ## Limits + - Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000). - Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. - Media uploads are capped by `channels.slack.mediaMaxMb` (default 20). ## Reply threading + By default, OpenClaw replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading: -| Mode | Behavior | -| --- | --- | -| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. | +| Mode | Behavior | +| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. | | `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. | -| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. | +| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. | The mode applies to both auto-replies and agent tool calls (`slack sendMessage`). ### Per-chat-type threading + You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`: ```json5 { channels: { slack: { - replyToMode: "off", // default for channels + replyToMode: "off", // default for channels replyToModeByChatType: { - direct: "all", // DMs always thread - group: "first" // group DMs/MPIM thread first reply + direct: "all", // DMs always thread + group: "first", // group DMs/MPIM thread first reply }, - } - } + }, + }, } ``` Supported chat types: + - `direct`: 1:1 DMs (Slack `im`) - `group`: group DMs / MPIMs (Slack `mpim`) - `channel`: standard channels (public/private) Precedence: -1) `replyToModeByChatType.` -2) `replyToMode` -3) Provider default (`off`) + +1. `replyToModeByChatType.` +2. `replyToMode` +3. Provider default (`off`) Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set. Examples: Thread DMs only: + ```json5 { channels: { slack: { replyToMode: "off", - replyToModeByChatType: { direct: "all" } - } - } + replyToModeByChatType: { direct: "all" }, + }, + }, } ``` Thread group DMs but keep channels in the root: + ```json5 { channels: { slack: { replyToMode: "off", - replyToModeByChatType: { group: "first" } - } - } + replyToModeByChatType: { group: "first" }, + }, + }, } ``` Make channels thread, keep DMs in the root: + ```json5 { channels: { slack: { replyToMode: "first", - replyToModeByChatType: { direct: "off", group: "off" } - } - } + replyToModeByChatType: { direct: "off", group: "off" }, + }, + }, } ``` ### Manual threading tags + For fine-grained control, use these tags in agent responses: + - `[[reply_to_current]]` — reply to the triggering message (start/continue thread). - `[[reply_to:]]` — reply to a specific message id. ## Sessions + routing + - DMs share the `main` session (like WhatsApp/Telegram). - Channels map to `agent::slack:channel:` sessions. - Slash commands use `agent::slack:slash:` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`). @@ -444,24 +474,27 @@ For fine-grained control, use these tags in agent responses: - Full command list + config: [Slash commands](/tools/slash-commands) ## DM security (pairing) + - Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour). - Approve via: `openclaw pairing approve slack `. - To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`. - `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow. ## Group policy + - `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`). - `allowlist` requires channels to be listed in `channels.slack.channels`. - - If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section, - the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`, - `channels.defaults.groupPolicy`, or a channel allowlist to lock it down. - - The configure wizard accepts `#channel` names and resolves them to IDs when possible - (public + private); if multiple matches exist, it prefers the active channel. - - On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow) - and logs the mapping; unresolved entries are kept as typed. - - To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist). +- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section, + the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`, + `channels.defaults.groupPolicy`, or a channel allowlist to lock it down. +- The configure wizard accepts `#channel` names and resolves them to IDs when possible + (public + private); if multiple matches exist, it prefers the active channel. +- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow) + and logs the mapping; unresolved entries are kept as typed. +- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist). Channel options (`channels.slack.channels.` or `channels.slack.channels.`): + - `allow`: allow/deny the channel when `groupPolicy="allowlist"`. - `requireMention`: mention gating for the channel. - `tools`: optional per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`). @@ -473,22 +506,26 @@ Channel options (`channels.slack.channels.` or `channels.slack.channels.` for DMs - `channel:` for channels ## Tool actions + Slack tool actions can be gated with `channels.slack.actions.*`: -| Action group | Default | Notes | -| --- | --- | --- | -| reactions | enabled | React + list reactions | -| messages | enabled | Read/send/edit/delete | -| pins | enabled | Pin/unpin/list | -| memberInfo | enabled | Member info | -| emojiList | enabled | Custom emoji list | +| Action group | Default | Notes | +| ------------ | ------- | ---------------------- | +| reactions | enabled | React + list reactions | +| messages | enabled | Read/send/edit/delete | +| pins | enabled | Pin/unpin/list | +| memberInfo | enabled | Member info | +| emojiList | enabled | Custom emoji list | ## Security notes + - Writes default to the bot token so state-changing actions stay scoped to the app's bot permissions and identity. - Setting `userTokenReadOnly: false` allows the user token to be used for write @@ -500,6 +537,7 @@ Slack tool actions can be gated with `channels.slack.actions.*`: `files:write`) or those operations will fail. ## Notes + - Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions. - Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`. - Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`). diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index 351988f1c..38e86d6b8 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -3,49 +3,56 @@ summary: "Telegram bot support status, capabilities, and configuration" read_when: - Working on Telegram features or webhooks --- -# Telegram (Bot API) +# Telegram (Bot API) Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional. ## Quick setup (beginner) -1) Create a bot with **@BotFather** and copy the token. -2) Set the token: + +1. Create a bot with **@BotFather** and copy the token. +2. Set the token: - Env: `TELEGRAM_BOT_TOKEN=...` - Or config: `channels.telegram.botToken: "..."`. - If both are set, config takes precedence (env fallback is default-account only). -3) Start the gateway. -4) DM access is pairing by default; approve the pairing code on first contact. +3. Start the gateway. +4. DM access is pairing by default; approve the pairing code on first contact. Minimal config: + ```json5 { channels: { telegram: { enabled: true, botToken: "123:abc", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` ## What it is + - A Telegram Bot API channel owned by the Gateway. - Deterministic routing: replies go back to Telegram; the model never chooses channels. - DMs share the agent's main session; groups stay isolated (`agent::telegram:group:`). ## Setup (fast path) + ### 1) Create a bot token (BotFather) -1) Open Telegram and chat with **@BotFather**. -2) Run `/newbot`, then follow the prompts (name + username ending in `bot`). -3) Copy the token and store it safely. + +1. Open Telegram and chat with **@BotFather**. +2. Run `/newbot`, then follow the prompts (name + username ending in `bot`). +3. Copy the token and store it safely. Optional BotFather settings: + - `/setjoingroups` — allow/deny adding the bot to groups. - `/setprivacy` — control whether the bot sees all group messages. ### 2) Configure the token (env or config) + Example: ```json5 @@ -55,9 +62,9 @@ Example: enabled: true, botToken: "123:abc", dmPolicy: "pairing", - groups: { "*": { requireMention: true } } - } - } + groups: { "*": { requireMention: true } }, + }, + }, } ``` @@ -66,19 +73,22 @@ If both env and config are set, config takes precedence. Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. -3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback). -4) DM access defaults to pairing. Approve the code when the bot is first contacted. -5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists. +3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback). +4. DM access defaults to pairing. Approve the code when the bot is first contacted. +5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists. ## Token + privacy + permissions (Telegram side) ### Token creation (BotFather) + - `/newbot` creates the bot and returns the token (keep it secret). - If a token leaks, revoke/regenerate it via @BotFather and update your config. ### Group message visibility (Privacy Mode) + Telegram bots default to **Privacy Mode**, which limits which group messages they receive. -If your bot must see *all* group messages, you have two options: +If your bot must see _all_ group messages, you have two options: + - Disable privacy mode with `/setprivacy` **or** - Add the bot as a group **admin** (admin bots receive all messages). @@ -86,10 +96,12 @@ If your bot must see *all* group messages, you have two options: to each group for the change to take effect. ### Group permissions (admin rights) + Admin status is set inside the group (Telegram UI). Admin bots always receive all group messages, so use admin if you need full visibility. ## How it works (behavior) + - Inbound messages are normalized into the shared channel envelope with reply context and media placeholders. - Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`). - Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`. @@ -98,12 +110,14 @@ group messages, so use admin if you need full visibility. - Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option. ## Formatting (Telegram HTML) + - Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset). - Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets. - Raw HTML from models is escaped to avoid Telegram parse errors. - If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text. ## Commands (native + custom) + OpenClaw registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup. You can add custom commands to the menu via config: @@ -113,10 +127,10 @@ You can add custom commands to the menu via config: telegram: { customCommands: [ { command: "backup", description: "Git backup" }, - { command: "generate", description: "Create an image" } - ] - } - } + { command: "generate", description: "Create an image" }, + ], + }, + }, } ``` @@ -128,12 +142,14 @@ You can add custom commands to the menu via config: More help: [Channel troubleshooting](/channels/troubleshooting). Notes: + - Custom commands are **menu entries only**; OpenClaw does not implement them unless you handle them elsewhere. - Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars). - Custom commands **cannot override native commands**. Conflicts are ignored and logged. - If `commands.native` is disabled, only custom commands are registered (or cleared if none). ## Limits + - Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000). - Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. - Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5). @@ -152,10 +168,10 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns channels: { telegram: { groups: { - "-1001234567890": { requireMention: false } // always respond in this group - } - } - } + "-1001234567890": { requireMention: false }, // always respond in this group + }, + }, + }, } ``` @@ -163,34 +179,37 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups..topics.`. To allow all groups with always-respond: + ```json5 { channels: { telegram: { groups: { - "*": { requireMention: false } // all groups, always respond - } - } - } + "*": { requireMention: false }, // all groups, always respond + }, + }, + }, } ``` To keep mention-only for all groups (default behavior): + ```json5 { channels: { telegram: { groups: { - "*": { requireMention: true } // or omit groups entirely - } - } - } + "*": { requireMention: true }, // or omit groups entirely + }, + }, + }, } ``` ### Via command (session-level) Send in the group: + - `/activation always` - respond to all messages - `/activation mention` - require mentions (default) @@ -205,21 +224,26 @@ Forward any message from the group to `@userinfobot` or `@getidsbot` on Telegram **Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `openclaw logs --follow` to read `chat.id`, or use the Bot API `getUpdates`. ## Config writes + By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`. This happens when: + - A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). OpenClaw can migrate `channels.telegram.groups` automatically. - You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`). Disable with: + ```json5 { - channels: { telegram: { configWrites: false } } + channels: { telegram: { configWrites: false } }, } ``` ## Topics (forum supergroups) + Telegram forum topics include a `message_thread_id` per message. OpenClaw: + - Appends `:topic:` to the Telegram group session key so each topic is isolated. - Sends typing indicators and replies with `message_thread_id` so responses stay in the topic. - General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it. @@ -235,34 +259,36 @@ Telegram supports inline keyboards with callback buttons. ```json5 { - "channels": { - "telegram": { - "capabilities": { - "inlineButtons": "allowlist" - } - } - } + channels: { + telegram: { + capabilities: { + inlineButtons: "allowlist", + }, + }, + }, } ``` For per-account configuration: + ```json5 { - "channels": { - "telegram": { - "accounts": { - "main": { - "capabilities": { - "inlineButtons": "allowlist" - } - } - } - } - } + channels: { + telegram: { + accounts: { + main: { + capabilities: { + inlineButtons: "allowlist", + }, + }, + }, + }, + }, } ``` Scopes: + - `off` — inline buttons disabled - `dm` — only DMs (group targets blocked) - `group` — only groups (DM targets blocked) @@ -278,19 +304,17 @@ Use the message tool with the `buttons` parameter: ```json5 { - "action": "send", - "channel": "telegram", - "to": "123456789", - "message": "Choose an option:", - "buttons": [ + action: "send", + channel: "telegram", + to: "123456789", + message: "Choose an option:", + buttons: [ [ - {"text": "Yes", "callback_data": "yes"}, - {"text": "No", "callback_data": "no"} + { text: "Yes", callback_data: "yes" }, + { text: "No", callback_data: "no" }, ], - [ - {"text": "Cancel", "callback_data": "cancel"} - ] - ] + [{ text: "Cancel", callback_data: "cancel" }], + ], } ``` @@ -305,9 +329,11 @@ Telegram capabilities can be configured at two levels (object form shown above; - `channels.telegram.accounts..capabilities`: Per-account capabilities that override the global defaults for that specific account. Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups). + ## Access control (DMs + groups) ### DM access + - Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: - `openclaw pairing list telegram` @@ -316,18 +342,22 @@ Use the global setting when all Telegram bots/accounts should behave the same. U - `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible. #### Finding your Telegram user ID + Safer (no third-party bot): -1) Start the gateway and DM your bot. -2) Run `openclaw logs --follow` and look for `from.id`. + +1. Start the gateway and DM your bot. +2. Run `openclaw logs --follow` and look for `from.id`. Alternate (official Bot API): -1) DM your bot. -2) Fetch updates with your bot token and read `message.from.id`: + +1. DM your bot. +2. Fetch updates with your bot token and read `message.from.id`: ```bash curl "https://api.telegram.org/bot/getUpdates" ``` Third-party (less private): + - DM `@userinfobot` or `@getidsbot` and use the returned user id. ### Group access @@ -335,37 +365,45 @@ Third-party (less private): Two independent controls: **1. Which groups are allowed** (group allowlist via `channels.telegram.groups`): + - No `groups` config = all groups allowed - With `groups` config = only listed groups or `"*"` are allowed - Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups **2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`): + - `"open"` = all senders in allowed groups can message - `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message - `"disabled"` = no group messages accepted at all -Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`). + Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`). Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups` ## Long-polling vs webhook + - Default: long-polling (no public URL required). - Webhook mode: set `channels.telegram.webhookUrl` (optionally `channels.telegram.webhookSecret` + `channels.telegram.webhookPath`). - The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default. - If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint. ## Reply threading + Telegram supports optional threaded replies via tags: + - `[[reply_to_current]]` -- reply to the triggering message. - `[[reply_to:]]` -- reply to a specific message id. Controlled by `channels.telegram.replyToMode`: + - `first` (default), `all`, `off`. ## Audio messages (voice vs file) + Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card). OpenClaw defaults to audio files for backward compatibility. To force a voice note bubble in agent replies, include this tag anywhere in the reply: + - `[[audio_as_voice]]` — send audio as a voice note instead of a file. The tag is stripped from the delivered text. Other channels ignore this tag. @@ -375,11 +413,11 @@ For message tool sends, set `asVoice: true` with a voice-compatible audio `media ```json5 { - "action": "send", - "channel": "telegram", - "to": "123456789", - "media": "https://example.com/voice.ogg", - "asVoice": true + action: "send", + channel: "telegram", + to: "123456789", + media: "https://example.com/voice.ogg", + asVoice: true, } ``` @@ -396,6 +434,7 @@ When a user sends a sticker, OpenClaw handles it based on the sticker type: - **Video stickers (WEBM):** Skipped (video format not supported for processing). Template context field available when receiving stickers: + - `Sticker` — object with: - `emoji` — emoji associated with the sticker - `setName` — name of the sticker set @@ -416,6 +455,7 @@ Stickers are processed through the AI's vision capabilities to generate descript **Cache location:** `~/.openclaw/telegram/sticker-cache.json` **Cache entry format:** + ```json { "fileId": "CAACAgIAAxkBAAI...", @@ -428,6 +468,7 @@ Stickers are processed through the AI's vision capabilities to generate descript ``` **Benefits:** + - Reduces API costs by avoiding repeated vision calls for the same sticker - Faster response times for cached stickers (no vision processing delay) - Enables sticker search functionality based on cached descriptions @@ -443,10 +484,10 @@ The agent can send and search stickers using the `sticker` and `sticker-search` channels: { telegram: { actions: { - sticker: true - } - } - } + sticker: true, + }, + }, + }, } ``` @@ -454,14 +495,15 @@ The agent can send and search stickers using the `sticker` and `sticker-search` ```json5 { - "action": "sticker", - "channel": "telegram", - "to": "123456789", - "fileId": "CAACAgIAAxkBAAI..." + action: "sticker", + channel: "telegram", + to: "123456789", + fileId: "CAACAgIAAxkBAAI...", } ``` Parameters: + - `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result. - `replyTo` (optional) — message ID to reply to. - `threadId` (optional) — message thread ID for forum topics. @@ -472,26 +514,27 @@ The agent can search cached stickers by description, emoji, or set name: ```json5 { - "action": "sticker-search", - "channel": "telegram", - "query": "cat waving", - "limit": 5 + action: "sticker-search", + channel: "telegram", + query: "cat waving", + limit: 5, } ``` Returns matching stickers from the cache: + ```json5 { - "ok": true, - "count": 2, - "stickers": [ + ok: true, + count: 2, + stickers: [ { - "fileId": "CAACAgIAAxkBAAI...", - "emoji": "👋", - "description": "A cartoon cat waving enthusiastically", - "setName": "CoolCats" - } - ] + fileId: "CAACAgIAAxkBAAI...", + emoji: "👋", + description: "A cartoon cat waving enthusiastically", + setName: "CoolCats", + }, + ], } ``` @@ -501,26 +544,29 @@ The search uses fuzzy matching across description text, emoji characters, and se ```json5 { - "action": "sticker", - "channel": "telegram", - "to": "-1001234567890", - "fileId": "CAACAgIAAxkBAAI...", - "replyTo": 42, - "threadId": 123 + action: "sticker", + channel: "telegram", + to: "-1001234567890", + fileId: "CAACAgIAAxkBAAI...", + replyTo: 42, + threadId: 123, } ``` ## Streaming (drafts) + Telegram can stream **draft bubbles** while the agent is generating a response. OpenClaw uses Bot API `sendMessageDraft` (not real messages) and then sends the final reply as a normal message. Requirements (Telegram Bot API 9.3+): + - **Private chats with topics enabled** (forum topic mode for the bot). - Incoming messages must include `message_thread_id` (private topic thread). - Streaming is ignored for groups/supergroups/channels. Config: + - `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`) - `partial`: update the draft bubble with the latest streaming text. - `block`: update the draft bubble in larger blocks (chunked). @@ -534,15 +580,18 @@ Block streaming is off by default and requires `channels.telegram.blockStreaming if you want early Telegram messages instead of draft updates. Reasoning stream (Telegram only): + - `/reasoning stream` streams reasoning into the draft bubble while the reply is generating, then sends the final answer without reasoning. - If `channels.telegram.streamMode` is `off`, reasoning stream is disabled. -More context: [Streaming + chunking](/concepts/streaming). + More context: [Streaming + chunking](/concepts/streaming). ## Retry policy + Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry). ## Agent tool (messages + reactions) + - Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`). - Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`). - Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`). @@ -562,6 +611,7 @@ Telegram reactions arrive as **separate `message_reaction` events**, not as prop The agent sees reactions as **system notifications** in the conversation history, not as message metadata. **Configuration:** + - `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications - `"off"` — ignore all reactions - `"own"` — notify when users react to bot messages (best-effort; in-memory) (default) @@ -576,29 +626,33 @@ The agent sees reactions as **system notifications** in the conversation history **Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together. **Example config:** + ```json5 { channels: { telegram: { - reactionNotifications: "all", // See all reactions - reactionLevel: "minimal" // Agent can react sparingly - } - } + reactionNotifications: "all", // See all reactions + reactionLevel: "minimal", // Agent can react sparingly + }, + }, } ``` **Requirements:** + - Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by OpenClaw) - For webhook mode, reactions are included in the webhook `allowed_updates` - For polling mode, reactions are included in the `getUpdates` `allowed_updates` ## Delivery targets (CLI/cron) + - Use a chat id (`123456789`) or a username (`@name`) as the target. - Example: `openclaw message send --channel telegram --target 123456789 --message "hi"`. ## Troubleshooting **Bot doesn’t respond to non-mention messages in a group:** + - If you set `channels.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) - `openclaw channels status` shows a warning when config expects unmentioned group messages. @@ -606,32 +660,39 @@ The agent sees reactions as **system notifications** in the conversation history - Quick test: `/activation always` (session-only; use config for persistence) **Bot not seeing group messages at all:** + - If `channels.telegram.groups` is set, the group must be listed or use `"*"` - Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF** - Verify bot is actually a member (not just an admin with no read access) - Check gateway logs: `openclaw logs --follow` (look for "skipping group message") **Bot responds to mentions but not `/activation always`:** + - The `/activation` command updates session state but doesn't persist to config - For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false` **Commands like `/status` don't work:** + - Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`) - Commands require authorization even in groups with `groupPolicy: "open"` **Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):** + - Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away. - Upgrade to a OpenClaw build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade. **Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):** + - Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests. - Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway. - Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns. ## Configuration reference (Telegram) + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.telegram.enabled`: enable/disable channel startup. - `channels.telegram.botToken`: bot token (BotFather). - `channels.telegram.tokenFile`: read token from file path. @@ -669,6 +730,7 @@ Provider options: - `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set). Related global options: + - `agents.list[].groupChat.mentionPatterns` (mention gating patterns). - `messages.groupChat.mentionPatterns` (global fallback). - `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`. diff --git a/docs/channels/tlon.md b/docs/channels/tlon.md index 54974cbdc..d082f66b3 100644 --- a/docs/channels/tlon.md +++ b/docs/channels/tlon.md @@ -3,6 +3,7 @@ summary: "Tlon/Urbit support status, capabilities, and configuration" read_when: - Working on Tlon/Urbit channel features --- + # Tlon (plugin) Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can @@ -32,11 +33,11 @@ Details: [Plugins](/plugin) ## Setup -1) Install the Tlon plugin. -2) Gather your ship URL and login code. -3) Configure `channels.tlon`. -4) Restart the gateway. -5) DM the bot or mention it in a group channel. +1. Install the Tlon plugin. +2. Gather your ship URL and login code. +3. Configure `channels.tlon`. +4. Restart the gateway. +5. DM the bot or mention it in a group channel. Minimal config (single account): @@ -47,9 +48,9 @@ Minimal config (single account): enabled: true, ship: "~sampel-palnet", url: "https://your-ship-host", - code: "lidlut-tabwed-pillex-ridrup" - } - } + code: "lidlut-tabwed-pillex-ridrup", + }, + }, } ``` @@ -61,12 +62,9 @@ Auto-discovery is enabled by default. You can also pin channels manually: { channels: { tlon: { - groupChannels: [ - "chat/~host-ship/general", - "chat/~host-ship/support" - ] - } - } + groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"], + }, + }, } ``` @@ -76,9 +74,9 @@ Disable auto-discovery: { channels: { tlon: { - autoDiscoverChannels: false - } - } + autoDiscoverChannels: false, + }, + }, } ``` @@ -90,9 +88,9 @@ DM allowlist (empty = allow all): { channels: { tlon: { - dmAllowlist: ["~zod", "~nec"] - } - } + dmAllowlist: ["~zod", "~nec"], + }, + }, } ``` @@ -107,15 +105,15 @@ Group authorization (restricted by default): channelRules: { "chat/~host-ship/general": { mode: "restricted", - allowedShips: ["~zod", "~nec"] + allowedShips: ["~zod", "~nec"], }, "chat/~host-ship/announcements": { - mode: "open" - } - } - } - } - } + mode: "open", + }, + }, + }, + }, + }, } ``` diff --git a/docs/channels/troubleshooting.md b/docs/channels/troubleshooting.md index 280557ec8..dc0b3a72e 100644 --- a/docs/channels/troubleshooting.md +++ b/docs/channels/troubleshooting.md @@ -4,6 +4,7 @@ read_when: - A channel connects but messages don’t flow - Investigating channel misconfiguration (intents, permissions, privacy mode) --- + # Channel troubleshooting Start with: @@ -16,10 +17,12 @@ openclaw channels status --probe `channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership). ## Channels + - Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting) - Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting) - WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick) ## Telegram quick fixes + - Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting). - Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies). diff --git a/docs/channels/twitch.md b/docs/channels/twitch.md index 571edf814..3bdc06c53 100644 --- a/docs/channels/twitch.md +++ b/docs/channels/twitch.md @@ -3,6 +3,7 @@ summary: "Twitch chat bot configuration and setup" read_when: - Setting up Twitch chat integration for OpenClaw --- + # Twitch (plugin) Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels. @@ -27,17 +28,17 @@ Details: [Plugins](/plugin) ## Quick setup (beginner) -1) Create a dedicated Twitch account for the bot (or use an existing account). -2) Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/) +1. Create a dedicated Twitch account for the bot (or use an existing account). +2. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/) - Select **Bot Token** - Verify scopes `chat:read` and `chat:write` are selected - Copy the **Client ID** and **Access Token** -3) Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ -4) Configure the token: +3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ +4. Configure the token: - Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only) - Or config: `channels.twitch.accessToken` - If both are set, config takes precedence (env fallback is default-account only). -5) Start the gateway. +5. Start the gateway. **⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`. @@ -48,13 +49,13 @@ Minimal config: channels: { twitch: { enabled: true, - username: "openclaw", // Bot's Twitch account - accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var) - clientId: "xyz789...", // Client ID from Token Generator - channel: "vevisk", // Which Twitch channel's chat to join (required) - allowFrom: ["123456789"] // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ - } - } + username: "openclaw", // Bot's Twitch account + accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var) + clientId: "xyz789...", // Client ID from Token Generator + channel: "vevisk", // Which Twitch channel's chat to join (required) + allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/ + }, + }, } ``` @@ -70,6 +71,7 @@ Minimal config: ### Generate credentials Use [Twitch Token Generator](https://twitchtokengenerator.com/): + - Select **Bot Token** - Verify scopes `chat:read` and `chat:write` are selected - Copy the **Client ID** and **Access Token** @@ -79,11 +81,13 @@ No manual app registration needed. Tokens expire after several hours. ### Configure the bot **Env var (default account only):** + ```bash OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123... ``` **Or config:** + ```json5 { channels: { @@ -92,9 +96,9 @@ OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123... username: "openclaw", accessToken: "oauth:abc123...", clientId: "xyz789...", - channel: "vevisk" - } - } + channel: "vevisk", + }, + }, } ``` @@ -106,10 +110,10 @@ If both env and config are set, config takes precedence. { channels: { twitch: { - allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - allowedRoles: ["moderator"] // Or restrict to roles - } - } + allowFrom: ["123456789"], // (recommended) Your Twitch user ID only + allowedRoles: ["moderator"], // Or restrict to roles + }, + }, } ``` @@ -130,9 +134,9 @@ For automatic token refresh, create your own Twitch application at [Twitch Devel channels: { twitch: { clientSecret: "your_client_secret", - refreshToken: "your_refresh_token" - } - } + refreshToken: "your_refresh_token", + }, + }, } ``` @@ -153,17 +157,17 @@ Example (one bot account in two channels): username: "openclaw", accessToken: "oauth:abc123...", clientId: "xyz789...", - channel: "vevisk" + channel: "vevisk", }, channel2: { username: "openclaw", accessToken: "oauth:def456...", clientId: "uvw012...", - channel: "secondchannel" - } - } - } - } + channel: "secondchannel", + }, + }, + }, + }, } ``` @@ -179,11 +183,11 @@ Example (one bot account in two channels): twitch: { accounts: { default: { - allowedRoles: ["moderator", "vip"] - } - } - } - } + allowedRoles: ["moderator", "vip"], + }, + }, + }, + }, } ``` @@ -195,11 +199,11 @@ Example (one bot account in two channels): twitch: { accounts: { default: { - allowFrom: ["123456789", "987654321"] - } - } - } - } + allowFrom: ["123456789", "987654321"], + }, + }, + }, + }, } ``` @@ -214,11 +218,11 @@ Users in `allowFrom` bypass role checks: accounts: { default: { allowFrom: ["123456789"], - allowedRoles: ["moderator"] - } - } - } - } + allowedRoles: ["moderator"], + }, + }, + }, + }, } ``` @@ -232,11 +236,11 @@ By default, `requireMention` is `true`. To disable and respond to all messages: twitch: { accounts: { default: { - requireMention: false - } - } - } - } + requireMention: false, + }, + }, + }, + }, } ``` @@ -258,6 +262,7 @@ openclaw channels status --probe ### Token issues **"Failed to connect" or authentication errors:** + - Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix) - Check token has `chat:read` and `chat:write` scopes - If using token refresh, verify `clientSecret` and `refreshToken` are set @@ -265,18 +270,21 @@ openclaw channels status --probe ### Token refresh not working **Check logs for refresh events:** + ``` Using env token source for mybot Access token refreshed for user 123456 (expires in 14400s) ``` If you see "token refresh disabled (no refresh token)": + - Ensure `clientSecret` is provided - Ensure `refreshToken` is provided ## Config **Account config:** + - `username` - Bot username - `accessToken` - OAuth access token with `chat:read` and `chat:write` - `clientId` - Twitch Client ID (from Token Generator or your app) @@ -291,6 +299,7 @@ If you see "token refresh disabled (no refresh token)": - `requireMention` - Require @mention (default: `true`) **Provider options:** + - `channels.twitch.enabled` - Enable/disable channel startup - `channels.twitch.username` - Bot username (simplified single-account config) - `channels.twitch.accessToken` - OAuth access token (simplified single-account config) @@ -325,28 +334,29 @@ Full example: expiresIn: 14400, obtainmentTimestamp: 1706092800000, allowFrom: ["123456789", "987654321"], - allowedRoles: ["moderator"] - } - } - } - } + allowedRoles: ["moderator"], + }, + }, + }, + }, } ``` ## Tool actions The agent can call `twitch` with action: + - `send` - Send a message to a channel Example: ```json5 { - "action": "twitch", - "params": { - "message": "Hello Twitch!", - "to": "#mychannel" - } + action: "twitch", + params: { + message: "Hello Twitch!", + to: "#mychannel", + }, } ``` diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index cb104fda2..deff5a9e2 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -3,45 +3,51 @@ summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and read_when: - Working on WhatsApp/web channel behavior or inbox routing --- -# WhatsApp (web channel) +# WhatsApp (web channel) Status: WhatsApp Web via Baileys only. Gateway owns the session(s). ## Quick setup (beginner) -1) Use a **separate phone number** if possible (recommended). -2) Configure WhatsApp in `~/.openclaw/openclaw.json`. -3) Run `openclaw channels login` to scan the QR code (Linked Devices). -4) Start the gateway. + +1. Use a **separate phone number** if possible (recommended). +2. Configure WhatsApp in `~/.openclaw/openclaw.json`. +3. Run `openclaw channels login` to scan the QR code (Linked Devices). +4. Start the gateway. Minimal config: + ```json5 { channels: { whatsapp: { dmPolicy: "allowlist", - allowFrom: ["+15551234567"] - } - } + allowFrom: ["+15551234567"], + }, + }, } ``` ## Goals + - Multiple WhatsApp accounts (multi-account) in one Gateway process. - Deterministic routing: replies return to WhatsApp, no model routing. - Model sees enough context to understand quoted replies. ## Config writes + By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`). Disable with: + ```json5 { - channels: { whatsapp: { configWrites: false } } + channels: { whatsapp: { configWrites: false } }, } ``` ## Architecture (who owns what) + - **Gateway** owns the Baileys socket and inbox loop. - **CLI / macOS app** talk to the gateway; no direct Baileys use. - **Active listener** is required for outbound sends; otherwise send fails fast. @@ -51,19 +57,21 @@ Disable with: WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp: ### Dedicated number (recommended) + Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR. **WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there. **Sample config (dedicated number, single-user allowlist):** + ```json5 { channels: { whatsapp: { dmPolicy: "allowlist", - allowFrom: ["+15551234567"] - } - } + allowFrom: ["+15551234567"], + }, + }, } ``` @@ -72,10 +80,12 @@ If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `p `openclaw pairing approve whatsapp ` ### Personal number (fallback) + Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.** When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number. **Sample config (personal number, self-chat):** + ```json { "whatsapp": { @@ -91,6 +101,7 @@ if `messages.responsePrefix` is unset. Set it explicitly to customize or disable the prefix (use `""` to remove it). ### Number sourcing tips + - **Local eSIM** from your country's mobile carrier (most reliable) - Austria: [hot.at](https://www.hot.at) - UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract @@ -101,6 +112,7 @@ the prefix (use `""` to remove it). **Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`. ## Why Not Twilio? + - Early OpenClaw builds supported Twilio’s WhatsApp Business integration. - WhatsApp Business numbers are a poor fit for a personal assistant. - Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages. @@ -108,6 +120,7 @@ the prefix (use `""` to remove it). - Result: unreliable delivery and frequent blocks, so support was removed. ## Login + credentials + - Login command: `openclaw channels login` (QR via Linked Devices). - Multi-account login: `openclaw channels login --account ` (`` = `accountId`). - Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted). @@ -118,6 +131,7 @@ the prefix (use `""` to remove it). - Logged-out socket => error instructs re-link. ## Inbound flow (DM + group) + - WhatsApp events come from `messages.upsert` (Baileys). - Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts. - Status/broadcast chats are ignored. @@ -128,38 +142,44 @@ the prefix (use `""` to remove it). - Your linked WhatsApp number is implicitly trusted, so self messages skip ⁠`channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks. ### Personal-number mode (fallback) + If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above). Behavior: + - Outbound DMs never trigger pairing replies (prevents spamming contacts). - Inbound unknown senders still follow `channels.whatsapp.dmPolicy`. - Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs. - Read receipts sent for non-self-chat DMs. ## Read receipts + By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted. Disable globally: + ```json5 { - channels: { whatsapp: { sendReadReceipts: false } } + channels: { whatsapp: { sendReadReceipts: false } }, } ``` Disable per account: + ```json5 { channels: { whatsapp: { accounts: { - personal: { sendReadReceipts: false } - } - } - } + personal: { sendReadReceipts: false }, + }, + }, + }, } ``` Notes: + - Self-chat mode always skips read receipts. ## WhatsApp FAQ: sending messages + pairing @@ -169,6 +189,7 @@ No. Default DM policy is **pairing**, so unknown senders only get a pairing code **How does pairing work on WhatsApp?** Pairing is a DM gate for unknown senders: + - First DM from a new sender returns a short code (message is not processed). - Approve with: `openclaw pairing approve whatsapp ` (list with `openclaw pairing list whatsapp`). - Codes expire after 1 hour; pending requests are capped at 3 per channel. @@ -180,6 +201,7 @@ Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`. ## Message normalization (what the model sees) + - `Body` is the current message body with envelope. - Quoted reply context is **always appended**: ``` @@ -195,6 +217,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted - `` ## Groups + - Groups map to `agent::whatsapp:group:` sessions. - Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`). - Activation modes: @@ -203,7 +226,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted - `/activation mention|always` is owner-only and must be sent as a standalone message. - Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset). - **History injection** (pending-only): - - Recent *unprocessed* messages (default 50) inserted under: + - Recent _unprocessed_ messages (default 50) inserted under: `[Chat messages since your last reply - for context]` (messages already in the session are not re-injected) - Current message under: `[Current message - respond to this]` @@ -211,6 +234,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted - Group metadata cached 5 min (subject + participants). ## Reply delivery (threading) + - WhatsApp Web sends standard messages (no quoted reply threading in the current gateway). - Reply tags are ignored on this channel. @@ -219,6 +243,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received. **Configuration:** + ```json { "whatsapp": { @@ -232,6 +257,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately ``` **Options:** + - `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled. - `direct` (boolean, default: `true`): Send reactions in direct/DM chats. - `group` (string, default: `"mentions"`): Group chat behavior: @@ -240,6 +266,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately - `"never"`: Never react in groups **Per-account override:** + ```json { "whatsapp": { @@ -257,6 +284,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately ``` **Behavior notes:** + - Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies. - In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions). - Fire-and-forget: reaction failures are logged but don't prevent the bot from replying. @@ -264,18 +292,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately - WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead. ## Agent tool (reactions) + - Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`). - Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account). - Reaction removal semantics: see [/tools/reactions](/tools/reactions). - Tool gating: `channels.whatsapp.actions.reactions` (default: enabled). ## Limits + - Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000). - Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking. - Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB). - Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB). ## Outbound send (text + media) + - Uses active web listener; error if gateway not running. - Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`). - Media: @@ -288,17 +319,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately - Gateway: `send` params include `gifPlayback: true` ## Voice notes (PTT audio) + WhatsApp sends audio as **voice notes** (PTT bubble). + - Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`. - `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note). ## Media limits + optimization + - Default outbound cap: 5 MB (per media item). - Override: `agents.defaults.mediaMaxMb`. - Images are auto-optimized to JPEG under cap (resize + quality sweep). - Oversize media => error; media reply falls back to text warning. ## Heartbeats + - **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s). - **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally via `agents.defaults.heartbeat` (fallback when no per-agent entries are set). @@ -306,12 +341,14 @@ WhatsApp sends audio as **voice notes** (PTT bubble). - Delivery defaults to the last used channel (or configured target). ## Reconnect behavior + - Backoff policy: `web.reconnect`: - `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`. - If maxAttempts reached, web monitoring stops (degraded). - Logged-out => stop and require re-link. ## Config quick map + - `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled). - `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number). - `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames). @@ -343,6 +380,7 @@ WhatsApp sends audio as **voice notes** (PTT bubble). - `web.reconnect.*` ## Logs + troubleshooting + - Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`. - Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable). - Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting). @@ -350,13 +388,16 @@ WhatsApp sends audio as **voice notes** (PTT bubble). ## Troubleshooting (quick) **Not linked / QR login required** + - Symptom: `channels status` shows `linked: false` or warns “Not linked”. - Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices). **Linked but disconnected / reconnect loop** + - Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”. - Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`. **Bun runtime** + - Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun. Run the gateway with **Node**. (See Getting Started runtime note.) diff --git a/docs/channels/zalo.md b/docs/channels/zalo.md index 407af50f0..e4c2f8c4b 100644 --- a/docs/channels/zalo.md +++ b/docs/channels/zalo.md @@ -3,43 +3,50 @@ summary: "Zalo bot support status, capabilities, and configuration" read_when: - Working on Zalo features or webhooks --- + # Zalo (Bot API) Status: experimental. Direct messages only; groups coming soon per Zalo docs. ## Plugin required + Zalo ships as a plugin and is not bundled with the core install. + - Install via CLI: `openclaw plugins install @openclaw/zalo` - Or select **Zalo** during onboarding and confirm the install prompt - Details: [Plugins](/plugin) ## Quick setup (beginner) -1) Install the Zalo plugin: + +1. Install the Zalo plugin: - From a source checkout: `openclaw plugins install ./extensions/zalo` - From npm (if published): `openclaw plugins install @openclaw/zalo` - Or pick **Zalo** in onboarding and confirm the install prompt -2) Set the token: +2. Set the token: - Env: `ZALO_BOT_TOKEN=...` - Or config: `channels.zalo.botToken: "..."`. -3) Restart the gateway (or finish onboarding). -4) DM access is pairing by default; approve the pairing code on first contact. +3. Restart the gateway (or finish onboarding). +4. DM access is pairing by default; approve the pairing code on first contact. Minimal config: + ```json5 { channels: { zalo: { enabled: true, botToken: "12345689:abc-xyz", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` ## What it is + Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations. It is a good fit for support or notifications where you want deterministic routing back to Zalo. + - A Zalo Bot API channel owned by the Gateway. - Deterministic routing: replies go back to Zalo; the model never chooses channels. - DMs share the agent's main session. @@ -48,11 +55,13 @@ It is a good fit for support or notifications where you want deterministic routi ## Setup (fast path) ### 1) Create a bot token (Zalo Bot Platform) -1) Go to **https://bot.zaloplatforms.com** and sign in. -2) Create a new bot and configure its settings. -3) Copy the bot token (format: `12345689:abc-xyz`). + +1. Go to **https://bot.zaloplatforms.com** and sign in. +2. Create a new bot and configure its settings. +3. Copy the bot token (format: `12345689:abc-xyz`). ### 2) Configure the token (env or config) + Example: ```json5 @@ -61,9 +70,9 @@ Example: zalo: { enabled: true, botToken: "12345689:abc-xyz", - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` @@ -71,15 +80,17 @@ Env option: `ZALO_BOT_TOKEN=...` (works for the default account only). Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`. -3) Restart the gateway. Zalo starts when a token is resolved (env or config). -4) DM access defaults to pairing. Approve the code when the bot is first contacted. +3. Restart the gateway. Zalo starts when a token is resolved (env or config). +4. DM access defaults to pairing. Approve the code when the bot is first contacted. ## How it works (behavior) + - Inbound messages are normalized into the shared channel envelope with media placeholders. - Replies always route back to the same Zalo chat. - Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`. ## Limits + - Outbound text is chunked to 2000 characters (Zalo API limit). - Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5). - Streaming is blocked by default due to the 2000 char limit making streaming less useful. @@ -87,6 +98,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and ## Access control (DMs) ### DM access + - Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour). - Approve via: - `openclaw pairing list zalo` @@ -95,6 +107,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and - `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available). ## Long-polling vs webhook + - Default: long-polling (no public URL required). - Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`. - The webhook secret must be 8-256 characters. @@ -105,44 +118,51 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and **Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs. ## Supported message types + - **Text messages**: Full support with 2000 character chunking. - **Image messages**: Download and process inbound images; send images via `sendPhoto`. - **Stickers**: Logged but not fully processed (no agent response). - **Unsupported types**: Logged (e.g., messages from protected users). ## Capabilities -| Feature | Status | -|---------|--------| -| Direct messages | ✅ Supported | -| Groups | ❌ Coming soon (per Zalo docs) | -| Media (images) | ✅ Supported | -| Reactions | ❌ Not supported | -| Threads | ❌ Not supported | -| Polls | ❌ Not supported | -| Native commands | ❌ Not supported | -| Streaming | ⚠️ Blocked (2000 char limit) | + +| Feature | Status | +| --------------- | ------------------------------ | +| Direct messages | ✅ Supported | +| Groups | ❌ Coming soon (per Zalo docs) | +| Media (images) | ✅ Supported | +| Reactions | ❌ Not supported | +| Threads | ❌ Not supported | +| Polls | ❌ Not supported | +| Native commands | ❌ Not supported | +| Streaming | ⚠️ Blocked (2000 char limit) | ## Delivery targets (CLI/cron) + - Use a chat id as the target. - Example: `openclaw message send --channel zalo --target 123456789 --message "hi"`. ## Troubleshooting **Bot doesn't respond:** + - Check that the token is valid: `openclaw channels status --probe` - Verify the sender is approved (pairing or allowFrom) - Check gateway logs: `openclaw logs --follow` **Webhook not receiving events:** + - Ensure webhook URL uses HTTPS - Verify secret token is 8-256 characters - Confirm the gateway HTTP endpoint is reachable on the configured path - Check that getUpdates polling is not running (they're mutually exclusive) ## Configuration reference (Zalo) + Full configuration: [Configuration](/gateway/configuration) Provider options: + - `channels.zalo.enabled`: enable/disable channel startup. - `channels.zalo.botToken`: bot token from Zalo Bot Platform. - `channels.zalo.tokenFile`: read token from file path. @@ -155,6 +175,7 @@ Provider options: - `channels.zalo.proxy`: proxy URL for API requests. Multi-account options: + - `channels.zalo.accounts..botToken`: per-account token. - `channels.zalo.accounts..tokenFile`: per-account token file. - `channels.zalo.accounts..name`: display name. diff --git a/docs/channels/zalouser.md b/docs/channels/zalouser.md index c6e0e28b6..b5e91b39e 100644 --- a/docs/channels/zalouser.md +++ b/docs/channels/zalouser.md @@ -4,6 +4,7 @@ read_when: - Setting up Zalo Personal for OpenClaw - Debugging Zalo Personal login or message flow --- + # Zalo Personal (unofficial) Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`. @@ -11,47 +12,54 @@ Status: experimental. This integration automates a **personal Zalo account** via > **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk. ## Plugin required + Zalo Personal ships as a plugin and is not bundled with the core install. + - Install via CLI: `openclaw plugins install @openclaw/zalouser` - Or from a source checkout: `openclaw plugins install ./extensions/zalouser` - Details: [Plugins](/plugin) ## Prerequisite: zca-cli + The Gateway machine must have the `zca` binary available in `PATH`. - Verify: `zca --version` - If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs). ## Quick setup (beginner) -1) Install the plugin (see above). -2) Login (QR, on the Gateway machine): + +1. Install the plugin (see above). +2. Login (QR, on the Gateway machine): - `openclaw channels login --channel zalouser` - Scan the QR code in the terminal with the Zalo mobile app. -3) Enable the channel: +3. Enable the channel: ```json5 { channels: { zalouser: { enabled: true, - dmPolicy: "pairing" - } - } + dmPolicy: "pairing", + }, + }, } ``` -4) Restart the Gateway (or finish onboarding). -5) DM access defaults to pairing; approve the pairing code on first contact. +4. Restart the Gateway (or finish onboarding). +5. DM access defaults to pairing; approve the pairing code on first contact. ## What it is + - Uses `zca listen` to receive inbound messages. - Uses `zca msg ...` to send replies (text/media/link). - Designed for “personal account” use cases where Zalo Bot API is not available. ## Naming + Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration. ## Finding IDs (directory) + Use the directory CLI to discover peers/groups and their IDs: ```bash @@ -61,18 +69,22 @@ openclaw directory groups list --channel zalouser --query "work" ``` ## Limits + - Outbound text is chunked to ~2000 characters (Zalo client limits). - Streaming is blocked by default. ## Access control (DMs) + `channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`). `channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available. Approve via: + - `openclaw pairing list zalouser` - `openclaw pairing approve zalouser ` ## Group access (optional) + - Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset. - Restrict to an allowlist with: - `channels.zalouser.groupPolicy = "allowlist"` @@ -82,6 +94,7 @@ Approve via: - On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed. Example: + ```json5 { channels: { @@ -89,14 +102,15 @@ Example: groupPolicy: "allowlist", groups: { "123456789": { allow: true }, - "Work Chat": { allow: true } - } - } - } + "Work Chat": { allow: true }, + }, + }, + }, } ``` ## Multi-account + Accounts map to zca profiles. Example: ```json5 @@ -106,18 +120,20 @@ Accounts map to zca profiles. Example: enabled: true, defaultAccount: "default", accounts: { - work: { enabled: true, profile: "work" } - } - } - } + work: { enabled: true, profile: "work" }, + }, + }, + }, } ``` ## Troubleshooting **`zca` not found:** + - Install zca-cli and ensure it’s on `PATH` for the Gateway process. **Login doesn’t stick:** + - `openclaw channels status --probe` - Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser` diff --git a/docs/cli/acp.md b/docs/cli/acp.md index 7ae571fc8..5e335d365 100644 --- a/docs/cli/acp.md +++ b/docs/cli/acp.md @@ -110,9 +110,12 @@ To target a specific Gateway or agent: "command": "openclaw", "args": [ "acp", - "--url", "wss://gateway-host:18789", - "--token", "", - "--session", "agent:design:main" + "--url", + "wss://gateway-host:18789", + "--token", + "", + "--session", + "agent:design:main" ], "env": {} } diff --git a/docs/cli/agent.md b/docs/cli/agent.md index 682deb204..778516eac 100644 --- a/docs/cli/agent.md +++ b/docs/cli/agent.md @@ -10,6 +10,7 @@ Run an agent turn via the Gateway (use `--local` for embedded). Use `--agent ` to target a configured agent directly. Related: + - Agent send tool: [Agent send](/tools/agent-send) ## Examples diff --git a/docs/cli/agents.md b/docs/cli/agents.md index 694871aaf..88a0a8272 100644 --- a/docs/cli/agents.md +++ b/docs/cli/agents.md @@ -9,6 +9,7 @@ read_when: Manage isolated agents (workspaces + auth + routing). Related: + - Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent) - Agent workspace: [Agent workspace](/concepts/agent-workspace) @@ -25,6 +26,7 @@ openclaw agents delete work ## Identity files Each agent workspace can include an `IDENTITY.md` at the workspace root: + - Example path: `~/.openclaw/workspace/IDENTITY.md` - `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`) @@ -33,6 +35,7 @@ Avatar paths resolve relative to the workspace root. ## Set identity `set-identity` writes fields into `agents.list[].identity`: + - `name` - `theme` - `emoji` @@ -62,10 +65,10 @@ Config sample: name: "OpenClaw", theme: "space lobster", emoji: "🦞", - avatar: "avatars/openclaw.png" - } - } - ] - } + avatar: "avatars/openclaw.png", + }, + }, + ], + }, } ``` diff --git a/docs/cli/approvals.md b/docs/cli/approvals.md index 2e3b60015..136072a31 100644 --- a/docs/cli/approvals.md +++ b/docs/cli/approvals.md @@ -11,6 +11,7 @@ Manage exec approvals for the **local host**, **gateway host**, or a **node host By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node. Related: + - Exec approvals: [Exec approvals](/tools/exec-approvals) - Nodes: [Nodes](/nodes) diff --git a/docs/cli/browser.md b/docs/cli/browser.md index a0f35f511..123db7eff 100644 --- a/docs/cli/browser.md +++ b/docs/cli/browser.md @@ -11,6 +11,7 @@ read_when: Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing). Related: + - Browser tool + API: [Browser tool](/tools/browser) - Chrome extension relay: [Chrome extension](/tools/chrome-extension) @@ -34,6 +35,7 @@ openclaw browser --browser-profile openclaw snapshot ## Profiles Profiles are named browser routing configs. In practice: + - `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir). - `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay. diff --git a/docs/cli/channels.md b/docs/cli/channels.md index 26657e0c5..0bb151fce 100644 --- a/docs/cli/channels.md +++ b/docs/cli/channels.md @@ -10,6 +10,7 @@ read_when: Manage chat channel accounts and their runtime status on the Gateway. Related docs: + - Channel guides: [Channels](/channels/index) - Gateway configuration: [Configuration](/gateway/configuration) @@ -56,6 +57,7 @@ openclaw channels capabilities --channel discord --target channel:123 ``` Notes: + - `--channel` is optional; omit it to list every channel (including extensions). - `--target` accepts `channel:` or a raw numeric channel id and only applies to Discord. - Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; MS Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`. @@ -71,5 +73,6 @@ openclaw channels resolve --channel matrix "Project Room" ``` Notes: + - Use `--kind user|group|auto` to force the target type. - Resolution prefers active matches when multiple entries share the same name. diff --git a/docs/cli/configure.md b/docs/cli/configure.md index 91a2bfb30..d820daf9f 100644 --- a/docs/cli/configure.md +++ b/docs/cli/configure.md @@ -15,10 +15,12 @@ Tip: `openclaw config` without a subcommand opens the same wizard. Use `openclaw config get|set|unset` for non-interactive edits. Related: + - Gateway configuration reference: [Configuration](/gateway/configuration) - Config CLI: [Config](/cli/config) Notes: + - Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need. - Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible. diff --git a/docs/cli/cron.md b/docs/cli/cron.md index 977339ee9..4cabcebb8 100644 --- a/docs/cli/cron.md +++ b/docs/cli/cron.md @@ -10,6 +10,7 @@ read_when: Manage cron jobs for the Gateway scheduler. Related: + - Cron jobs: [Cron jobs](/automation/cron-jobs) Tip: run `openclaw cron --help` for the full command surface. diff --git a/docs/cli/dashboard.md b/docs/cli/dashboard.md index 03a6d7c74..b63aa8c6f 100644 --- a/docs/cli/dashboard.md +++ b/docs/cli/dashboard.md @@ -13,4 +13,3 @@ Open the Control UI using your current auth. openclaw dashboard openclaw dashboard --no-open ``` - diff --git a/docs/cli/directory.md b/docs/cli/directory.md index 82c52a3a8..8caec8147 100644 --- a/docs/cli/directory.md +++ b/docs/cli/directory.md @@ -10,11 +10,13 @@ read_when: Directory lookups for channels that support it (contacts/peers, groups, and “me”). ## Common flags + - `--channel `: channel id/alias (required when multiple channels are configured; auto when only one is configured) - `--account `: account id (default: channel default) - `--json`: output JSON ## Notes + - `directory` is meant to help you find IDs you can paste into other commands (especially `openclaw message send --target ...`). - For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory. - Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting. diff --git a/docs/cli/dns.md b/docs/cli/dns.md index b92aed918..cfe03dad8 100644 --- a/docs/cli/dns.md +++ b/docs/cli/dns.md @@ -10,6 +10,7 @@ read_when: DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS. Related: + - Gateway discovery: [Discovery](/gateway/discovery) - Wide-area discovery config: [Configuration](/gateway/configuration) diff --git a/docs/cli/docs.md b/docs/cli/docs.md index a7febc00b..db2f4c397 100644 --- a/docs/cli/docs.md +++ b/docs/cli/docs.md @@ -12,4 +12,3 @@ Search the live docs index. openclaw docs browser extension openclaw docs sandbox allowHostControl ``` - diff --git a/docs/cli/doctor.md b/docs/cli/doctor.md index 24e1e8aa0..df75d932c 100644 --- a/docs/cli/doctor.md +++ b/docs/cli/doctor.md @@ -10,6 +10,7 @@ read_when: Health checks + quick fixes for the gateway and channels. Related: + - Troubleshooting: [Troubleshooting](/gateway/troubleshooting) - Security audit: [Security](/gateway/security) @@ -22,6 +23,7 @@ openclaw doctor --deep ``` Notes: + - Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts. - `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal. diff --git a/docs/cli/gateway.md b/docs/cli/gateway.md index 84d16113c..8630ef69d 100644 --- a/docs/cli/gateway.md +++ b/docs/cli/gateway.md @@ -13,6 +13,7 @@ The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks). Subcommands in this page live under `openclaw gateway …`. Related docs: + - [/gateway/bonjour](/gateway/bonjour) - [/gateway/discovery](/gateway/discovery) - [/gateway/configuration](/gateway/configuration) @@ -32,6 +33,7 @@ openclaw gateway run ``` Notes: + - By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs. - Binding beyond loopback without auth is blocked (safety guardrail). - `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update). @@ -62,11 +64,13 @@ Notes: All query commands use WebSocket RPC. Output modes: + - Default: human-readable (colored in TTY). - `--json`: machine-readable JSON (no styling/spinner). - `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout. Shared options (where supported): + - `--url `: Gateway WebSocket URL. - `--token `: Gateway token. - `--password `: Gateway password. @@ -89,6 +93,7 @@ openclaw gateway status --json ``` Options: + - `--url `: override the probe URL. - `--token `: token auth for the probe. - `--password `: password auth for the probe. @@ -99,6 +104,7 @@ Options: ### `gateway probe` `gateway probe` is the “debug everything” command. It always probes: + - your configured remote gateway (if set), and - localhost (loopback) **even if remote is configured**. @@ -120,11 +126,13 @@ openclaw gateway probe --ssh user@gateway-host ``` Options: + - `--ssh `: `user@host` or `user@host:port` (port defaults to `22`). - `--ssh-identity `: identity file. - `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only). Config (optional, used as defaults): + - `gateway.remote.sshTarget` - `gateway.remote.sshIdentity` @@ -148,6 +156,7 @@ openclaw gateway uninstall ``` Notes: + - `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`. - Lifecycle commands accept `--json` for scripting. @@ -161,6 +170,7 @@ Notes: Only gateways with Bonjour discovery enabled (default) advertise the beacon. Wide-Area discovery records include (TXT): + - `role` (gateway role hint) - `transport` (transport hint, e.g. `gateway`) - `gatewayPort` (WebSocket port, usually `18789`) @@ -176,6 +186,7 @@ openclaw gateway discover ``` Options: + - `--timeout `: per-command timeout (browse/resolve); default `2000`. - `--json`: machine-readable output (also disables styling/spinner). diff --git a/docs/cli/health.md b/docs/cli/health.md index a8f219eee..2e04f2294 100644 --- a/docs/cli/health.md +++ b/docs/cli/health.md @@ -15,5 +15,6 @@ openclaw health --verbose ``` Notes: + - `--verbose` runs live probes and prints per-account timings when multiple accounts are configured. - Output includes per-agent session stores when multiple agents are configured. diff --git a/docs/cli/hooks.md b/docs/cli/hooks.md index cc0e00f3b..80025c383 100644 --- a/docs/cli/hooks.md +++ b/docs/cli/hooks.md @@ -10,6 +10,7 @@ read_when: Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup). Related: + - Hooks: [Hooks](/hooks) - Plugin hooks: [Plugins](/plugin#plugin-hooks) @@ -22,6 +23,7 @@ openclaw hooks list List all discovered hooks from workspace, managed, and bundled directories. **Options:** + - `--eligible`: Show only eligible hooks (requirements met) - `--json`: Output as JSON - `-v, --verbose`: Show detailed information including missing requirements @@ -63,9 +65,11 @@ openclaw hooks info Show detailed information about a specific hook. **Arguments:** + - ``: Hook name (e.g., `session-memory`) **Options:** + - `--json`: Output as JSON **Example:** @@ -101,6 +105,7 @@ openclaw hooks check Show summary of hook eligibility status (how many are ready vs. not ready). **Options:** + - `--json`: Output as JSON **Example output:** @@ -125,6 +130,7 @@ Enable a specific hook by adding it to your config (`~/.openclaw/config.json`). can’t be enabled/disabled here. Enable/disable the plugin instead. **Arguments:** + - ``: Hook name (e.g., `session-memory`) **Example:** @@ -140,11 +146,13 @@ openclaw hooks enable session-memory ``` **What it does:** + - Checks if hook exists and is eligible - Updates `hooks.internal.entries..enabled = true` in your config - Saves config to disk **After enabling:** + - Restart the gateway so hooks reload (menu bar app restart on macOS, or restart your gateway process in dev). ## Disable a Hook @@ -156,6 +164,7 @@ openclaw hooks disable Disable a specific hook by updating your config. **Arguments:** + - ``: Hook name (e.g., `command-logger`) **Example:** @@ -171,6 +180,7 @@ openclaw hooks disable command-logger ``` **After disabling:** + - Restart the gateway so hooks reload ## Install Hooks @@ -182,11 +192,13 @@ openclaw hooks install Install a hook pack from a local folder/archive or npm. **What it does:** + - Copies the hook pack into `~/.openclaw/hooks/` - Enables the installed hooks in `hooks.internal.entries.*` - Records the install under `hooks.internal.installs` **Options:** + - `-l, --link`: Link a local directory instead of copying (adds it to `hooks.internal.load.extraDirs`) **Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar` @@ -217,6 +229,7 @@ openclaw hooks update --all Update installed hook packs (npm installs only). **Options:** + - `--all`: Update all tracked hook packs - `--dry-run`: Show what would change without writing diff --git a/docs/cli/index.md b/docs/cli/index.md index 03770965f..61b10311e 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -269,6 +269,7 @@ Vector search over `MEMORY.md` + `memory/*.md`: Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands). Highlights: + - `/status` for quick diagnostics. - `/config` for persisted config changes. - `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`). @@ -276,9 +277,11 @@ Highlights: ## Setup + onboarding ### `setup` + Initialize config + workspace. Options: + - `--workspace `: agent workspace path (default `~/.openclaw/workspace`). - `--wizard`: run the onboarding wizard. - `--non-interactive`: run wizard without prompts. @@ -289,9 +292,11 @@ Options: Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`). ### `onboard` + Interactive wizard to set up gateway, workspace, and skills. Options: + - `--workspace ` - `--reset` (reset config + credentials + sessions + workspace before wizard) - `--non-interactive` @@ -332,21 +337,26 @@ Options: - `--json` ### `configure` + Interactive configuration wizard (models, channels, skills, gateway). ### `config` + Non-interactive config helpers (get/set/unset). Running `openclaw config` with no subcommand launches the wizard. Subcommands: + - `config get `: print a config value (dot/bracket path). - `config set `: set a value (JSON5 or raw string). - `config unset `: remove a value. ### `doctor` + Health checks + quick fixes (config + gateway + legacy services). Options: + - `--no-workspace-suggestions`: disable workspace memory hints. - `--yes`: accept defaults without prompting (headless). - `--non-interactive`: skip prompts; apply safe migrations only. @@ -355,9 +365,11 @@ Options: ## Channel helpers ### `channels` + Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams). Subcommands: + - `channels list`: show configured channels and auth profiles. - `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `openclaw health` or `openclaw status --deep` for gateway health probes). - Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `openclaw doctor`). @@ -368,24 +380,29 @@ Subcommands: - `channels logout`: log out of a channel session (if supported). Common options: + - `--channel `: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` - `--account `: channel account id (default `default`) - `--name