Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcedeb4e1f | |||
| f076eba98a | |||
| f3bd6bf342 | |||
| c29c9a1e3e | |||
| 7a384ea07c | |||
| 5fc866e8fe | |||
| 437535ee94 | |||
| 3b929ff843 | |||
| 93737ee152 | |||
| 765626b492 | |||
| 42b8fce4e5 | |||
| c27294133e | |||
| 94095386b3 | |||
| 7a524e8667 | |||
| 876bbb742a | |||
| ef7971e3a4 | |||
| 9d742ba51f | |||
| 386d21b6d1 | |||
| c8afa8207c | |||
| d0e21f05a6 | |||
| 11f039ef85 | |||
| e90e3ba954 | |||
| 0de7852d46 | |||
| 834663dfef | |||
| a72d7a9f36 | |||
| 67e57e7c99 | |||
| 4c98d6c121 | |||
| 174a1cb68a | |||
| a4d56bd06e |
@@ -106,6 +106,7 @@
|
||||
## 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/clawdbot && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
|
||||
- When working on a GitHub Issue or PR, print the full URL at the end of the task.
|
||||
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
|
||||
- Never update the Carbon dependency.
|
||||
|
||||
+63
-64
@@ -2,81 +2,80 @@
|
||||
|
||||
Docs: https://docs.clawd.bot
|
||||
|
||||
## 2026.1.23 (Unreleased)
|
||||
|
||||
### Highlights
|
||||
- TTS: allow model-driven TTS tags by default for expressive audio replies (laughter, singing cues, etc.).
|
||||
## 2026.1.24
|
||||
|
||||
### Changes
|
||||
- Gateway: add /tools/invoke HTTP endpoint for direct tool calls and document it. (#1575) Thanks @vignesh07.
|
||||
- Agents: keep system prompt time zone-only and move current time to `session_status` for better cache hits.
|
||||
- Agents: remove redundant bash tool alias from tool registration/display. (#1571) Thanks @Takhoffman.
|
||||
- Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node).
|
||||
- Heartbeat: add per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer.
|
||||
- Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07.
|
||||
- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it.
|
||||
- CLI: add live auth probes to `clawdbot models status` for per-profile verification.
|
||||
- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`.
|
||||
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3.
|
||||
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc.
|
||||
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc.
|
||||
- Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).
|
||||
- Docs: add verbose installer troubleshooting guidance.
|
||||
- Docs: update Fly.io guide notes.
|
||||
|
||||
### Fixes
|
||||
- Web UI: hide internal `message_id` hints in chat bubbles.
|
||||
- Heartbeat: normalize target identifiers for consistent routing.
|
||||
|
||||
## 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.clawd.bot/tts
|
||||
- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.clawd.bot/gateway/tools-invoke-http-api
|
||||
- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.clawd.bot/gateway/heartbeat
|
||||
- Deploy: add Fly.io deployment support + guide. (#1570) https://docs.clawd.bot/platforms/fly
|
||||
- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.clawd.bot/channels/tlon
|
||||
|
||||
### Changes
|
||||
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.clawd.bot/multi-agent-sandbox-tools
|
||||
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.clawd.bot/bedrock
|
||||
- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.clawd.bot/cli/system
|
||||
- CLI: add live auth probes to `clawdbot models status` for per-profile verification. (commit 40181afde) https://docs.clawd.bot/cli/models
|
||||
- CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. (commit 2c85b1b40)
|
||||
- Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node). (commit c3cb26f7c)
|
||||
- Plugins: add optional `llm-task` JSON-only tool for workflows. (#1498) Thanks @vignesh07. https://docs.clawd.bot/tools/llm-task
|
||||
- Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0.
|
||||
- Tlon: add Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a.
|
||||
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt.
|
||||
- TTS: move Telegram TTS into core with auto-replies, commands, and gateway methods. (#1559) Thanks @Glucksberg.
|
||||
- Agents: keep system prompt time zone-only and move current time to `session_status` for better cache hits. (commit 66eec295b)
|
||||
- Agents: remove redundant bash tool alias from tool registration/display. (#1571) Thanks @Takhoffman.
|
||||
- Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc. https://docs.clawd.bot/automation/cron-vs-heartbeat
|
||||
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.clawd.bot/gateway/heartbeat
|
||||
|
||||
### Fixes
|
||||
- Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518)
|
||||
- Gateway: compare Linux process start time to avoid PID recycling lock loops; keep locks unless stale. (#1572) Thanks @steipete.
|
||||
- Messaging: mirror outbound sends into target session keys (threads + dmScope) and create session entries on send. (#1520)
|
||||
- Sessions: normalize session key casing to lowercase for consistent routing.
|
||||
- BlueBubbles: normalize group session keys for outbound mirroring. (#1520)
|
||||
- Slack: match auto-thread mirroring channel ids case-insensitively. (#1520)
|
||||
- Gateway: lowercase provided session keys when mirroring outbound sends. (#1520)
|
||||
- Skills: gate bird Homebrew install to macOS. (#1569) Thanks @bradleypriest.
|
||||
- Slack: honor open groupPolicy for unlisted channels in message + slash gating. (#1563) Thanks @itsjaydesu.
|
||||
- Agents: show tool error fallback when the last assistant turn only invoked tools (prevents silent stops).
|
||||
- Agents: ignore IDENTITY.md template placeholders when parsing identity to avoid placeholder replies. (#1556)
|
||||
- Agents: drop orphaned OpenAI Responses reasoning blocks on model switches. (#1562) Thanks @roshanasingh4.
|
||||
- Docker: update gateway command in docker-compose and Hetzner guide. (#1514)
|
||||
- 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)
|
||||
- Sessions: reject array-backed session stores to prevent silent wipes. (#1469)
|
||||
- Voice wake: auto-save wake words on blur/submit across iOS/Android and align limits with macOS.
|
||||
- UI: keep the Control UI sidebar visible while scrolling long pages. (#1515) Thanks @pookNast.
|
||||
- UI: cache Control UI markdown rendering + memoize chat text extraction to reduce Safari typing jank.
|
||||
- Tailscale: retry serve/funnel with sudo only for permission errors and keep original failure details. (#1551) Thanks @sweepies.
|
||||
- Agents: add CLI log hint to "agent failed before reply" messages. (#1550) Thanks @sweepies.
|
||||
- Discord: limit autoThread mention bypass to bot-owned threads; keep ack reactions mention-gated. (#1511) Thanks @pvoo.
|
||||
- Discord: retry rate-limited allowlist resolution + command deploy to avoid gateway crashes.
|
||||
- Mentions: ignore mentionPattern matches when another explicit mention is present in group chats (Slack/Discord/Telegram/WhatsApp).
|
||||
- Gateway: compare Linux process start time to avoid PID recycling lock loops; keep locks unless stale. (#1572) Thanks @steipete.
|
||||
- Gateway: accept null optional fields in exec approval requests. (#1511) Thanks @pvoo.
|
||||
- Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts).
|
||||
- TUI: forward unknown slash commands (for example, `/context`) to the Gateway.
|
||||
- TUI: include Gateway slash commands in autocomplete and `/help`.
|
||||
- CLI: skip usage lines in `clawdbot models status` when provider usage is unavailable.
|
||||
- CLI: suppress diagnostic session/run noise during auth probes.
|
||||
- CLI: hide auth probe timeout warnings from embedded runs.
|
||||
- CLI: render auth probe results as a table in `clawdbot models status`.
|
||||
- CLI: suppress probe-only embedded logs unless `--verbose` is set.
|
||||
- CLI: move auth probe errors below the table to reduce wrapping.
|
||||
- CLI: prevent ANSI color bleed when table cells wrap.
|
||||
- CLI: explain when auth profiles are excluded by auth.order in probe details.
|
||||
- CLI: drop the em dash when the banner tagline wraps to a second line.
|
||||
- CLI: inline auth probe errors in status rows to reduce wrapping.
|
||||
- Telegram: render markdown in media captions. (#1478)
|
||||
- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests.
|
||||
- Agents: trigger model fallback when auth profiles are all in cooldown or unavailable. (#1522)
|
||||
- Daemon: use platform PATH delimiters when building minimal service paths.
|
||||
- Tests: skip embedded runner ordering assertion on Windows to avoid CI timeouts.
|
||||
- Exec approvals: persist allowlist entry ids to keep macOS allowlist rows stable. (#1521) Thanks @ngutman.
|
||||
- Exec: honor tools.exec ask/security defaults for elevated approvals (avoid unwanted prompts). (commit 5662a9cdf)
|
||||
- Daemon: use platform PATH delimiters when building minimal service paths. (commit a4e57d3ac)
|
||||
- Linux: include env-configured user bin roots in systemd PATH and align PATH audits. (#1512) Thanks @robbyczgw-cla.
|
||||
- TUI: render Gateway slash-command replies as system output (for example, `/context`).
|
||||
- Tailscale: retry serve/funnel with sudo only for permission errors and keep original failure details. (#1551) Thanks @sweepies.
|
||||
- Docker: update gateway command in docker-compose and Hetzner guide. (#1514)
|
||||
- Agents: show tool error fallback when the last assistant turn only invoked tools (prevents silent stops). (commit 8ea8801d0)
|
||||
- Agents: ignore IDENTITY.md template placeholders when parsing identity. (#1556)
|
||||
- Agents: drop orphaned OpenAI Responses reasoning blocks on model switches. (#1562) Thanks @roshanasingh4.
|
||||
- Agents: add CLI log hint to "agent failed before reply" messages. (#1550) Thanks @sweepies.
|
||||
- Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)
|
||||
- Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467)
|
||||
- Agents: honor enqueue overrides for embedded runs to avoid queue deadlocks in tests. (commit 084002998)
|
||||
- Slack: honor open groupPolicy for unlisted channels in message + slash gating. (#1563) Thanks @itsjaydesu.
|
||||
- Discord: limit autoThread mention bypass to bot-owned threads; keep ack reactions mention-gated. (#1511) Thanks @pvoo.
|
||||
- Discord: retry rate-limited allowlist resolution + command deploy to avoid gateway crashes. (commit f70ac0c7c)
|
||||
- Mentions: ignore mentionPattern matches when another explicit mention is present in group chats (Slack/Discord/Telegram/WhatsApp). (commit d905ca0e0)
|
||||
- Telegram: render markdown in media captions. (#1478)
|
||||
- MS Teams: remove `.default` suffix from Graph scopes and Bot Framework probe scopes. (#1507, #1574) Thanks @Evizero.
|
||||
- Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)
|
||||
- Voice wake: auto-save wake words on blur/submit across iOS/Android and align limits with macOS. (commit 69f645c66)
|
||||
- UI: keep the Control UI sidebar visible while scrolling long pages. (#1515) Thanks @pookNast.
|
||||
- UI: cache Control UI markdown rendering + memoize chat text extraction to reduce Safari typing jank. (commit d57cb2e1a)
|
||||
- TUI: forward unknown slash commands, include Gateway commands in autocomplete, and render slash replies as system output. (commit 1af227b61, commit 8195497ce, commit 6fba598ea)
|
||||
- CLI: auth probe output polish (table output, inline errors, reduced noise, and wrap fixes in `clawdbot models status`). (commit da3f2b489, commit 00ae21bed, commit 31e59cd58, commit f7dc27f2d, commit 438e782f8, commit 886752217, commit aabe0bed3, commit 81535d512, commit c63144ab1)
|
||||
- Media: only parse `MEDIA:` tags when they start the line to avoid stripping prose mentions. (#1206)
|
||||
- Media: preserve PNG alpha when possible; fall back to JPEG when still over size cap. (#1491) Thanks @robbyczgw-cla.
|
||||
- Agents: treat plugin-only tool allowlists as opt-ins; keep core tools enabled. (#1467)
|
||||
- Exec approvals: persist allowlist entry ids to keep macOS allowlist rows stable. (#1521) Thanks @ngutman.
|
||||
- MS Teams (plugin): remove `.default` suffix from Graph scopes to avoid double-appending. (#1507) Thanks @Evizero.
|
||||
- MS Teams (plugin): remove `.default` suffix from Bot Framework probe scope to avoid double-appending. (#1574) Thanks @Evizero.
|
||||
- Browser: keep extension relay tabs controllable when the extension reuses a session id after switching tabs. (#1160)
|
||||
- Agents: warn and ignore tool allowlists that only reference unknown or unloaded plugin tools. (#1566)
|
||||
- Skills: gate bird Homebrew install to macOS. (#1569) Thanks @bradleypriest.
|
||||
|
||||
## 2026.1.22
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ android {
|
||||
applicationId = "com.clawdbot.android"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 202601230
|
||||
versionName = "2026.1.23"
|
||||
versionCode = 202601240
|
||||
versionName = "2026.1.24"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.23</string>
|
||||
<string>2026.1.24</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260123</string>
|
||||
<string>20260124</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.23</string>
|
||||
<string>2026.1.24</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>20260123</string>
|
||||
<string>20260124</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -81,8 +81,8 @@ targets:
|
||||
properties:
|
||||
CFBundleDisplayName: Clawdbot
|
||||
CFBundleIconName: AppIcon
|
||||
CFBundleShortVersionString: "2026.1.23"
|
||||
CFBundleVersion: "20260123"
|
||||
CFBundleShortVersionString: "2026.1.24"
|
||||
CFBundleVersion: "20260124"
|
||||
UILaunchScreen: {}
|
||||
UIApplicationSceneManifest:
|
||||
UIApplicationSupportsMultipleScenes: false
|
||||
@@ -130,5 +130,5 @@ targets:
|
||||
path: Tests/Info.plist
|
||||
properties:
|
||||
CFBundleDisplayName: ClawdbotTests
|
||||
CFBundleShortVersionString: "2026.1.23"
|
||||
CFBundleVersion: "20260123"
|
||||
CFBundleShortVersionString: "2026.1.24"
|
||||
CFBundleVersion: "20260124"
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.1.23</string>
|
||||
<string>2026.1.24</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>202601230</string>
|
||||
<string>202601240</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>Clawdbot</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -82,7 +82,7 @@ and logged; a message that is only `HEARTBEAT_OK` is dropped.
|
||||
every: "30m", // default: 30m (0m disables)
|
||||
model: "anthropic/claude-opus-4-5",
|
||||
includeReasoning: false, // default: false (deliver separate Reasoning: message when available)
|
||||
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
|
||||
target: "last", // last | none | <channel id> (core or plugin, e.g. "bluebubbles")
|
||||
to: "+15551234567", // optional channel-specific override
|
||||
prompt: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.",
|
||||
ackMaxChars: 300 // max chars allowed after HEARTBEAT_OK
|
||||
|
||||
+168
-1
@@ -14,14 +14,24 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [How do I open the dashboard after onboarding?](#how-do-i-open-the-dashboard-after-onboarding)
|
||||
- [How do I authenticate the dashboard (token) on localhost vs remote?](#how-do-i-authenticate-the-dashboard-token-on-localhost-vs-remote)
|
||||
- [What runtime do I need?](#what-runtime-do-i-need)
|
||||
- [Does it run on Raspberry Pi?](#does-it-run-on-raspberry-pi)
|
||||
- [Can I migrate my setup to a new machine (Mac mini) without redoing onboarding?](#can-i-migrate-my-setup-to-a-new-machine-mac-mini-without-redoing-onboarding)
|
||||
- [Where do I see what’s new in the latest version?](#where-do-i-see-whats-new-in-the-latest-version)
|
||||
- [I can't access docs.clawd.bot (SSL error). What now?](#i-cant-access-docsclawdbot-ssl-error-what-now)
|
||||
- [How do I install the beta version, and what’s the difference between beta and dev?](#how-do-i-install-the-beta-version-and-whats-the-difference-between-beta-and-dev)
|
||||
- [The docs didn’t answer my question — how do I get a better answer?](#the-docs-didnt-answer-my-question--how-do-i-get-a-better-answer)
|
||||
- [How do I install Clawdbot on a VPS?](#how-do-i-install-clawdbot-on-a-vps)
|
||||
- [What does the onboarding wizard actually do?](#what-does-the-onboarding-wizard-actually-do)
|
||||
- [How does Anthropic "setup-token" auth work?](#how-does-anthropic-setup-token-auth-work)
|
||||
- [Where do I find an Anthropic setup-token?](#where-do-i-find-an-anthropic-setup-token)
|
||||
- [Do you support Claude subscription auth (Claude Code OAuth)?](#do-you-support-claude-subscription-auth-claude-code-oauth)
|
||||
- [Why am I seeing `HTTP 429: rate_limit_error` from Anthropic?](#why-am-i-seeing-http-429-rate_limit_error-from-anthropic)
|
||||
- [Is AWS Bedrock supported?](#is-aws-bedrock-supported)
|
||||
- [How does Codex auth work?](#how-does-codex-auth-work)
|
||||
- [Do you support OpenAI subscription auth (Codex OAuth)?](#do-you-support-openai-subscription-auth-codex-oauth)
|
||||
- [Is a local model OK for casual chats?](#is-a-local-model-ok-for-casual-chats)
|
||||
- [How do I keep hosted model traffic in a specific region?](#how-do-i-keep-hosted-model-traffic-in-a-specific-region)
|
||||
- [Do I have to buy a Mac Mini to install this?](#do-i-have-to-buy-a-mac-mini-to-install-this)
|
||||
- [Can I use Bun?](#can-i-use-bun)
|
||||
- [Telegram: what goes in `allowFrom`?](#telegram-what-goes-in-allowfrom)
|
||||
- [Can multiple people use one WhatsApp number with different Clawdbots?](#can-multiple-people-use-one-whatsapp-number-with-different-clawdbots)
|
||||
@@ -34,6 +44,7 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Can I load skills from a custom folder?](#can-i-load-skills-from-a-custom-folder)
|
||||
- [How can I use different models for different tasks?](#how-can-i-use-different-models-for-different-tasks)
|
||||
- [How do I install skills on Linux?](#how-do-i-install-skills-on-linux)
|
||||
- [Can Clawdbot run tasks on a schedule or continuously in the background?](#can-clawdbot-run-tasks-on-a-schedule-or-continuously-in-the-background)
|
||||
- [Can I run Apple/macOS-only skills from Linux?](#can-i-run-applemacos-only-skills-from-linux)
|
||||
- [Do you have a Notion or HeyGen integration?](#do-you-have-a-notion-or-heygen-integration)
|
||||
- [How do I install the Chrome extension for browser takeover?](#how-do-i-install-the-chrome-extension-for-browser-takeover)
|
||||
@@ -81,8 +92,10 @@ Quick answers plus deeper troubleshooting for real-world setups (local dev, VPS,
|
||||
- [Why doesn’t Clawdbot reply in a group?](#why-doesnt-clawdbot-reply-in-a-group)
|
||||
- [Do groups/threads share context with DMs?](#do-groupsthreads-share-context-with-dms)
|
||||
- [How many workspaces and agents can I create?](#how-many-workspaces-and-agents-can-i-create)
|
||||
- [Can I run multiple bots or chats at the same time (Slack), and how should I set that up?](#can-i-run-multiple-bots-or-chats-at-the-same-time-slack-and-how-should-i-set-that-up)
|
||||
- [Models: defaults, selection, aliases, switching](#models-defaults-selection-aliases-switching)
|
||||
- [What is the “default model”?](#what-is-the-default-model)
|
||||
- [What model do you recommend?](#what-model-do-you-recommend)
|
||||
- [How do I switch models on the fly (without restarting)?](#how-do-i-switch-models-on-the-fly-without-restarting)
|
||||
- [Why do I see “Model … is not allowed” and then no reply?](#why-do-i-see-model-is-not-allowed-and-then-no-reply)
|
||||
- [Why do I see “Unknown model: minimax/MiniMax-M2.1”?](#why-do-i-see-unknown-model-minimaxminimax-m21)
|
||||
@@ -231,6 +244,86 @@ See [Dashboard](/web/dashboard) and [Web surfaces](/web) for bind modes and auth
|
||||
|
||||
Node **>= 22** is required. `pnpm` is recommended. Bun is **not recommended** for the Gateway.
|
||||
|
||||
### Does it run on Raspberry Pi?
|
||||
|
||||
Yes. The Gateway is lightweight — docs list **512MB–1GB RAM**, **1 core**, and about **500MB**
|
||||
disk as enough for personal use, and note that a **Raspberry Pi 4 can run it**.
|
||||
|
||||
If you want extra headroom (logs, media, other services), **2GB is recommended**, but it’s
|
||||
not a hard minimum.
|
||||
|
||||
### Can I migrate my setup to a new machine (Mac mini) without redoing onboarding?
|
||||
|
||||
Yes. Copy the **state directory** and **workspace**, then run Doctor once:
|
||||
|
||||
1) Install Clawdbot on the new machine.
|
||||
2) Copy `$CLAWDBOT_STATE_DIR` (default: `~/.clawdbot`) from the old machine.
|
||||
3) Copy your workspace (default: `~/clawd`).
|
||||
4) Run `clawdbot doctor` and restart the Gateway service.
|
||||
|
||||
That preserves config, auth profiles, WhatsApp creds, sessions, and memory. If you’re in
|
||||
remote mode, remember the gateway host owns the session store and workspace.
|
||||
|
||||
Related: [Where things live on disk](/help/faq#where-does-clawdbot-store-its-data),
|
||||
[Agent workspace](/concepts/agent-workspace), [Doctor](/gateway/doctor),
|
||||
[Remote mode](/gateway/remote).
|
||||
|
||||
### Where do I see what’s new in the latest version?
|
||||
|
||||
Check the GitHub changelog:
|
||||
https://github.com/clawdbot/clawdbot/blob/main/CHANGELOG.md
|
||||
|
||||
Newest entries are at the top. If the top section is marked **Unreleased**, the next dated
|
||||
section is the latest shipped version. Entries are grouped by **Highlights**, **Changes**, and
|
||||
**Fixes** (plus docs/other sections when needed).
|
||||
|
||||
### I can't access docs.clawd.bot (SSL error). What now?
|
||||
|
||||
Some Comcast/Xfinity connections incorrectly block `docs.clawd.bot` via Xfinity
|
||||
Advanced Security. Disable it or allowlist `docs.clawd.bot`, then retry. More
|
||||
detail: [Troubleshooting](/help/troubleshooting#docsclawdbot-shows-an-ssl-error-comcastxfinity).
|
||||
|
||||
If you still can't reach the site, the docs are mirrored on GitHub:
|
||||
https://github.com/clawdbot/clawdbot/tree/main/docs
|
||||
|
||||
### How do I install the beta version, and what’s the difference between beta and dev?
|
||||
|
||||
**Beta** is a prerelease tag (`vYYYY.M.D-beta.N`) published to the npm dist‑tag `beta`.
|
||||
**Dev** is the moving head of `main` (git); when published, it uses the npm dist‑tag `dev`.
|
||||
|
||||
One‑liners (macOS/Linux):
|
||||
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://clawd.bot/install.sh | bash -s -- --beta
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -fsSL --proto '=https' --tlsv1.2 https://clawd.bot/install.sh | bash -s -- --install-method git
|
||||
```
|
||||
|
||||
Windows installer (PowerShell):
|
||||
https://clawd.bot/install.ps1
|
||||
|
||||
More detail: [Development channels](/install/development-channels) and [Installer flags](/install/installer).
|
||||
|
||||
### The docs didn’t answer my question — how do I get a better answer?
|
||||
|
||||
Use the **hackable (git) install** so you have the full source and docs locally, then ask
|
||||
your bot (or Claude/Codex) *from that folder* so it can read the repo and answer precisely.
|
||||
|
||||
```bash
|
||||
curl -fsSL https://clawd.bot/install.sh | bash -s -- --install-method git
|
||||
```
|
||||
|
||||
More detail: [Install](/install) and [Installer flags](/install/installer).
|
||||
|
||||
### How do I install Clawdbot on a VPS?
|
||||
|
||||
Any Linux VPS works. Install on the server, then use SSH/Tailscale to reach the Gateway.
|
||||
|
||||
Guides: [exe.dev](/platforms/exe-dev), [Hetzner](/platforms/hetzner), [Fly.io](/platforms/fly).
|
||||
Remote access: [Gateway remote](/gateway/remote).
|
||||
|
||||
### What does the onboarding wizard actually do?
|
||||
|
||||
`clawdbot onboard` is the recommended setup path. In **local mode** it walks you through:
|
||||
@@ -268,6 +361,16 @@ Yes. Clawdbot can **reuse Claude Code CLI credentials** (OAuth) and also support
|
||||
|
||||
Note: Claude subscription access is governed by Anthropic’s terms. For production or multi‑user workloads, API keys are usually the safer choice.
|
||||
|
||||
### Why am I seeing `HTTP 429: rate_limit_error` from Anthropic?
|
||||
|
||||
That means your **Anthropic quota/rate limit** is exhausted for the current window. If you
|
||||
use a **Claude subscription** (setup‑token or Claude Code OAuth), wait for the window to
|
||||
reset or upgrade your plan. If you use an **Anthropic API key**, check the Anthropic Console
|
||||
for usage/billing and raise limits as needed.
|
||||
|
||||
Tip: set a **fallback model** so Clawdbot can keep replying while a provider is rate‑limited.
|
||||
See [Models](/cli/models) and [OAuth](/concepts/oauth).
|
||||
|
||||
### Is AWS Bedrock supported?
|
||||
|
||||
Yes — via pi‑ai’s **Amazon Bedrock (Converse)** provider with **manual config**. You must supply AWS credentials/region on the gateway host and add a Bedrock provider entry in your models config. See [Amazon Bedrock](/bedrock) and [Model providers](/providers/models). If you prefer a managed key flow, an OpenAI‑compatible proxy in front of Bedrock is still a valid option.
|
||||
@@ -276,6 +379,14 @@ Yes — via pi‑ai’s **Amazon Bedrock (Converse)** provider with **manual con
|
||||
|
||||
Clawdbot supports **OpenAI Code (Codex)** via OAuth or by reusing your Codex CLI login (`~/.codex/auth.json`). The wizard can import the CLI login or run the OAuth flow and will set the default model to `openai-codex/gpt-5.2` when appropriate. See [Model providers](/concepts/model-providers) and [Wizard](/start/wizard).
|
||||
|
||||
### Do you support OpenAI subscription auth (Codex OAuth)?
|
||||
|
||||
Yes. Clawdbot fully supports **OpenAI Code (Codex) subscription OAuth** and can also reuse an
|
||||
existing Codex CLI login (`~/.codex/auth.json`) on the gateway host. The onboarding wizard
|
||||
can import the CLI login or run the OAuth flow for you.
|
||||
|
||||
See [OAuth](/concepts/oauth), [Model providers](/concepts/model-providers), and [Wizard](/start/wizard).
|
||||
|
||||
### Is a local model OK for casual chats?
|
||||
|
||||
Usually no. Clawdbot needs large context + strong safety; small cards truncate and leak. If you must, run the **largest** MiniMax M2.1 build you can locally (LM Studio) and see [/gateway/local-models](/gateway/local-models). Smaller/quantized models increase prompt-injection risk — see [Security](/gateway/security).
|
||||
@@ -284,6 +395,17 @@ Usually no. Clawdbot needs large context + strong safety; small cards truncate a
|
||||
|
||||
Pick region-pinned endpoints. OpenRouter exposes US-hosted options for MiniMax, Kimi, and GLM; choose the US-hosted variant to keep data in-region. You can still list Anthropic/OpenAI alongside these by using `models.mode: "merge"` so fallbacks stay available while respecting the regioned provider you select.
|
||||
|
||||
### Do I have to buy a Mac Mini to install this?
|
||||
|
||||
No. Clawdbot runs on macOS or Linux (Windows via WSL2). A Mac mini is optional — some people
|
||||
buy one as an always‑on host, but a small VPS, home server, or Raspberry Pi‑class box works too.
|
||||
|
||||
You only need a Mac **for macOS‑only tools**. For iMessage, you can keep the Gateway on Linux
|
||||
and run `imsg` on any Mac over SSH by pointing `channels.imessage.cliPath` at an SSH wrapper.
|
||||
If you want other macOS‑only tools, run the Gateway on a Mac or pair a macOS node.
|
||||
|
||||
Docs: [iMessage](/channels/imessage), [Nodes](/nodes), [Mac remote mode](/platforms/mac/remote).
|
||||
|
||||
### Can I use Bun?
|
||||
|
||||
Bun is **not recommended**. We see runtime bugs, especially with WhatsApp and Telegram.
|
||||
@@ -405,6 +527,17 @@ npm i -g clawdhub
|
||||
pnpm add -g clawdhub
|
||||
```
|
||||
|
||||
### Can Clawdbot run tasks on a schedule or continuously in the background?
|
||||
|
||||
Yes. Use the Gateway scheduler:
|
||||
|
||||
- **Cron jobs** for scheduled or recurring tasks (persist across restarts).
|
||||
- **Heartbeat** for “main session” periodic checks.
|
||||
- **Isolated jobs** for autonomous agents that post summaries or deliver to chats.
|
||||
|
||||
Docs: [Cron jobs](/automation/cron-jobs), [Cron vs Heartbeat](/automation/cron-vs-heartbeat),
|
||||
[Heartbeat](/gateway/heartbeat).
|
||||
|
||||
### Is there a way to run Apple/macOS-only skills if my Gateway runs on Linux?
|
||||
|
||||
Not directly. macOS skills are gated by `metadata.clawdbot.os` plus required binaries, and skills only appear in the system prompt when they are eligible on the **Gateway host**. On Linux, `darwin`-only skills (like `imsg`, `apple-notes`, `apple-reminders`) will not load unless you override the gating.
|
||||
@@ -774,7 +907,8 @@ Docs: [Nodes](/nodes), [Nodes CLI](/cli/nodes), [Chrome extension](/tools/chrome
|
||||
### Do nodes run a gateway service?
|
||||
|
||||
No. Only **one gateway** should run per host unless you intentionally run isolated profiles (see [Multiple gateways](/gateway/multiple-gateways)). Nodes are peripherals that connect
|
||||
to the gateway (iOS/Android nodes, or macOS “node mode” in the menubar app).
|
||||
to the gateway (iOS/Android nodes, or macOS “node mode” in the menubar app). For headless node
|
||||
hosts and CLI control, see [Node host CLI](/cli/node).
|
||||
|
||||
A full restart is required for `gateway`, `discovery`, and `canvasHost` changes.
|
||||
|
||||
@@ -1041,6 +1175,24 @@ Tips:
|
||||
- Prune old sessions (delete JSONL or store entries) if disk grows.
|
||||
- Use `clawdbot doctor` to spot stray workspaces and profile mismatches.
|
||||
|
||||
### Can I run multiple bots or chats at the same time (Slack), and how should I set that up?
|
||||
|
||||
Yes. Use **Multi‑Agent Routing** to run multiple isolated agents and route inbound messages by
|
||||
channel/account/peer. Slack is supported as a channel and can be bound to specific agents.
|
||||
|
||||
Browser access is powerful but not “do anything a human can” — anti‑bot, CAPTCHAs, and MFA can
|
||||
still block automation. For the most reliable browser control, use the Chrome extension relay
|
||||
on the machine that runs the browser (and keep the Gateway anywhere).
|
||||
|
||||
Best‑practice setup:
|
||||
- Always‑on Gateway host (VPS/Mac mini).
|
||||
- One agent per role (bindings).
|
||||
- Slack channel(s) bound to those agents.
|
||||
- Local browser via extension relay (or a node) when needed.
|
||||
|
||||
Docs: [Multi‑Agent Routing](/concepts/multi-agent), [Slack](/channels/slack),
|
||||
[Browser](/tools/browser), [Chrome extension](/tools/chrome-extension), [Nodes](/nodes).
|
||||
|
||||
## Models: defaults, selection, aliases, switching
|
||||
|
||||
### What is the “default model”?
|
||||
@@ -1053,6 +1205,21 @@ agents.defaults.model.primary
|
||||
|
||||
Models are referenced as `provider/model` (example: `anthropic/claude-opus-4-5`). If you omit the provider, Clawdbot currently assumes `anthropic` as a temporary deprecation fallback — but you should still **explicitly** set `provider/model`.
|
||||
|
||||
### What model do you recommend?
|
||||
|
||||
**Recommended default:** `anthropic/claude-opus-4-5`.
|
||||
**Good alternative:** `anthropic/claude-sonnet-4-5`.
|
||||
**Reliable (less character):** `openai/gpt-5.2` — nearly as good as Opus, just less personality.
|
||||
**Budget:** `zai/glm-4.7`.
|
||||
|
||||
MiniMax M2.1 has its own docs: [MiniMax](/providers/minimax) and
|
||||
[Local models](/gateway/local-models).
|
||||
|
||||
Strong warning: weaker/over-quantized models are more vulnerable to prompt
|
||||
injection and unsafe behavior. See [Security](/gateway/security).
|
||||
|
||||
More context: [Models](/concepts/models).
|
||||
|
||||
### How do I switch models on the fly (without restarting)?
|
||||
|
||||
Use the `/model` command as a standalone message:
|
||||
|
||||
@@ -33,6 +33,22 @@ Almost always a Node/npm PATH issue. Start here:
|
||||
|
||||
- [Install (Node/npm PATH sanity)](/install#nodejs--npm-path-sanity)
|
||||
|
||||
### Installer fails (or you need full logs)
|
||||
|
||||
Re-run the installer in verbose mode to see the full trace and npm output:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://clawd.bot/install.sh | bash -s -- --verbose
|
||||
```
|
||||
|
||||
For beta installs:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://clawd.bot/install.sh | bash -s -- --beta --verbose
|
||||
```
|
||||
|
||||
You can also set `CLAWDBOT_VERBOSE=1` instead of the flag.
|
||||
|
||||
### Gateway “unauthorized”, can’t connect, or keeps reconnecting
|
||||
|
||||
- [Gateway troubleshooting](/gateway/troubleshooting)
|
||||
|
||||
@@ -181,7 +181,7 @@ cat > /data/clawdbot.json << 'EOF'
|
||||
"bind": "auto"
|
||||
},
|
||||
"meta": {
|
||||
"lastTouchedVersion": "2026.1.23"
|
||||
"lastTouchedVersion": "2026.1.24"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
@@ -30,17 +30,17 @@ Notes:
|
||||
# From repo root; set release IDs so Sparkle feed is enabled.
|
||||
# APP_BUILD must be numeric + monotonic for Sparkle compare.
|
||||
BUNDLE_ID=com.clawdbot.mac \
|
||||
APP_VERSION=2026.1.23 \
|
||||
APP_VERSION=2026.1.24 \
|
||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||
BUILD_CONFIG=release \
|
||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||
scripts/package-mac-app.sh
|
||||
|
||||
# Zip for distribution (includes resource forks for Sparkle delta support)
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.23.zip
|
||||
ditto -c -k --sequesterRsrc --keepParent dist/Clawdbot.app dist/Clawdbot-2026.1.24.zip
|
||||
|
||||
# Optional: also build a styled DMG for humans (drag to /Applications)
|
||||
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.23.dmg
|
||||
scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.24.dmg
|
||||
|
||||
# Recommended: build + notarize/staple zip + DMG
|
||||
# First, create a keychain profile once:
|
||||
@@ -48,26 +48,26 @@ scripts/create-dmg.sh dist/Clawdbot.app dist/Clawdbot-2026.1.23.dmg
|
||||
# --apple-id "<apple-id>" --team-id "<team-id>" --password "<app-specific-password>"
|
||||
NOTARIZE=1 NOTARYTOOL_PROFILE=clawdbot-notary \
|
||||
BUNDLE_ID=com.clawdbot.mac \
|
||||
APP_VERSION=2026.1.23 \
|
||||
APP_VERSION=2026.1.24 \
|
||||
APP_BUILD="$(git rev-list --count HEAD)" \
|
||||
BUILD_CONFIG=release \
|
||||
SIGN_IDENTITY="Developer ID Application: <Developer Name> (<TEAMID>)" \
|
||||
scripts/package-mac-dist.sh
|
||||
|
||||
# Optional: ship dSYM alongside the release
|
||||
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.23.dSYM.zip
|
||||
ditto -c -k --keepParent apps/macos/.build/release/Clawdbot.app.dSYM dist/Clawdbot-2026.1.24.dSYM.zip
|
||||
```
|
||||
|
||||
## Appcast entry
|
||||
Use the release note generator so Sparkle renders formatted HTML notes:
|
||||
```bash
|
||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.23.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
||||
SPARKLE_PRIVATE_KEY_FILE=/path/to/ed25519-private-key scripts/make_appcast.sh dist/Clawdbot-2026.1.24.zip https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml
|
||||
```
|
||||
Generates HTML release notes from `CHANGELOG.md` (via [`scripts/changelog-to-html.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/changelog-to-html.sh)) and embeds them in the appcast entry.
|
||||
Commit the updated `appcast.xml` alongside the release assets (zip + dSYM) when publishing.
|
||||
|
||||
## Publish & verify
|
||||
- Upload `Clawdbot-2026.1.23.zip` (and `Clawdbot-2026.1.23.dSYM.zip`) to the GitHub release for tag `v2026.1.23`.
|
||||
- Upload `Clawdbot-2026.1.24.zip` (and `Clawdbot-2026.1.24.dSYM.zip`) to the GitHub release for tag `v2026.1.24`.
|
||||
- Ensure the raw appcast URL matches the baked feed: `https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml`.
|
||||
- Sanity checks:
|
||||
- `curl -I https://raw.githubusercontent.com/clawdbot/clawdbot/main/appcast.xml` returns 200.
|
||||
|
||||
@@ -92,6 +92,21 @@ In group chats where you receive every message, be **smart about when to contrib
|
||||
|
||||
Participate, don't dominate.
|
||||
|
||||
### 😊 React Like a Human!
|
||||
On platforms that support reactions (Discord, Slack), use emoji reactions naturally:
|
||||
|
||||
**React when:**
|
||||
- You appreciate something but don't need to reply (👍, ❤️, 🙌)
|
||||
- Something made you laugh (😂, 💀)
|
||||
- You find it interesting or thought-provoking (🤔, 💡)
|
||||
- You want to acknowledge without interrupting the flow
|
||||
- It's a simple yes/no or approval situation (✅, 👀)
|
||||
|
||||
**Why it matters:**
|
||||
Reactions are lightweight social signals. Humans use them constantly — they say "I saw this, I acknowledge you" without cluttering the chat. You should too.
|
||||
|
||||
**Don't overdo it:** One reaction per message max. Pick the one that fits best.
|
||||
|
||||
## Tools
|
||||
|
||||
Skills provide your tools. When you need one, check its `SKILL.md`. Keep local notes (camera names, SSH details, voice preferences) in `TOOLS.md`.
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "clawdbot",
|
||||
"version": "2026.1.23",
|
||||
"version": "2026.1.24-0",
|
||||
"description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@@ -43,6 +43,7 @@
|
||||
"dist/slack/**",
|
||||
"dist/telegram/**",
|
||||
"dist/tui/**",
|
||||
"dist/tts/**",
|
||||
"dist/web/**",
|
||||
"dist/wizard/**",
|
||||
"dist/*.js",
|
||||
|
||||
@@ -69,6 +69,9 @@ export function isModernModelRef(ref: ModelRef): boolean {
|
||||
if (provider === "opencode" && id.endsWith("-free")) {
|
||||
return false;
|
||||
}
|
||||
if (provider === "opencode" && id === "alpha-glm-4.7") {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (provider === "openrouter" || provider === "opencode") {
|
||||
return matchesAny(id, [
|
||||
|
||||
@@ -54,7 +54,7 @@ describe("getOpencodeZenStaticFallbackModels", () => {
|
||||
it("returns an array of models", () => {
|
||||
const models = getOpencodeZenStaticFallbackModels();
|
||||
expect(Array.isArray(models)).toBe(true);
|
||||
expect(models.length).toBe(10);
|
||||
expect(models.length).toBe(9);
|
||||
});
|
||||
|
||||
it("includes Claude, GPT, Gemini, and GLM models", () => {
|
||||
|
||||
@@ -67,10 +67,9 @@ export const OPENCODE_ZEN_MODEL_ALIASES: Record<string, string> = {
|
||||
"gemini-2.5-pro": "gemini-3-pro",
|
||||
"gemini-2.5-flash": "gemini-3-flash",
|
||||
|
||||
// GLM (free + alpha)
|
||||
// GLM (free)
|
||||
glm: "glm-4.7",
|
||||
"glm-free": "glm-4.7",
|
||||
"alpha-glm": "alpha-glm-4.7",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,7 +121,6 @@ const MODEL_COSTS: Record<
|
||||
},
|
||||
"claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
||||
"gemini-3-pro": { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 },
|
||||
"alpha-glm-4.7": { input: 0.6, output: 2.2, cacheRead: 0.6, cacheWrite: 0 },
|
||||
"gpt-5.1-codex-mini": {
|
||||
input: 0.25,
|
||||
output: 2,
|
||||
@@ -147,7 +145,6 @@ const MODEL_CONTEXT_WINDOWS: Record<string, number> = {
|
||||
"gpt-5.1-codex": 400000,
|
||||
"claude-opus-4-5": 200000,
|
||||
"gemini-3-pro": 1048576,
|
||||
"alpha-glm-4.7": 204800,
|
||||
"gpt-5.1-codex-mini": 400000,
|
||||
"gpt-5.1": 400000,
|
||||
"glm-4.7": 204800,
|
||||
@@ -164,7 +161,6 @@ const MODEL_MAX_TOKENS: Record<string, number> = {
|
||||
"gpt-5.1-codex": 128000,
|
||||
"claude-opus-4-5": 64000,
|
||||
"gemini-3-pro": 65536,
|
||||
"alpha-glm-4.7": 131072,
|
||||
"gpt-5.1-codex-mini": 128000,
|
||||
"gpt-5.1": 128000,
|
||||
"glm-4.7": 131072,
|
||||
@@ -201,7 +197,6 @@ const MODEL_NAMES: Record<string, string> = {
|
||||
"gpt-5.1-codex": "GPT-5.1 Codex",
|
||||
"claude-opus-4-5": "Claude Opus 4.5",
|
||||
"gemini-3-pro": "Gemini 3 Pro",
|
||||
"alpha-glm-4.7": "Alpha GLM-4.7",
|
||||
"gpt-5.1-codex-mini": "GPT-5.1 Codex Mini",
|
||||
"gpt-5.1": "GPT-5.1",
|
||||
"glm-4.7": "GLM-4.7",
|
||||
@@ -229,7 +224,6 @@ export function getOpencodeZenStaticFallbackModels(): ModelDefinitionConfig[] {
|
||||
"gpt-5.1-codex",
|
||||
"claude-opus-4-5",
|
||||
"gemini-3-pro",
|
||||
"alpha-glm-4.7",
|
||||
"gpt-5.1-codex-mini",
|
||||
"gpt-5.1",
|
||||
"glm-4.7",
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Command } from "commander";
|
||||
import type { CronJob } from "../../cron/types.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import type { GatewayRpcOpts } from "../gateway-rpc.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import { parsePositiveIntOrUndefined } from "../program/helpers.js";
|
||||
@@ -138,7 +139,9 @@ export function registerCronAddCommand(cron: Command) {
|
||||
}
|
||||
|
||||
const agentId =
|
||||
typeof opts.agent === "string" && opts.agent.trim() ? opts.agent.trim() : undefined;
|
||||
typeof opts.agent === "string" && opts.agent.trim()
|
||||
? normalizeAgentId(opts.agent.trim())
|
||||
: undefined;
|
||||
|
||||
const payload = (() => {
|
||||
const systemEvent = typeof opts.systemEvent === "string" ? opts.systemEvent.trim() : "";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Command } from "commander";
|
||||
import { danger } from "../../globals.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { normalizeAgentId } from "../../routing/session-key.js";
|
||||
import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js";
|
||||
import {
|
||||
getCronChannelOptions,
|
||||
@@ -90,7 +91,7 @@ export function registerCronEditCommand(cron: Command) {
|
||||
throw new Error("Use --agent or --clear-agent, not both");
|
||||
}
|
||||
if (typeof opts.agent === "string" && opts.agent.trim()) {
|
||||
patch.agentId = opts.agent.trim();
|
||||
patch.agentId = normalizeAgentId(opts.agent.trim());
|
||||
}
|
||||
if (opts.clearAgent) {
|
||||
patch.agentId = null;
|
||||
|
||||
@@ -9,6 +9,7 @@ async function writePluginFixture(params: {
|
||||
dir: string;
|
||||
id: string;
|
||||
schema: Record<string, unknown>;
|
||||
channels?: string[];
|
||||
}) {
|
||||
await fs.mkdir(params.dir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
@@ -16,16 +17,16 @@ async function writePluginFixture(params: {
|
||||
`export default { id: "${params.id}", register() {} };`,
|
||||
"utf-8",
|
||||
);
|
||||
const manifest: Record<string, unknown> = {
|
||||
id: params.id,
|
||||
configSchema: params.schema,
|
||||
};
|
||||
if (params.channels) {
|
||||
manifest.channels = params.channels;
|
||||
}
|
||||
await fs.writeFile(
|
||||
path.join(params.dir, "clawdbot.plugin.json"),
|
||||
JSON.stringify(
|
||||
{
|
||||
id: params.id,
|
||||
configSchema: params.schema,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
JSON.stringify(manifest, null, 2),
|
||||
"utf-8",
|
||||
);
|
||||
}
|
||||
@@ -149,4 +150,43 @@ describe("config plugin validation", () => {
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("accepts plugin heartbeat targets", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
|
||||
const pluginDir = path.join(home, "bluebubbles-plugin");
|
||||
await writePluginFixture({
|
||||
dir: pluginDir,
|
||||
id: "bluebubbles-plugin",
|
||||
channels: ["bluebubbles"],
|
||||
schema: { type: "object" },
|
||||
});
|
||||
|
||||
vi.resetModules();
|
||||
const { validateConfigObjectWithPlugins } = await import("./config.js");
|
||||
const res = validateConfigObjectWithPlugins({
|
||||
agents: { defaults: { heartbeat: { target: "bluebubbles" } }, list: [{ id: "pi" }] },
|
||||
plugins: { enabled: false, load: { paths: [pluginDir] } },
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unknown heartbeat targets", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
process.env.CLAWDBOT_STATE_DIR = path.join(home, ".clawdbot");
|
||||
vi.resetModules();
|
||||
const { validateConfigObjectWithPlugins } = await import("./config.js");
|
||||
const res = validateConfigObjectWithPlugins({
|
||||
agents: { defaults: { heartbeat: { target: "not-a-channel" } }, list: [{ id: "pi" }] },
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.issues).toContainEqual({
|
||||
path: "agents.defaults.heartbeat.target",
|
||||
message: "unknown heartbeat target: not-a-channel",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -84,4 +84,22 @@ describe("config schema", () => {
|
||||
const channelProps = channelSchema?.properties as Record<string, unknown> | undefined;
|
||||
expect(channelProps?.accessToken).toBeTruthy();
|
||||
});
|
||||
|
||||
it("adds heartbeat target hints with dynamic channels", () => {
|
||||
const res = buildConfigSchema({
|
||||
channels: [
|
||||
{
|
||||
id: "bluebubbles",
|
||||
label: "BlueBubbles",
|
||||
configSchema: { type: "object" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const defaultsHint = res.uiHints["agents.defaults.heartbeat.target"];
|
||||
const listHint = res.uiHints["agents.list.*.heartbeat.target"];
|
||||
expect(defaultsHint?.help).toContain("bluebubbles");
|
||||
expect(defaultsHint?.help).toContain("last");
|
||||
expect(listHint?.help).toContain("bluebubbles");
|
||||
});
|
||||
});
|
||||
|
||||
+43
-1
@@ -1,3 +1,4 @@
|
||||
import { CHANNEL_IDS } from "../channels/registry.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { ClawdbotSchema } from "./zod-schema.js";
|
||||
|
||||
@@ -807,6 +808,44 @@ function applyChannelHints(hints: ConfigUiHints, channels: ChannelUiMetadata[]):
|
||||
return next;
|
||||
}
|
||||
|
||||
function listHeartbeatTargetChannels(channels: ChannelUiMetadata[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
const ordered: string[] = [];
|
||||
for (const id of CHANNEL_IDS) {
|
||||
const normalized = id.trim().toLowerCase();
|
||||
if (!normalized || seen.has(normalized)) continue;
|
||||
seen.add(normalized);
|
||||
ordered.push(normalized);
|
||||
}
|
||||
for (const channel of channels) {
|
||||
const normalized = channel.id.trim().toLowerCase();
|
||||
if (!normalized || seen.has(normalized)) continue;
|
||||
seen.add(normalized);
|
||||
ordered.push(normalized);
|
||||
}
|
||||
return ordered;
|
||||
}
|
||||
|
||||
function applyHeartbeatTargetHints(
|
||||
hints: ConfigUiHints,
|
||||
channels: ChannelUiMetadata[],
|
||||
): ConfigUiHints {
|
||||
const next: ConfigUiHints = { ...hints };
|
||||
const channelList = listHeartbeatTargetChannels(channels);
|
||||
const channelHelp = channelList.length ? ` Known channels: ${channelList.join(", ")}.` : "";
|
||||
const help = `Delivery target ("last", "none", or a channel id).${channelHelp}`;
|
||||
const paths = ["agents.defaults.heartbeat.target", "agents.list.*.heartbeat.target"];
|
||||
for (const path of paths) {
|
||||
const current = next[path] ?? {};
|
||||
next[path] = {
|
||||
...current,
|
||||
help: current.help ?? help,
|
||||
placeholder: current.placeholder ?? "last",
|
||||
};
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
function applyPluginSchemas(schema: ConfigSchema, plugins: PluginUiMetadata[]): ConfigSchema {
|
||||
const next = cloneSchema(schema);
|
||||
const root = asSchemaObject(next);
|
||||
@@ -908,7 +947,10 @@ export function buildConfigSchema(params?: {
|
||||
const channels = params?.channels ?? [];
|
||||
if (plugins.length === 0 && channels.length === 0) return base;
|
||||
const mergedHints = applySensitiveHints(
|
||||
applyChannelHints(applyPluginHints(base.uiHints, plugins), channels),
|
||||
applyHeartbeatTargetHints(
|
||||
applyChannelHints(applyPluginHints(base.uiHints, plugins), channels),
|
||||
channels,
|
||||
),
|
||||
);
|
||||
const mergedSchema = applyChannelSchemas(applyPluginSchemas(base.schema, plugins), channels);
|
||||
return {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
HumanDelayConfig,
|
||||
TypingMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelId } from "../channels/plugins/types.js";
|
||||
import type {
|
||||
SandboxBrowserSettings,
|
||||
SandboxDockerSettings,
|
||||
@@ -177,18 +178,8 @@ export type AgentDefaultsConfig = {
|
||||
model?: string;
|
||||
/** Session key for heartbeat runs ("main" or explicit session key). */
|
||||
session?: string;
|
||||
/** Delivery target (last|whatsapp|telegram|discord|slack|mattermost|msteams|signal|imessage|none). */
|
||||
target?:
|
||||
| "last"
|
||||
| "whatsapp"
|
||||
| "telegram"
|
||||
| "discord"
|
||||
| "slack"
|
||||
| "mattermost"
|
||||
| "msteams"
|
||||
| "signal"
|
||||
| "imessage"
|
||||
| "none";
|
||||
/** Delivery target ("last", "none", or a channel id). */
|
||||
target?: "last" | "none" | ChannelId;
|
||||
/** Optional delivery override (E.164 for WhatsApp, chat id for Telegram). */
|
||||
to?: string;
|
||||
/** Override the heartbeat prompt body (default: "Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK."). */
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import path from "node:path";
|
||||
|
||||
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { CHANNEL_IDS } from "../channels/registry.js";
|
||||
import { CHANNEL_IDS, normalizeChatChannelId } from "../channels/registry.js";
|
||||
import {
|
||||
normalizePluginsConfig,
|
||||
resolveEnableState,
|
||||
@@ -226,6 +226,41 @@ export function validateConfigObjectWithPlugins(raw: unknown):
|
||||
}
|
||||
}
|
||||
|
||||
const heartbeatChannelIds = new Set<string>();
|
||||
for (const channelId of CHANNEL_IDS) {
|
||||
heartbeatChannelIds.add(channelId.toLowerCase());
|
||||
}
|
||||
for (const record of registry.plugins) {
|
||||
for (const channelId of record.channels) {
|
||||
const trimmed = channelId.trim();
|
||||
if (trimmed) heartbeatChannelIds.add(trimmed.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
const validateHeartbeatTarget = (target: string | undefined, path: string) => {
|
||||
if (typeof target !== "string") return;
|
||||
const trimmed = target.trim();
|
||||
if (!trimmed) {
|
||||
issues.push({ path, message: "heartbeat target must not be empty" });
|
||||
return;
|
||||
}
|
||||
const normalized = trimmed.toLowerCase();
|
||||
if (normalized === "last" || normalized === "none") return;
|
||||
if (normalizeChatChannelId(trimmed)) return;
|
||||
if (heartbeatChannelIds.has(normalized)) return;
|
||||
issues.push({ path, message: `unknown heartbeat target: ${target}` });
|
||||
};
|
||||
|
||||
validateHeartbeatTarget(
|
||||
config.agents?.defaults?.heartbeat?.target,
|
||||
"agents.defaults.heartbeat.target",
|
||||
);
|
||||
if (Array.isArray(config.agents?.list)) {
|
||||
for (const [index, entry] of config.agents.list.entries()) {
|
||||
validateHeartbeatTarget(entry?.heartbeat?.target, `agents.list.${index}.heartbeat.target`);
|
||||
}
|
||||
}
|
||||
|
||||
let selectedMemoryPluginId: string | null = null;
|
||||
const seenPlugins = new Set<string>();
|
||||
for (const record of registry.plugins) {
|
||||
|
||||
@@ -22,19 +22,7 @@ export const HeartbeatSchema = z
|
||||
model: z.string().optional(),
|
||||
session: z.string().optional(),
|
||||
includeReasoning: z.boolean().optional(),
|
||||
target: z
|
||||
.union([
|
||||
z.literal("last"),
|
||||
z.literal("whatsapp"),
|
||||
z.literal("telegram"),
|
||||
z.literal("discord"),
|
||||
z.literal("slack"),
|
||||
z.literal("msteams"),
|
||||
z.literal("signal"),
|
||||
z.literal("imessage"),
|
||||
z.literal("none"),
|
||||
])
|
||||
.optional(),
|
||||
target: z.string().optional(),
|
||||
to: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
ackMaxChars: z.number().int().nonnegative().optional(),
|
||||
|
||||
@@ -24,7 +24,7 @@ describe("normalizeCronJobCreate", () => {
|
||||
expect("provider" in payload).toBe(false);
|
||||
});
|
||||
|
||||
it("normalizes agentId and drops null", () => {
|
||||
it("trims agentId and drops null", () => {
|
||||
const normalized = normalizeCronJobCreate({
|
||||
name: "agent-set",
|
||||
enabled: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { parseAbsoluteTimeMs } from "./parse.js";
|
||||
import { migrateLegacyCronPayload } from "./payload-migration.js";
|
||||
import type { CronJobCreate, CronJobPatch } from "./types.js";
|
||||
@@ -75,7 +76,7 @@ export function normalizeCronJobInput(
|
||||
next.agentId = null;
|
||||
} else if (typeof agentId === "string") {
|
||||
const trimmed = agentId.trim();
|
||||
if (trimmed) next.agentId = trimmed;
|
||||
if (trimmed) next.agentId = normalizeAgentId(trimmed);
|
||||
else delete next.agentId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { inspect } from "node:util";
|
||||
import { Client } from "@buape/carbon";
|
||||
import { GatewayIntents, GatewayPlugin } from "@buape/carbon/gateway";
|
||||
import { Routes } from "discord-api-types/v10";
|
||||
@@ -95,7 +96,8 @@ function formatDiscordDeployErrorDetails(err: unknown): string {
|
||||
try {
|
||||
bodyText = JSON.stringify(rawBody);
|
||||
} catch {
|
||||
bodyText = String(rawBody);
|
||||
bodyText =
|
||||
typeof rawBody === "string" ? rawBody : inspect(rawBody, { depth: 3, breakLength: 120 });
|
||||
}
|
||||
if (bodyText) {
|
||||
const maxLen = 800;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { stripEnvelopeFromMessage } from "./chat-sanitize.js";
|
||||
|
||||
describe("stripEnvelopeFromMessage", () => {
|
||||
test("removes message_id hint lines from user messages", () => {
|
||||
const input = {
|
||||
role: "user",
|
||||
content: "[WhatsApp 2026-01-24 13:36] yolo\n[message_id: 7b8b]",
|
||||
};
|
||||
const result = stripEnvelopeFromMessage(input) as { content?: string };
|
||||
expect(result.content).toBe("yolo");
|
||||
});
|
||||
|
||||
test("removes message_id hint lines from text content arrays", () => {
|
||||
const input = {
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "hi\n[message_id: abc123]" }],
|
||||
};
|
||||
const result = stripEnvelopeFromMessage(input) as {
|
||||
content?: Array<{ type: string; text?: string }>;
|
||||
};
|
||||
expect(result.content?.[0]?.text).toBe("hi");
|
||||
});
|
||||
|
||||
test("does not strip inline message_id text that is part of a line", () => {
|
||||
const input = {
|
||||
role: "user",
|
||||
content: "I typed [message_id: 123] on purpose",
|
||||
};
|
||||
const result = stripEnvelopeFromMessage(input) as { content?: string };
|
||||
expect(result.content).toBe("I typed [message_id: 123] on purpose");
|
||||
});
|
||||
|
||||
test("does not strip assistant messages", () => {
|
||||
const input = {
|
||||
role: "assistant",
|
||||
content: "note\n[message_id: 123]",
|
||||
};
|
||||
const result = stripEnvelopeFromMessage(input) as { content?: string };
|
||||
expect(result.content).toBe("note\n[message_id: 123]");
|
||||
});
|
||||
});
|
||||
@@ -14,6 +14,8 @@ const ENVELOPE_CHANNELS = [
|
||||
"BlueBubbles",
|
||||
];
|
||||
|
||||
const MESSAGE_ID_LINE = /^\s*\[message_id:\s*[^\]]+\]\s*$/i;
|
||||
|
||||
function looksLikeEnvelopeHeader(header: string): boolean {
|
||||
if (/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}Z\b/.test(header)) return true;
|
||||
if (/\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/.test(header)) return true;
|
||||
@@ -28,13 +30,20 @@ export function stripEnvelope(text: string): string {
|
||||
return text.slice(match[0].length);
|
||||
}
|
||||
|
||||
function stripMessageIdHints(text: string): string {
|
||||
if (!text.includes("[message_id:")) return text;
|
||||
const lines = text.split(/\r?\n/);
|
||||
const filtered = lines.filter((line) => !MESSAGE_ID_LINE.test(line));
|
||||
return filtered.length === lines.length ? text : filtered.join("\n");
|
||||
}
|
||||
|
||||
function stripEnvelopeFromContent(content: unknown[]): { content: unknown[]; changed: boolean } {
|
||||
let changed = false;
|
||||
const next = content.map((item) => {
|
||||
if (!item || typeof item !== "object") return item;
|
||||
const entry = item as Record<string, unknown>;
|
||||
if (entry.type !== "text" || typeof entry.text !== "string") return item;
|
||||
const stripped = stripEnvelope(entry.text);
|
||||
const stripped = stripMessageIdHints(stripEnvelope(entry.text));
|
||||
if (stripped === entry.text) return item;
|
||||
changed = true;
|
||||
return {
|
||||
@@ -55,7 +64,7 @@ export function stripEnvelopeFromMessage(message: unknown): unknown {
|
||||
const next: Record<string, unknown> = { ...entry };
|
||||
|
||||
if (typeof entry.content === "string") {
|
||||
const stripped = stripEnvelope(entry.content);
|
||||
const stripped = stripMessageIdHints(stripEnvelope(entry.content));
|
||||
if (stripped !== entry.content) {
|
||||
next.content = stripped;
|
||||
changed = true;
|
||||
@@ -67,7 +76,7 @@ export function stripEnvelopeFromMessage(message: unknown): unknown {
|
||||
changed = true;
|
||||
}
|
||||
} else if (typeof entry.text === "string") {
|
||||
const stripped = stripEnvelope(entry.text);
|
||||
const stripped = stripMessageIdHints(stripEnvelope(entry.text));
|
||||
if (stripped !== entry.text) {
|
||||
next.text = stripped;
|
||||
changed = true;
|
||||
|
||||
@@ -96,9 +96,9 @@ function pickFirstExistingAgentId(cfg: ClawdbotConfig, agentId: string): string
|
||||
if (!trimmed) return normalizeAgentId(resolveDefaultAgentId(cfg));
|
||||
const normalized = normalizeAgentId(trimmed);
|
||||
const agents = listAgents(cfg);
|
||||
if (agents.length === 0) return trimmed;
|
||||
if (agents.length === 0) return normalized;
|
||||
const match = agents.find((agent) => normalizeAgentId(agent.id) === normalized);
|
||||
if (match?.id?.trim()) return match.id.trim();
|
||||
if (match?.id?.trim()) return normalizeAgentId(match.id.trim());
|
||||
return normalizeAgentId(resolveDefaultAgentId(cfg));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user