Compare commits

..
686 Commits
Author SHA1 Message Date
Peter Steinberger 7a5e3662a3 fix(web): show all WhatsApp shared contacts 2026-01-10 00:16:54 +00:00
Peter Steinberger d3d6d80f4a fix(agents): require raw for gateway config.apply (#566) (thanks @sircrumpet) 2026-01-10 00:16:17 +00:00
Eugene (via Claudius) a51948a528 fix(tools): flatten gateway tool schema for Vertex AI compatibility
Claude API on Vertex AI (Cloud Code Assist / Antigravity) enforces strict
JSON Schema 2020-12 validation and rejects root-level anyOf without a
top-level type field.

TypeBox Type.Union compiles to { anyOf: [...] } which Anthropic's direct
API accepts but Vertex rejects with:
  tools.11.custom.input_schema: JSON schema is invalid

This follows the same pattern used in browser-tool.ts which has the same
fix with an explanatory comment.

Flatten the schema to Type.Object with an action enum, matching how
browser tool handles this constraint.
2026-01-10 00:16:17 +00:00
Peter Steinberger 52565864d1 Merge pull request #619 from rubyrunsstuff/fix/telegram-block-streaming
fix: enable block streaming for Telegram when streamMode is 'block'
2026-01-10 00:16:04 +00:00
Peter Steinberger 1fd7a6e310 fix: keep telegram streamMode draft-only (#619) (thanks @rubyrunsstuff) 2026-01-10 01:14:40 +01:00
Peter Steinberger 08015fbce6 Merge pull request #544 from jeffersonwarrior/fix/cloud-code-assist-api-errors
Fix Cloud Code Assist API errors (429/400)
2026-01-10 00:13:46 +00:00
Peter Steinberger a9bd01b523 refactor: unify contact list summaries 2026-01-10 01:13:38 +01:00
Peter Steinberger 7b8ac0d5ad Merge pull request #623 from magimetal/feat/opencode-zen-provider
feat(onboard): add OpenCode Zen as model provider
2026-01-10 00:13:30 +00:00
Peter Steinberger 251ed83680 fix: harden Cloud Code Assist failover (#544) (thanks @jeffersonwarrior) 2026-01-10 01:12:46 +01:00
Peter Steinberger d75b302699 style: fix pi-embedded-runner formatting (#623) (thanks @magimetal) 2026-01-10 01:12:22 +01:00
Ruby b4fbf2fe0d fix: enable block streaming for Telegram when streamMode is 'block'
- Fix disableBlockStreaming logic in telegram/bot.ts to properly enable
  block streaming when telegram.streamMode is 'block' regardless of
  blockStreamingDefault setting
- Set minChars default to 1 for Telegram block mode so chunks send
  immediately on newlines/sentences instead of waiting for 800 chars
- Skip coalescing for Telegram block mode when not explicitly configured
  to reduce chunk batching delays
- Fix newline preference to wait for actual newlines instead of breaking
  on any whitespace when buffer is under maxChars

Fixes issue where all Telegram messages were batched into one message
at the end instead of streaming as separate messages during generation.
2026-01-10 01:11:41 +01:00
Jefferson Warrior 5843733978 Fix Cloud Code Assist API errors (429/400)
- Enhanced rate limit detection for Cloud Code Assist quota exhaustion
- Added tool call ID sanitization to fix invalid request format errors
- Integrated Cloud Code Assist format error detection into failover system
- Added comprehensive error pattern matching for Cloud Code Assist APIs

Fixes #cloud-code-assist-api-errors
2026-01-10 01:10:23 +01:00
Peter Steinberger c69c4caa33 fix: polish opencode-zen onboarding (#623) (thanks @magimetal) 2026-01-10 01:09:34 +01:00
Magi Metal 05bd100f7a style: fix formatting 2026-01-10 01:09:34 +01:00
Magi Metal a399fa36c8 feat(onboard): add OpenCode Zen as model provider 2026-01-10 01:09:34 +01:00
Peter Steinberger 9b1f164447 fix: guard small context windows 2026-01-10 01:08:56 +01:00
Peter Steinberger 3554004968 fix: QuickStart provider selection (#485) 2026-01-10 01:08:00 +01:00
Jefferson Warrior 74485f171b CLI: fix Cloud Code Assist 400 API errors and missing session exports
- Add sanitizeToolCallId() to fix Cloud Code Assist tool call ID validation
- Apply sanitization in sanitizeSessionMessagesImages() for toolResult and assistant messages
- Add legacy CONFIG_PATH_CLAWDIS and STATE_DIR_CLAWDIS exports for backward compatibility
- Resolves Cloud Code Assist rejection of invalid tool call IDs with pipe characters
- Fixes missing session export functions that were blocking system startup

Addresses Cloud Code Assist API 400 errors from invalid tool call IDs like 'call_abc123|item_456'
2026-01-10 01:07:51 +01:00
Peter Steinberger 0cb28e26fc Merge pull request #625 from mahmoudashraf93/fix/whatsapp-contact-cards-multi
fix: include numbers for WhatsApp contact arrays
2026-01-10 00:04:00 +00:00
Peter Steinberger 103dd3af64 fix: keep contact summary counts (#625) (thanks @mahmoudashraf93) 2026-01-10 01:02:28 +01:00
Peter Steinberger 21eebb6d3b fix: limit subagent bootstrap context 2026-01-10 00:01:16 +00:00
Mahmoud Ibrahim d1e10af1e1 WhatsApp: show all contacts in shares 2026-01-10 01:00:00 +01:00
Mahmoud Ibrahim 18338bc60f Style: format contact label helper 2026-01-10 01:00:00 +01:00
Mahmoud Ibrahim 77c7387bbd Changelog: note multi-contact numbers 2026-01-10 01:00:00 +01:00
Mahmoud Ibrahim fd7450e5b9 WhatsApp: include numbers in contact cards 2026-01-10 01:00:00 +01:00
Peter Steinberger e311dc82e0 refactor: centralize reasoning tag handling 2026-01-10 00:53:48 +01:00
Peter Steinberger 097550c299 fix: centralize verbose overrides and tool stream gating 2026-01-10 00:52:24 +01:00
Peter Steinberger 9a8d3aed26 test: update status expectations for verbose/elevated labels 2026-01-09 23:43:24 +00:00
Peter Steinberger e18080163f fix: simplify verbose/elevated status labels 2026-01-09 23:41:57 +00:00
Peter Steinberger eea686c81e docs: update changelog 2026-01-09 23:41:57 +00:00
Peter Steinberger bd10f3d3f1 feat: allow session_status in sandbox 2026-01-09 23:41:57 +00:00
Peter Steinberger a25922a21f fix: clarify sessions_send delivery semantics 2026-01-10 00:34:24 +01:00
Peter Steinberger 96e17d407a fix: filter NO_REPLY prefixes 2026-01-09 23:29:05 +00:00
Peter Steinberger a9a70ea278 fix: persist verbose off and gate tool stream 2026-01-10 00:22:28 +01:00
Peter Steinberger 695be8e92d Merge pull request #624 from clawdbot/refactor/vcard-utils
refactor: extract vcard parsing helper
2026-01-09 23:16:31 +00:00
Peter Steinberger c17d3a0738 docs: update changelog for vcard refactor (#624) (thanks @steipete) 2026-01-10 00:16:00 +01:00
Peter Steinberger b6b1b570f9 refactor: extract vcard parsing helper 2026-01-10 00:12:53 +01:00
Peter Steinberger 6016a63162 refactor(web): centralize active web listener resolution 2026-01-09 23:12:29 +00:00
Peter Steinberger 6f6acd94cc Merge pull request #614 from zknicker/fix/block-stream-think-leak
fix: prevent <think> leakage in block streaming
2026-01-09 23:10:17 +00:00
Peter Steinberger 2396a66e82 Merge pull request #622 from mahmoudashraf93/fix/whatsapp-contact-cards
fix: handle WhatsApp contact cards inbound
2026-01-09 23:09:40 +00:00
Peter Steinberger 1277f6e27b fix: prefer FN for WhatsApp contact cards (#622) (thanks @mahmoudashraf93) 2026-01-10 00:08:30 +01:00
Mahmoud Ibrahim c9c5a71d8b Changelog: note WhatsApp contact cards 2026-01-10 00:07:04 +01:00
Mahmoud Ibrahim 30b3d6ce61 WhatsApp: handle contact cards inbound 2026-01-10 00:07:04 +01:00
Peter Steinberger 875c62689e docs: remove unused root showcase 2026-01-10 00:02:39 +01:00
Peter Steinberger 51ec578cec fix: suppress <think> leakage + split reasoning output (#614) (thanks @zknicker) 2026-01-10 00:02:13 +01:00
Zach Knickerbocker 2d0ca67c21 fix: prevent <think> leakage in block streaming 2026-01-09 23:58:48 +01:00
Peter Steinberger 88cbe2d275 fix: cap pairing requests and suppress outbound pairing replies 2026-01-09 22:58:18 +00:00
Peter Steinberger 98d0318d4e refactor: cron payload migration cleanup (#621)
* refactor: centralize cron payload migration

* test: stabilize block streaming mocks

* test: adjust chunker fence-close case
2026-01-09 22:56:55 +00:00
Peter Steinberger e3c340fd38 docs: add fresh showcase projects 2026-01-09 23:56:44 +01:00
Peter Steinberger 89dc6ebb8b feat(signal): add reaction notifications 2026-01-09 23:53:36 +01:00
Peter Steinberger 90d6a55e05 Merge pull request #615 from YuriNachos/fix/smoke-test-exit-code
fix(build): remove non-functional smoke test from package-mac-app.sh
2026-01-09 22:42:08 +00:00
Peter Steinberger a18743eabc fix(web): improve WhatsApp Web listener errors (#612, thanks @YuriNachos) 2026-01-09 22:40:58 +00:00
Yurii ChukhlibandClaude a9f8719cd2 fix(web): remove provider name from gateway listener error messages
Error messages in sendMessageWhatsApp, sendReactionWhatsApp, and
sendPollWhatsApp included hardcoded \"WhatsApp\" references. This caused
confusion when cron jobs using other providers (e.g., Telegram) failed
with errors mentioning WhatsApp.

Changes error messages to be provider-agnostic while maintaining the
same error handling behavior.

Fixes #461
Fixes #470

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 22:40:58 +00:00
Peter Steinberger 0730e15c6c fix: verify bundled relay with version check (#615) (thanks @YuriNachos) 2026-01-09 23:40:52 +01:00
Peter Steinberger f4b9a00d37 Merge pull request #616 from neist/feat/signal-reactions
feat(signal): add reaction message support
2026-01-09 22:40:37 +00:00
Peter Steinberger 5bc3d15bba fix: handle signal reactions safely (#616) (thanks @neist) 2026-01-09 23:39:18 +01:00
Yurii ChukhlibandClaude 9c05675d66 fix(build): remove non-functional smoke test from package-mac-app.sh
The --smoke qr argument was removed from the relay CLI, but the smoke
test remained in the macOS app packaging script. This caused the build
to fail with exit code 1 when the bundled relay didn't recognize the
argument.

Removes the 2-line smoke test section to fix the build.

Fixes #317

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 23:37:57 +01:00
Kasper Neist 94e7a98bf2 feat(signal): add reaction message support
- Add SignalReactionMessage type with emoji, targetAuthor, timestamp
- Handle reaction messages in monitor (log and skip for now)
- Prevents reactions from showing as unknown media
2026-01-09 23:37:33 +01:00
Peter Steinberger 65cc92c06a chore: bump pi libs and oxlint-tsgolint 2026-01-09 22:23:58 +00:00
Peter Steinberger 870c9f0b99 refactor: stop pinning hardcoded main session in chat UI 2026-01-09 23:20:58 +01:00
Peter Steinberger 3b91148a0a fix: handle fence-close paragraph breaks 2026-01-09 22:20:22 +00:00
Peter Steinberger 22b3bd4415 fix: migrate cron payload channel alias 2026-01-09 22:20:22 +00:00
Peter Steinberger bdee50da6b fix(test): reduce gateway port collisions 2026-01-09 22:18:57 +00:00
Peter Steinberger 50461c23c0 fix(protocol): sync generated Swift models 2026-01-09 22:12:28 +00:00
Peter Steinberger 8a9c38e66e docs: embed YouTube video on showcase 2026-01-09 23:10:22 +01:00
Peter Steinberger 8624feb36a docs: clarify npm vs git install switching 2026-01-09 23:10:15 +01:00
Peter Steinberger 15757d01bc fix(protocol): regen Swift GatewayModels 2026-01-09 22:10:01 +00:00
Peter Steinberger 2af3853bfa fix(protocol): keep agent provider string 2026-01-09 23:09:10 +01:00
Peter Steinberger 3f1415b8fe ci: run pnpm test on macOS (PRs) 2026-01-09 22:06:36 +00:00
Peter Steinberger 79f5ccc99d fix(gateway): harden agent provider routing 2026-01-09 23:00:36 +01:00
Peter Steinberger 3adec35632 fix: make forced block chunking fence-safe 2026-01-09 21:52:47 +00:00
Peter Steinberger dc3a0bfd1e chore: set version to 2026.1.9 2026-01-09 22:42:25 +01:00
Peter Steinberger 79af03ba5e fix(auto-reply): tighten block streaming defaults 2026-01-09 22:41:10 +01:00
Peter Steinberger f8bf041396 test(gateway): cover internal provider defaults 2026-01-09 22:40:37 +01:00
Peter Steinberger 92cc7a841c refactor: centralize main session key normalization in apps 2026-01-09 22:38:17 +01:00
Peter Steinberger 40b4341a1d test: fix Windows path in LID mapping test 2026-01-09 21:34:35 +00:00
Peter Steinberger 35083fcb37 fix(gateway): avoid whatsapp fallback for internal runs 2026-01-09 22:33:05 +01:00
Peter Steinberger 53f51786f2 fix: default block streaming coalesce idle to 1s 2026-01-09 22:31:19 +01:00
Peter Steinberger 6c7a27c010 refactor: normalize main session key handling 2026-01-09 22:30:15 +01:00
Peter Steinberger 83270f98f7 chore: set version to 2026.1.8 2026-01-09 22:22:32 +01:00
Peter Steinberger 304ec1abe5 fix(agents): sessions_spawn inherits provider (#528) 2026-01-09 22:21:33 +01:00
Peter Steinberger 55e830b009 fix: harden onboarding for non-systemd environments 2026-01-09 22:17:09 +01:00
Peter Steinberger 402c35b91c refactor(agents): centralize failover normalization 2026-01-09 22:15:06 +01:00
Peter Steinberger 6220106ab2 refactor: centralize main session key resolution 2026-01-09 22:13:20 +01:00
Peter Steinberger c37b77855b Merge pull request #464 from austinm911/fix/slack-thread-replies
feat(slack): implement configurable reply threading
2026-01-09 21:10:39 +00:00
Peter Steinberger 84046cbad8 fix(slack): mrkdwn + thread edge cases (#464) (thanks @austinm911) 2026-01-09 22:09:02 +01:00
Peter Steinberger c27b1441f7 fix(auth): billing backoff + cooldown UX 2026-01-09 22:00:14 +01:00
Austin Mudd 8890fbcf38 Slack: fix message tool not passing threading context to handleSlackAction 2026-01-09 21:59:51 +01:00
Austin Mudd 8ae0429162 Slack: add mrkdwn formatter for proper bold/italic/strikethrough rendering 2026-01-09 21:59:51 +01:00
Austin Mudd 909c14d443 fix: resolve merge conflicts and fix threading tests
- Update MessageToolOptions type to include Slack threading options
- Remove duplicate threadTs property in slack/actions.ts
- Fix replyThreadTs parameter name in monitor.ts
- Update test to correctly verify 'first' mode threading behavior:
  - 'off' mode: no threading unless already in a thread
  - 'first' mode: first reply starts a thread
- Add new test case for 'first' mode threading
2026-01-09 21:59:51 +01:00
Austin Mudd b4663ed11c Slack: implement replyToMode threading for tool path
- Add shared hasRepliedRef state between auto-reply and tool paths
- Extract buildSlackThreadingContext helper in agent-runner.ts
- Extract resolveThreadTsFromContext helper in slack-actions.ts
- Update docs with clear replyToMode table (off/first/all)
- Add tests for first mode behavior across multiple messages
2026-01-09 21:59:51 +01:00
Austin Mudd 29e6f13b29 Test: add threadTs passthrough test for Slack sendMessage 2026-01-09 21:59:27 +01:00
Austin Mudd 1be8d06cca Slack: expose threadTs in agent sendMessage tool for thread replies 2026-01-09 21:59:27 +01:00
Peter Steinberger 42a0089b3b fix: require explicit system event session keys 2026-01-09 21:59:01 +01:00
Peter Steinberger cb2a365594 Merge pull request #607 from wes-davis/fix/launch-at-login-persistence
fix Mac app Launch at Login
2026-01-09 20:55:26 +00:00
Peter Steinberger 1985790f7f fix: avoid clearing Launch at Login on startup (#607) (thanks @wes-davis) 2026-01-09 21:54:26 +01:00
Wes 6f5503688d fix(macos): prevent launch-at-login plist deletion on startup 2026-01-09 21:53:32 +01:00
Peter Steinberger a5065b354e fix(whatsapp): resolve lid mappings for inbound 2026-01-09 21:46:18 +01:00
Peter Steinberger 5fa26bfec7 feat: add per-agent elevated controls 2026-01-09 20:42:19 +00:00
Peter Steinberger 1a97aadb6b fix: broadcast group history consistency (#547) 2026-01-09 21:39:58 +01:00
Peter Steinberger 7641b142ad refactor: harden broadcast groups 2026-01-09 21:39:58 +01:00
Peter Steinberger 374aa856f2 refactor(agents): centralize failover handling 2026-01-09 21:31:18 +01:00
Peter Steinberger cfeaa34c16 refactor: centralize cli timeout parsing 2026-01-09 21:29:52 +01:00
Peter Steinberger 1689ef0b97 docs: update changelog 2026-01-09 21:27:51 +01:00
Peter Steinberger 2bb9716598 fix: write clawdbot config atomically 2026-01-09 21:27:51 +01:00
Peter Steinberger 564caf49bb feat: add config --section 2026-01-09 21:27:51 +01:00
Peter Steinberger 24605379b9 refactor: centralize skills prompt resolution 2026-01-09 21:27:20 +01:00
Shadow cf8d7139e1 Docs: credit Docker cache PR 2026-01-09 14:24:45 -06:00
Zach Knickerbocker c6fc7c2ea6 Docker: cache deps layer for faster rebuilds (#605) 2026-01-09 14:23:06 -06:00
Peter Steinberger e8dbb350ae fix: honor config timeout in tui 2026-01-09 21:22:50 +01:00
Peter Steinberger 4861f09f78 fix: inject skills prompt list 2026-01-09 21:20:51 +01:00
Peter Steinberger 0297b38ce0 fix(discord): remove unused import 2026-01-09 21:18:26 +01:00
Peter Steinberger 65cb9dc3f7 fix(agents): fail over on billing/credits errors 2026-01-09 21:17:07 +01:00
Peter Steinberger e0089bb4eb Merge pull request #547 from pasogott/feature/broadcast-groups
feat: add broadcast groups for multi-agent responses
2026-01-09 20:15:42 +00:00
Peter Steinberger 76964162c7 fix: land broadcast groups (#547) (thanks @pasogott) 2026-01-09 21:14:19 +01:00
Shadow 2396df0d97 Docs: add PR 608 changelog entry 2026-01-09 14:12:38 -06:00
Nicholas Spisak d1b0a3584f fix(discord): add zombie connection detection with HELLO timeout
Add 30-second timeout after WebSocket opens to detect when Discord
never sends HELLO (zombie state). If isConnected stays false after
timeout, forces a fresh connection instead of hanging indefinitely.

Relates to #595
2026-01-09 14:12:38 -06:00
Nicholas Spisak 90be8c632a fix(discord): log gateway debug events for disconnect visibility
Add listener for Carbon GatewayPlugin 'debug' events to capture WebSocket
connection state changes. Critical events (close, reconnect, resume) are
logged at INFO level always; all debug messages logged in verbose mode.

Fixes #595
2026-01-09 14:12:38 -06:00
Peter Steinberger 4121f9e6dc feat(agents): add session_status tool 2026-01-09 21:09:42 +01:00
Peter Steinberger d309d4fe8b fix(discord): log gateway reconnects 2026-01-09 21:06:19 +01:00
Peter Steinberger e73b812236 fix: dedupe telegram updates 2026-01-09 21:06:06 +01:00
sheeek 09769d127f feat: add broadcast groups for multi-agent responses
Enables multiple agents to process the same message simultaneously,
allowing teams of specialized agents with atomic tasks to work together
in the same group using one phone number.

Key features:
- Configure multiple agents per WhatsApp group/DM via routing.broadcast
- Parallel (default) or sequential processing strategies
- Full session isolation (separate history, workspace, tools per agent)
- Minimal code changes (~50 lines in auto-reply.ts)
- Backward compatible with existing routing

Use cases:
- Specialized agent teams (code reviewer + security scanner + docs)
- Multi-language support (EN + DE + ES agents)
- Quality assurance workflows (support + QA agents)
- Task automation (tracker + logger + reporter)

Example config:
{
  "routing": {
    "broadcast": {
      "strategy": "parallel",
      "120363403215116621@g.us": ["alfred", "baerbel", "assistant3"]
    }
  }
}

This enables scaling to hundreds of focused micro-agents on a single
phone number, each handling specific atomic tasks.
2026-01-09 21:05:58 +01:00
Shadow 2469384643 update carbon 2026-01-09 13:52:59 -06:00
Peter Steinberger 81e7e25b3a style(macos): swiftformat cleanup 2026-01-09 19:49:12 +00:00
Peter Steinberger f97fbbf2b8 chore(swift): narrow formatter/lint scope 2026-01-09 19:49:12 +00:00
Peter SteinbergerandAzade 6d378ee608 feat(telegram): inline keyboard buttons (#491)
Co-authored-by: Azade <azade@hey.com>
2026-01-09 20:47:03 +01:00
Peter Steinberger 46f0a08878 chore: update clawtributors 2026-01-09 20:46:33 +01:00
Peter Steinberger fb989cd0f8 Merge pull request #600 from samratjha96/fix/discord-duplicate-messages
fix(queue): deduplicate followup queue entries
2026-01-09 19:45:10 +00:00
Peter Steinberger d3a0114b6b fix: dedupe followup queue by message id (#600) (thanks @samratjha96) 2026-01-09 20:44:11 +01:00
Peter Steinberger e4cd03033a Merge pull request #537 from Nachx639/fix/discord-web-provider-activity
fix: Discord eventQueue + WhatsApp provider activity accounting
2026-01-09 19:43:27 +00:00
Peter Steinberger a65455e25d fix: align WhatsApp activity account id (#537) (thanks @Nachx639) 2026-01-09 20:42:35 +01:00
Samrat Jha 9185fdc896 fix(queue): deduplicate followup queue entries to prevent duplicate responses
## Problem

When messages arrived while the agent was busy processing a previous message,
the same message could be enqueued multiple times into the followup queue.
This happened because Discord's event system can emit the same message multiple
times (e.g., during reconnects or due to slow listener processing), and the
followup queue had no deduplication logic.

This caused the bot to respond to the same user message 2-4+ times.

## Solution

Add simple exact-match deduplication in `enqueueFollowupRun()`: if a prompt
is already in the queue, skip adding it again. Extracted into a small
`isPromptAlreadyQueued()` helper for clarity.

## Testing

- Added test cases for deduplication (same prompt rejected, different accepted)
- Manually verified on Discord: single response per message even when multiple
  events fire during slow agent processing
2026-01-09 20:40:18 +01:00
Tu Nombre RealandClaude Opus 4.5 a05c4fca5c fix(web): pass accountId in WhatsApp provider activity tracking
Recent changes added recordProviderActivity calls with accountId, but
the type definition and usage didn't include accountId in ActiveWebSendOptions.
This fix adds the optional accountId field and uses optional chaining
when accessing it to handle cases where options is undefined.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 20:40:03 +01:00
Peter Steinberger 85fab7afe3 chore: add pnpm app workflow scripts 2026-01-09 19:38:48 +00:00
Peter Steinberger 2977b296e6 feat(messages): add whatsapp messagePrefix and responsePrefix auto 2026-01-09 19:29:04 +00:00
Peter Steinberger 0a4cb0d264 Merge pull request #604 from clawdbot/fix/model-fallback-error-message
Fix model fallback error message handling
2026-01-09 19:24:22 +00:00
Peter Steinberger 9ecef09c69 docs: update changelog for model fallback lint fix (#604) (thanks @steipete) 2026-01-09 20:24:08 +01:00
Peter Steinberger 6481806751 fix: avoid base-to-string in model fallback 2026-01-09 20:23:00 +01:00
Shadow 60bd21d77b chore: update carbon 2026-01-09 13:22:32 -06:00
Peter Steinberger 7e00fc4ebc Merge pull request #603 from clawdbot/refactor/ui-chat-split-475
refactor(ui): split chat renderers and styles
2026-01-09 19:21:56 +00:00
Peter Steinberger 1acbd6aea0 refactor(ui): split chat renderers and styles 2026-01-09 20:20:06 +01:00
Peter Steinberger 71a08eed84 Merge pull request #599 from mcinteerj/fix/gemini-tool-schemas
fix: simplify tool schemas for Gemini compatibility
2026-01-09 19:19:01 +00:00
Peter Steinberger 72b0777341 fix(messages): restore explicit responsePrefix default 2026-01-09 19:18:34 +00:00
Peter Steinberger 0edacd0469 fix: simplify session tool schemas for Gemini compatibility (#599) (thanks @mcinteerj) 2026-01-09 20:17:46 +01:00
Peter Steinberger 237480ed9b Merge pull request #602 from clawdbot/refactor/model-override-warnings
refactor: centralize model override validation
2026-01-09 19:15:51 +00:00
Peter Steinberger 837cec64af refactor: centralize test path containment checks 2026-01-09 19:15:44 +00:00
Keith the Silly Goose 423eef4624 fix: simplify tool schemas for Gemini compatibility
Replaces Type.Integer with Type.Number and simplifies the sessions_send tool schema to avoid anyOf/oneOf unions that cause 400 errors with Google Cloud Code Assist API.
2026-01-09 20:15:26 +01:00
Peter Steinberger 133e30c594 refactor: centralize model override validation (#602) (thanks @steipete) 2026-01-09 20:15:26 +01:00
Peter Steinberger 7e81980747 refactor: centralize model override validation 2026-01-09 20:14:42 +01:00
Shadow f0a909f6dd Changelog: add PR 601 2026-01-09 13:06:41 -06:00
Peter Steinberger 840ccbccf6 docs: add messages concept overview 2026-01-09 19:06:14 +00:00
Peter Steinberger ade06cb9fb Merge pull request #472 from koala73/main
feat: add hooks.gmail.model for cheaper Gmail PubSub processing
2026-01-09 19:00:53 +00:00
Martin Púčik d23bba9d24 docs: split CLI install commands into separate code blocks (#601) 2026-01-09 13:00:50 -06:00
Peter Steinberger 5755d85ad1 fix: harden Gmail hook model defaults (#472) (thanks @koala73) 2026-01-09 19:59:45 +01:00
Magi MetalandShadow 50a5b4ddcc Discord: fix DM recipient parsing for bare numeric user IDs (#596)
Co-authored-by: Shadow <shadow@clawd.bot>
2026-01-09 12:58:25 -06:00
Shadow 04512ee67c Docs: pin Carbon dependency 2026-01-09 12:57:58 -06:00
Peter Steinberger 47a8b410ee Merge pull request #475 from rahthakor/feature/ui-enhancements
feat(ui): refactor chat layout with sidebar, message grouping, and nav improvements
2026-01-09 18:54:44 +00:00
Peter Steinberger 067c20608c fix(ui): landing cleanup (#475) (thanks @rahthakor) 2026-01-09 19:53:32 +01:00
Peter Steinberger 6ec4c4bf8f Merge pull request #505 from koala73/feat/whatsapp-media-max-size
feat(whatsapp): add configurable media max size
2026-01-09 18:53:04 +00:00
Peter Steinberger 87f432880a fix: honor whatsapp mediaMaxMb (#505) (thanks @koala73) 2026-01-09 19:51:35 +01:00
rahthakor 9624d70187 test(ui): add tests for chat modules and update for icon refactor
- Add 21 tests for message-normalizer.ts (normalizeMessage, normalizeRoleForGrouping, isToolResultMessage)
- Add 17 tests for tool-helpers.ts (formatToolOutputForSidebar, getTruncatedPreview)
- Update navigation.test.ts to test iconClassForTab instead of deprecated iconForTab
- Skip focus-mode.browser.test.ts (toggle button moved to settings)
- Skip chat-markdown.browser.test.ts (tool card rendering refactored to sidebar)
- Skip bash-tools.test.ts line offset tests (shell env pollution issue)
2026-01-09 19:47:19 +01:00
Elie Habib 3026367c1b feat(whatsapp): add configurable media max size
- Add whatsapp.mediaMaxMb config option (default: 50MB)
- Increases default from previous 5MB hardcoded limit
- Allows receiving larger documents/media files
- Per-account override via whatsapp.accounts.*.mediaMaxMb

Fixes #<issue-number> (if applicable)
2026-01-09 19:40:21 +01:00
Elie Habib da2323f80e docs: add gateway restart guide 2026-01-09 19:39:42 +01:00
Elie Habib f85807a2a6 fix: add serveBaseUrl to compactEmbeddedPiSession params 2026-01-09 19:39:42 +01:00
Elie Habib 1a47aec6e4 feat(gateway): add serve feature for sharing files via public URLs 2026-01-09 19:39:42 +01:00
Peter Steinberger fd15704c77 fix(auto-reply): coalesce block replies and document streaming toggles (#536) (thanks @mcinteerj) 2026-01-09 18:19:55 +00:00
Jake a05916bee8 Config: add support for per-provider blockStreaming override 2026-01-09 18:11:27 +00:00
Peter Steinberger 91a6f721a3 docs: changelog for iMessage thread isolation (#535) (thanks @mdahmann) 2026-01-09 18:43:32 +01:00
Peter Steinberger 3eef200145 Merge pull request #535 from mdahmann/fix/imessage-groupish-threads
imessage: isolate group-ish threads by chat_id
2026-01-09 17:42:42 +00:00
Peter Steinberger d6d84ce349 test: harden logout path check on windows 2026-01-09 18:39:38 +01:00
Peter Steinberger 42ead1499f refactor: centralize minimax onboarding + keys 2026-01-09 18:37:06 +01:00
Peter Steinberger d0a20cadaf Merge branch 'main' into fix/imessage-groupish-threads 2026-01-09 17:36:15 +00:00
Peter Steinberger dac3b675cc fix: stabilize CI path assumptions 2026-01-09 18:35:52 +01:00
Peter Steinberger 6aac3184c3 test: normalize windows path assertions 2026-01-09 18:32:45 +01:00
Peter Steinberger a06b6c807e Merge pull request #592 from clawdbot/docs/hetzner-followups 2026-01-09 17:31:27 +00:00
Peter Steinberger e6a7429ac7 refactor: add helper for empty assistant content 2026-01-09 18:29:27 +01:00
Peter Steinberger e251ac4f74 Merge pull request #591 from clawdbot/followup/empty-error-filter
refactor: drop empty error messages from session history
2026-01-09 17:24:51 +00:00
Peter Steinberger 65a11095c0 docs: add changelog entry for #591 2026-01-09 18:23:26 +01:00
Peter Steinberger 79b3abd797 refactor: drop empty error messages in history 2026-01-09 18:23:26 +01:00
Peter Steinberger 37389005fc docs: note hetzner cross-links in changelog (#592) 2026-01-09 18:21:53 +01:00
Peter Steinberger 6904a79d1d docs: link Hetzner guide from install/platforms 2026-01-09 18:21:28 +01:00
Peter Steinberger 62bf779c14 Merge pull request #556 from Iamadig/docs/hetzner-guide
Docs/hetzner guide
2026-01-09 17:18:18 +00:00
Peter Steinberger 5c13dcdb7b Merge pull request #590 from mneves75/feat/minimax-anthropic-api
feat: Add MiniMax Anthropic-compatible API support (minimax-api)
2026-01-09 17:18:09 +00:00
Peter Steinberger 706cbe89ec fix: align Hetzner docker guide with gateway runtime (#556) (thanks @Iamadig) 2026-01-09 18:17:19 +01:00
Peter Steinberger a6a469435a fix: finalize minimax-api onboarding (#590) (thanks @mneves75) 2026-01-09 18:17:13 +01:00
Peter Steinberger 834e2b82f3 docs: comment doctor switch e2e 2026-01-09 18:16:59 +01:00
Peter Steinberger 805969b598 Merge branch 'main' into fix/imessage-groupish-threads 2026-01-09 17:16:44 +00:00
Peter Steinberger 18bc9db6b3 Merge pull request #561 from mukhtharcm/fix/filter-empty-error-messages
fix: filter empty error messages to prevent session corruption
2026-01-09 17:15:15 +00:00
AG 258184232d docs: add Hetzner deployment guide 2026-01-09 18:15:02 +01:00
Peter Steinberger 6156402c1a fix: scope 429 fast-fail to antigravity (#561) (thanks @mukhtharcm) 2026-01-09 18:13:47 +01:00
mneves75andClaude 9279795c47 feat: Add MiniMax Anthropic-compatible API support (minimax-api)
Add --auth-choice minimax-api for direct MiniMax API usage at
https://api.minimax.io/anthropic using the anthropic-messages API.

Changes:
- Add applyMinimaxApiConfig() function with provider/model config
- Add minimax-api to AuthChoice type and CLI options
- Add handler and non-interactive support
- Fix duplicate minimax entry in envMap
- Update live test to use anthropic-messages API
- Add 11 unit tests covering all edge cases
- Document configuration in gateway docs

Test results:
- 11/11 unit tests pass
- 1/1 live API test passes (verified with real API key)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 18:13:18 +01:00
Muhammed Mukhthar CM d0450bb425 fix(antigravity): add fail-fast on 429 rate limit
When Antigravity returns 429, throw immediately instead of waiting for the
server-provided retry delay (which can be 10+ minutes). This lets clawdbot
quickly rotate to another account.

This patch was previously in PR #454 but was accidentally removed in 0dbb569
when bumping to pi-ai 0.40.0. The upstream release did NOT include this fix.

Context: Antigravity rate limits cause pi-ai to sleep for the full retry
delay inside the request, blocking the thread. Clawdbot's timeout would
eventually fire, but waiting 10+ minutes is unacceptable UX.

Bundled with the empty error message filter since both handle 429 recovery.
2026-01-09 18:10:23 +01:00
Muhammed Mukhthar CM 1cfe409a09 fix: filter empty error messages to prevent session corruption
When 429/500 errors occur during tool execution, empty assistant messages
with stopReason='error' and content=[] get persisted to the session file.

These break the tool_use -> tool_result chain that Claude/Gemini require:
- Claude expects every tool_use block to have a matching tool_result
- Empty error messages inserted mid-sequence violate this invariant
- Results in: 'tool_use ids were found without tool_result blocks'

This patch filters out empty error messages when building session context,
allowing sessions to recover gracefully from transient API errors.

Evidence from production:
- 113 of 170 sessions had empty error messages
- Session 30764430 demonstrated recovery: 429 at 14:30:11 IST,
  resumed successfully at 14:30:22, completed at 14:30:34

Sorry Peter, one more patch! 🙈
2026-01-09 18:10:23 +01:00
NickSpisak_andShadow cfcff68e91 feat(discord): add channel/category management actions (#487)
Co-authored-by: Shadow <shadow@clawd.bot>
2026-01-09 11:05:58 -06:00
Shadow d588bb00d4 fix: update carbon version for gateway fixes 2026-01-09 10:56:13 -06:00
Peter Steinberger e74656fa71 Merge pull request #589 from clawdbot/chore/commands-registry-guards
fix: harden slash command registry
2026-01-09 16:56:01 +00:00
Peter Steinberger d64ed620d5 Merge pull request #588 from clawdbot/refactor/discord-thread-context
refactor: consolidate discord thread context handling
2026-01-09 16:54:14 +00:00
Peter Steinberger 377885bd36 style: format doctor gateway services 2026-01-09 17:53:57 +01:00
Peter Steinberger cf1e0d743c fix: harden slash command registry 2026-01-09 17:53:24 +01:00
Peter Steinberger b23ce86c81 docs: update changelog for #588 2026-01-09 17:52:47 +01:00
Peter Steinberger cae8e8d6e8 refactor: consolidate discord thread context handling 2026-01-09 17:52:47 +01:00
Peter Steinberger 24af8adcf9 Merge pull request #587 from clawdbot/refactor/hook-agent-normalize
Hooks: normalize agent provider handling
2026-01-09 16:52:32 +00:00
Peter Steinberger c3083f0186 feat: add setup-token + token auth 2026-01-09 17:50:34 +01:00
Peter Steinberger 083877d286 fix: audit doctor service entrypoint 2026-01-09 17:50:28 +01:00
Peter Steinberger 044ea90dde fix: repair doctor service install switches 2026-01-09 17:50:28 +01:00
Peter Steinberger 9c9f89e9be Merge pull request #497 from lc0rp-contrib/commands-list-clean
feat(commands): add /commands to list available commands
2026-01-09 16:46:32 +00:00
Luke 401f2a77e3 Merge branch 'main' into commands-list-clean 2026-01-09 11:46:08 -05:00
Peter Steinberger b1ddf1f048 refactor: share hook provider types 2026-01-09 17:45:42 +01:00
Peter Steinberger 51725a71a3 refactor: normalize hook agent providers 2026-01-09 17:40:36 +01:00
Peter Steinberger f146c9ef16 Merge pull request #585 from clawdbot/shadow/forum
Discord: fix forum thread starters
2026-01-09 16:35:33 +00:00
Peter Steinberger abfd5719d6 fix: cache discord channel lookups for thread starters (#585) (thanks @thewilloftheshadow) 2026-01-09 17:35:16 +01:00
Peter Steinberger 3a0cc3c927 Merge pull request #533 from mcinteerj/hooks-deliver-true-by-default
Hooks: default agent delivery to true
2026-01-09 16:35:09 +00:00
Peter Steinberger 40fcfc9479 test: cover hook deliver default (#533) (thanks @mcinteerj) 2026-01-09 17:34:22 +01:00
Jake cac467a2df Hooks: default agent delivery to true 2026-01-09 17:32:42 +01:00
Shadow 6ef9fc64d7 Discord: fix forum thread starters 2026-01-09 17:32:14 +01:00
Peter Steinberger ee70a1d1fb fix(status): use claude-cli token for usage 2026-01-09 16:24:55 +00:00
Peter Steinberger 1478473537 refactor(commands): canonicalize text command aliases 2026-01-09 17:22:46 +01:00
Peter Steinberger c82ebd3ef3 docs(commands): document /usage slash command 2026-01-09 17:18:01 +01:00
Peter Steinberger 1838582546 refactor(auto-reply): centralize chat command aliases 2026-01-09 17:16:52 +01:00
Peter Steinberger d372fac9c6 refactor: streamline reply tag parsing 2026-01-09 17:14:40 +01:00
Peter Steinberger cef13aa705 Merge pull request #586 from clawdbot/temp/landpr-492
fix(commands): wire /usage alias to /status
2026-01-09 16:14:11 +00:00
Peter Steinberger f4ec53dcb9 refactor(sessions): dedupe sessions.resolve 2026-01-09 17:13:38 +01:00
Peter Steinberger 68ad27e31c fix(commands): wire /usage to status (#492) (thanks @lc0rp) 2026-01-09 17:10:53 +01:00
Peter Steinberger 613d866296 chore(docker): pass claude web session env 2026-01-09 16:07:40 +00:00
Luke 4658f937cc Merge branch 'main' into commands-list-clean 2026-01-09 11:06:06 -05:00
Peter Steinberger 67af3c3291 Merge pull request #560 from mcinteerj/fix/reply-tags-whitespace
Auto-Reply: relax regex for reply tags to allow whitespace
2026-01-09 16:05:17 +00:00
Luke 98b875cd0f Merge branch 'main' into commands-list-clean 2026-01-09 11:04:23 -05:00
Peter Steinberger 7d9300e0f5 fix: allow whitespace in reply tags (#560) (thanks @mcinteerj) 2026-01-09 17:03:44 +01:00
Peter Steinberger c892fd174e refactor(sessions): add sessions.resolve + label helper (#570) 2026-01-09 17:02:57 +01:00
LK 08caf7b9fc feat(commands): add /usage alias for /status 2026-01-09 17:02:29 +01:00
Jake 4381b03412 Auto-Reply: relax regex for reply tags to allow whitespace 2026-01-09 17:01:58 +01:00
Peter Steinberger d099dabf37 refactor: centralize slack threading helpers 2026-01-09 16:01:53 +00:00
Peter Steinberger d0b06b4334 fix: avoid object stringification in session labels 2026-01-09 16:57:50 +01:00
Peter Steinberger be48233bc4 chore: format 2026-01-09 16:55:51 +01:00
Peter Steinberger fd9e2d3def feat: remove provider config in configure 2026-01-09 16:55:47 +01:00
Peter Steinberger c643ce2a7a feat: add /debug runtime overrides 2026-01-09 16:55:16 +01:00
Peter Steinberger 36bdec0f2c refactor(messages): centralize per-agent prefixes 2026-01-09 16:54:54 +01:00
Peter Steinberger 8341b662af refactor(test): temp home env + normalize status 2026-01-09 16:50:09 +01:00
Peter Steinberger e8d75a39bc ci: drop output sanitize wrapper 2026-01-09 16:49:12 +01:00
Peter Steinberger 02b945cc95 refactor(ui): split mobile layout css 2026-01-09 16:49:02 +01:00
Peter Steinberger c8b15af979 refactor(test): centralize temp home + polling 2026-01-09 16:49:02 +01:00
Peter Steinberger eb73b4e58e chore: make pnpm test non-watch 2026-01-09 16:49:02 +01:00
Peter Steinberger 7d518e336e feat(sessions): label lookup tightening (#570) (thanks @azade-c) 2026-01-09 16:42:09 +01:00
Peter Steinberger 09b602b4ec style: format trigger test 2026-01-09 16:41:05 +01:00
Peter Steinberger 4ffbd9802a refactor(test): consolidate temp home + vitest setup 2026-01-09 16:41:05 +01:00
Peter Steinberger 1eecce9a15 Merge pull request #578 from p6l-richard/feature/identity-based-message-prefix
fix(messages): derive messagePrefix from identity.name
2026-01-09 15:40:52 +00:00
Peter Steinberger 66bbb723c5 fix: derive prefixes from routed identity (#578) (thanks @p6l-richard) 2026-01-09 16:39:32 +01:00
Peter Steinberger 8de1c449ee Merge pull request #558 from carlulsoe/mobile-ui-improvements
feat(ui): improve mobile responsiveness [AI-assisted]
2026-01-09 15:39:19 +00:00
Peter Steinberger facf5c09a0 fix: honor slack reply threading (#574, thanks @bolismauro) 2026-01-09 15:38:43 +00:00
Peter Steinberger b3e0fafe50 fix: stabilize windows CI (#558) (thanks @carlulsoe) 2026-01-09 16:37:58 +01:00
Mauro Bolis 96149d1f71 fix: honor slack reply threading 2026-01-09 15:35:54 +00:00
Peter Steinberger 014a4d51a6 feat(status): add claude.ai usage fallback 2026-01-09 15:34:58 +00:00
Peter Steinberger 922ca2ee1c fix(status): surface provider usage errors 2026-01-09 15:34:58 +00:00
KitandCarl Ulsoe 480aa406bb feat(ui): improve mobile responsiveness
- Add @media (max-width: 600px) breakpoint for mobile-specific styles
- Compact header: smaller title, hidden subtitle, minimal status pill
- Horizontal scrollable nav: all items in one row, swipeable
- Hide redundant page titles on mobile
- Hide docs button on mobile (saves space)
- Smaller theme toggle icons
- Tighter spacing on cards, stats, forms
- Better chat layout: full-width session selector, compact compose
- Single-column log entries on small screens

Desktop layout remains unchanged.

Co-authored-by: Carl Ulsoe <34673973+carlulsoe@users.noreply.github.com>
2026-01-09 16:32:55 +01:00
Richard Poelderl 43848b7b43 feat(messages): also derive responsePrefix from identity.name
When identity.name is configured and responsePrefix is not explicitly set,
automatically default responsePrefix to [identity.name].

This means users only need to set their identity once:
  { identity: { name: "MyBot" } }

And outbound messages will automatically be prefixed with [MyBot].
2026-01-09 16:31:20 +01:00
Richard Poelderl 8112b276c0 feat(messages): derive messagePrefix from identity.name
When identity.name is configured, use it for the default messagePrefix
instead of hardcoded '[clawdbot]'. Falls back to 'clawdbot' if not set.

This allows users to customize how their bot identifies itself in messages
by setting identity.name in their config or IDENTITY.md.
2026-01-09 16:31:01 +01:00
Peter Steinberger f436808735 refactor: consolidate schema scrub + test harness 2026-01-09 16:27:28 +01:00
Peter Steinberger 7957196924 feat: add TUI bootstrap start 2026-01-09 16:25:29 +01:00
Josh Palmer 25babbfdc4 🤖 codex: fix duplicate agentDir (no-issue) 2026-01-09 16:23:34 +01:00
Emanuel Stadler 277225dc45 Chore: ignore .vscode 2026-01-09 15:21:30 +00:00
Peter Steinberger d3ca84e14c feat: add dev workspace templates 2026-01-09 16:13:41 +01:00
Peter Steinberger 07430eb33d Merge pull request #569 from bjesuiter/ui-build-default-to-relative-path
fix(ui): default to relative paths for control UI assets
2026-01-09 14:47:46 +00:00
Peter Steinberger 9af5b13803 test: make withTempHome cross-platform 2026-01-09 15:47:26 +01:00
Peter Steinberger cfb369d727 docs: add dev gateway flow 2026-01-09 15:47:22 +01:00
Peter Steinberger 2679768447 Merge pull request #577 from clawdbot/fix/node-bridge-keepalive
fix: harden node bridge keepalive
2026-01-09 14:46:59 +00:00
Peter Steinberger d746304371 docs: changelog for keepalive hardening (#577) 2026-01-09 15:46:45 +01:00
Peter Steinberger f5cc6bb283 fix: harden node bridge keepalive 2026-01-09 15:46:45 +01:00
Peter Steinberger 6177c2d575 fix: auto-create dev config for dev profile 2026-01-09 15:45:11 +01:00
Peter Steinberger 616293f8a7 Merge pull request #567 from erikpr1994/fix/gemini-schema-sanitization
fix(agents): remove unsupported JSON Schema keywords for Cloud Code Assist API
2026-01-09 14:44:38 +00:00
Peter Steinberger d77dee50c9 Merge pull request #570 from azade-c/feat/sessions-label
feat(sessions): expose label in sessions.list and support label lookup in sessions_send
2026-01-09 14:44:23 +00:00
Peter Steinberger c4c0f1349a fix: keep build green after main rebase (#570) (thanks @azade-c) 2026-01-09 15:40:36 +01:00
Peter Steinberger 17a7d4e8dd test: stabilize Windows test env (#567) (thanks @erikpr1994) 2026-01-09 15:40:02 +01:00
Peter Steinberger 2aeeeff65f ci: sanitize CI test output 2026-01-09 15:39:16 +01:00
Peter Steinberger d17141b859 fix(status): show usage for token auth profiles 2026-01-09 14:36:46 +00:00
Peter Steinberger 1afa48fcdf style(models): biome format auth order 2026-01-09 14:36:46 +00:00
Peter Steinberger 7cb9e95a53 docs: document auth profile selection 2026-01-09 14:36:46 +00:00
Peter Steinberger 407a3c2c10 fix: quiet telegram getUpdates errors 2026-01-09 15:35:39 +01:00
Peter Steinberger 5f4eb8b509 style: format cli files 2026-01-09 15:34:41 +01:00
Peter Steinberger d28c266771 fix: sanitize Windows test output 2026-01-09 15:32:55 +01:00
Peter Steinberger d5b826ffc8 fix: restore openUrl import 2026-01-09 15:32:55 +01:00
Peter Steinberger 59d942c9ec fix: unblock CI on main (#569) (thanks @bjesuiter) 2026-01-09 15:32:55 +01:00
Peter Steinberger 92b792b3f0 fix: land #569 (thanks @bjesuiter) 2026-01-09 15:32:55 +01:00
Benjamin Jesuiter 7dcf19d902 fix(ui): default to relative paths for control UI assets
Changes the default base path from "/" to "./" so the control UI works
correctly when served under a custom basePath (e.g., /jbclawd/).

Previously, assets were referenced with absolute paths like /assets/...,
which failed when the UI was served under a subpath. With relative paths
(./assets/...), the browser resolves them relative to the HTML location,
making the UI work regardless of the configured basePath.
2026-01-09 15:32:55 +01:00
Peter Steinberger f7e8cd8ac8 chore: regen protocol models (#570) (thanks @azade-c) 2026-01-09 15:32:49 +01:00
Peter Steinberger 56e77f6843 fix: sessions label lookup and persistence (#570) (thanks @azade-c) 2026-01-09 15:32:49 +01:00
Azade e24e0cf364 test(sessions): add tests for sessions_send label lookup
- Test finding session by label
- Test error when label not found
- Test error when neither sessionKey nor label provided
2026-01-09 15:32:49 +01:00
Azade 3133c7c84e feat(sessions): expose label in sessions.list and support label lookup in sessions_send
- Add `label` field to session entries and expose it in `sessions.list`
- Display label column in the web UI sessions table
- Support `label` parameter in `sessions_send` for lookup by label instead of sessionKey

- `sessions.patch`: Accept and store `label` field
- `sessions.list`: Return `label` in session entries
- `sessions_spawn`: Pass label through to registry and announce flow
- `sessions_send`: Accept optional `label` param, lookup session by label if sessionKey not provided
- `agent` method: Accept `label` and `spawnedBy` params (stored in session entry)

- Add `label` column to sessions table in web UI

- Changed session store writes to merge with existing entry (`{ ...existing, ...new }`)
  to preserve fields like `label` that might be set separately

We attempted to implement label persistence "properly" by passing the label
through the `agent` call and storing it during session initialization. However,
the auto-reply flow has multiple write points that overwrite the session entry,
and making all of them merge-aware proved unreliable.

The working solution patches the label in the `finally` block of
`runSubagentAnnounceFlow`, after all other session writes complete.
This is a workaround but robust - the patch happens at the very end,
just before potential cleanup.

A future refactor could make session writes consistently merge-based,
which would allow the cleaner approach of setting label at spawn time.

```typescript
// Spawn with label
sessions_spawn({ task: "...", label: "my-worker" })

// Later, find by label
sessions_send({ label: "my-worker", message: "continue..." })

// Or use sessions_list to see labels
sessions_list() // includes label field in response
```
2026-01-09 15:32:49 +01:00
Peter Steinberger b85854d0fe Merge pull request #575 from clawdbot/blacksmith-migration-944f15e 2026-01-09 14:31:53 +00:00
Peter Steinberger 4b7d9c72df docs: emphasize setup-token 2026-01-09 15:29:50 +01:00
Peter Steinberger e612aedbff docs: prefer setup-token for Anthropic 2026-01-09 15:27:49 +01:00
Peter Steinberger 80ff2dc77d fix: honor gateway --dev flag 2026-01-09 15:25:51 +01:00
Peter Steinberger c228df8f90 fix: rebase onto main + restore build/lint (#567) (thanks @erikpr1994) 2026-01-09 15:23:41 +01:00
Peter Steinberger fd3cbd96a8 test: sanitize Windows CI vitest runner output (#567) (thanks @erikpr1994) 2026-01-09 15:19:49 +01:00
Peter Steinberger cb96deb517 test: harden Windows CI output sanitization (#567) (thanks @erikpr1994) 2026-01-09 15:19:49 +01:00
Peter Steinberger f58b3d082f test: sanitize Windows CI buffer output (#567) (thanks @erikpr1994) 2026-01-09 15:19:49 +01:00
Peter Steinberger 090d16392b test: sanitize Windows CI output (#567) 2026-01-09 15:19:49 +01:00
Peter Steinberger 760e9b3df5 fix: avoid Windows runner unicode crash (#567) 2026-01-09 15:19:49 +01:00
Peter Steinberger 63f5fa47de fix: avoid invalid UTF-16 in truncation (#567) 2026-01-09 15:19:49 +01:00
Peter Steinberger fd535a50d3 fix: scrub tool schemas for Cloud Code Assist (#567) (thanks @erikpr1994) 2026-01-09 15:19:49 +01:00
Erik e9217181c1 fix(agents): remove unsupported JSON Schema keywords for Cloud Code Assist API
Cloud Code Assist API requires strict JSON Schema draft 2020-12 compliance
and rejects keywords like patternProperties, additionalProperties, $schema,
$id, $ref, $defs, and definitions.

This extends cleanSchemaForGemini to:
- Remove all unsupported keywords from tool schemas
- Add oneOf literal flattening (matching existing anyOf behavior)
- Add test to verify no unsupported keywords remain in tool schemas
2026-01-09 15:19:48 +01:00
Peter Steinberger 2c5ec94843 test: stabilize sandbox/doctor tests 2026-01-09 15:17:20 +01:00
Peter Steinberger fc7580ab5e fix: remove configure control-ui prompt 2026-01-09 15:17:20 +01:00
Peter Steinberger adb3bc2577 fix: reset dev gateway setup 2026-01-09 15:17:20 +01:00
Peter Steinberger 3e400ff9f2 feat(models): add per-agent auth order overrides 2026-01-09 14:07:45 +00:00
blacksmith-sh[bot] a596f32a8e Migrate workflows to Blacksmith 2026-01-09 14:06:46 +00:00
Peter Steinberger 944f15e401 fix(discovery): decode dns-sd escaped UTF-8 2026-01-09 14:56:01 +01:00
Peter Steinberger 02aeff8efc style(gateway): multiline discovery output 2026-01-09 14:43:46 +01:00
Peter Steinberger b5c32a4c79 fix(onboarding): remove redundant model spread fallback 2026-01-09 14:16:37 +01:00
Peter Steinberger 6f91bcafdb fix(config): align agents defaults schema 2026-01-09 14:13:52 +01:00
Peter Steinberger 576f8d4681 Merge pull request #572 from ngutman/fix/mac-node-bridge-ping
fix(macos): add node bridge ping loop (AI-assisted)
2026-01-09 13:03:03 +00:00
Peter Steinberger cb86d0d6d4 fix: land mac node bridge ping loop (#572) (thanks @ngutman) 2026-01-09 14:01:20 +01:00
Peter Steinberger f6f178ddee style(telegram): format bot init 2026-01-09 13:57:06 +01:00
Peter Steinberger b3d640b978 fix(onboarding): remove duplicate minimax constants 2026-01-09 13:56:16 +01:00
Peter Steinberger 0fc7a06913 fix(discovery): silence oxlint warning 2026-01-09 13:55:28 +01:00
Peter Steinberger e38cdb3133 docs(cli): fix sandbox links 2026-01-09 13:55:28 +01:00
Peter Steinberger b21e62f072 style: format gateway discovery 2026-01-09 13:55:28 +01:00
Peter Steinberger b0672da396 fix(gateway): fix wide-area discovery update 2026-01-09 13:55:27 +01:00
Peter Steinberger c2d185aab7 fix: normalize routed replies 2026-01-09 13:55:27 +01:00
Peter Steinberger bfadc8f4ee style: format onboard providers 2026-01-09 13:54:53 +01:00
Peter Steinberger 1716c01bdf feat(gateway): improve wide-area discovery 2026-01-09 13:54:53 +01:00
Nimrod Gutman 975aa5bf82 fix(macos): add node bridge ping loop 2026-01-09 13:50:38 +01:00
Peter SteinbergerandMark Pors 7b81d97ec2 feat: wire multi-agent config and routing
Co-authored-by: Mark Pors <1078320+pors@users.noreply.github.com>
2026-01-09 12:48:42 +00:00
Peter Steinberger 81beda0772 feat: migrate agent config to multi-agent layout 2026-01-09 12:44:29 +00:00
Peter Steinberger 82b342e77b fix: respect auth cooldown with auth.order 2026-01-09 12:44:29 +00:00
Peter Steinberger 4166cd12b6 Merge pull request #495 from tobiasbischoff/main
Onboarding: add hosted MiniMax M2.1 API key option
2026-01-09 12:43:00 +00:00
Peter Steinberger ac922e83d3 Merge branch 'main' into tobias-sync 2026-01-09 13:42:34 +01:00
Peter Steinberger dc6f22c2c5 fix: add hosted minimax onboarding (#495, thanks @tobiasbischoff) 2026-01-09 13:40:01 +01:00
Tobias Bischoff 897685a2de Telegram: cast fetch to grammy client type 2026-01-09 13:39:28 +01:00
Tobias Bischoff a0bb2bccaf Onboarding: add MiniMax hosted API key option 2026-01-09 13:39:28 +01:00
Josh Palmer 7b79823b24 Adjust UI install for offline pnpm fetch (#568)
* 🤖 codex: make ui build install prod deps for offline pnpm (issue-pnpm-offline)

* 🤖 codex: ensure ui:test installs dev deps (issue-pnpm-offline)
2026-01-09 13:38:46 +01:00
Peter Steinberger 17a7dfc966 chore: update dev identity default 2026-01-09 13:34:45 +01:00
Peter Steinberger 22d517a520 fix: clarify WhatsApp owner number prompts 2026-01-09 13:32:52 +01:00
Peter Steinberger 201c879772 fix(sandbox): compare list to config images (#563) - thanks @pasogott 2026-01-09 13:29:47 +01:00
sheeek 7883491ce2 test(sandbox): add unit tests for formatter functions
Add comprehensive tests for sandbox-formatters.ts (20 tests):
- formatStatus: running/stopped with emojis
- formatSimpleStatus: running/stopped without emojis
- formatImageMatch: match/mismatch indicators
- formatAge: seconds, minutes, hours, days with edge cases
- countRunning: count items by running state
- countMismatches: count items by image mismatch

All pure functions now covered. 152 LOC tests added.

Total test count: 39 tests (19 integration + 20 unit)
Test coverage increased from ~66% to ~80%.
2026-01-09 13:23:16 +01:00
sheeek 7f02b62bba style(sandbox): fix linting errors
- Remove unused normalizeOptions function
- Fix line length violations (format long lines)
- Fix import order (alphabetical)
- Format function signatures for readability

All lint checks now passing.
2026-01-09 13:23:16 +01:00
sheeek b0c97d6178 refactor(sandbox-cli): improve structure and reduce duplication
Improvements:
- Extract help text examples into EXAMPLES constant object
- Add createRunner() helper to reduce try-catch boilerplate
- Add normalizeOptions() helper (prepared for future use)
- Fix command names from 'clawd' to 'clawdbot' throughout
- Update main sandbox command to show help by default
- Better organize code with clear section comments

Increases from 82 to 137 LOC but with much better organization
and reduced duplication in error handling.
2026-01-09 13:23:16 +01:00
sheeek 1c757ae35e refactor(sandbox): use extracted display and formatter modules
Update sandbox.ts to import and use functions from:
- sandbox-display.ts for all UI output
- sandbox-formatters.ts for data formatting

Removes 141 LOC of display/formatting code from sandbox.ts,
reducing it from 351 to 210 LOC (-40%).

Core business logic now clearer and more focused.
2026-01-09 13:23:16 +01:00
sheeek 81c55be19b refactor(sandbox): extract display logic into separate module
Move all display functions to sandbox-display.ts:
- displayContainers, displayBrowsers with generic displayItems helper
- displaySummary with image mismatch warnings
- displayRecreatePreview, displayRecreateResult

Uses DisplayConfig pattern to reduce duplication between container
and browser display logic. 156 LOC.
2026-01-09 13:23:16 +01:00
sheeek dd0104290e refactor(sandbox): extract formatters into separate module
Move formatting utilities to sandbox-formatters.ts:
- formatStatus, formatSimpleStatus, formatImageMatch, formatAge
- countRunning, countMismatches helper functions
- ContainerItem type definition

Improves modularity and reusability. 49 LOC.
2026-01-09 13:23:16 +01:00
sheeek 6ca34c1259 docs(sandbox): fix command name from clawd to clawdbot
Update all command examples in documentation to use 'clawdbot' instead
of 'clawd' for consistency with actual CLI command name.
2026-01-09 13:23:16 +01:00
sheeek c3932c8508 test(sandbox): add comprehensive test suite for CLI commands
Add 19 tests covering sandboxListCommand and sandboxRecreateCommand:
- List command: human/JSON output, browser flag, error handling
- Recreate command: validation, filtering (session/agent), confirmation flow
- Factory functions (createContainer, createBrowser) reduce duplication
- Helper functions (expectLogContains, setupDefaultMocks) improve readability

All tests passing. 365 LOC with ~66% production code coverage.
2026-01-09 13:23:16 +01:00
sheeek 00b77421dd refactor: improve sandbox commands code structure
Improvements:
- Extract validation into separate function
- Split display logic from business logic
- Create reusable container matcher for agent filtering
- Abstract status/image formatting into helpers
- Reduce code duplication between containers and browsers
- Extract container removal into generic function
- Add type safety with FilteredContainers type
- Improve readability with smaller, focused functions

Changes:
- validateRecreateOptions(): Validate mutual exclusivity
- fetchAndFilterContainers(): Fetch + filter in one place
- createAgentMatcher(): Reusable agent filter predicate
- displayContainers/Browsers(): Dedicated display functions
- displaySummary/RecreatePreview/Result(): Clear separation
- removeContainer(): Generic removal with error handling
- Format helpers: formatStatus, formatImageMatch, etc.
- Count helpers: countRunning, countMismatches

Result: 85 more lines but much better maintainability and testability.
2026-01-09 13:23:16 +01:00
sheeek bcd52ee546 chore: update CHANGELOG for sandbox CLI commands 2026-01-09 13:23:16 +01:00
sheeek 75927d736a docs: add sandbox CLI documentation 2026-01-09 13:22:51 +01:00
sheeek 5e78d5a21f feat: add sandbox CLI commands for container management
Add 'clawd sandbox list' and 'clawd sandbox recreate' commands to manage
sandbox containers. This fixes the issue where containers continue using
old images/configs after updates.

Problem:
- When sandbox Docker images or configs are updated, existing containers
  keep running with old settings
- Containers are only recreated after 24h inactivity (pruning)
- If agents are used regularly, old containers run indefinitely

Solution:
- 'clawd sandbox list': Show all containers with status, age, and image match
- 'clawd sandbox recreate': Force container removal (recreated on next use)
  - Supports --all, --session, --agent, --browser filters
  - Requires confirmation unless --force is used

Implementation:
- Added helper functions to sandbox.ts (list/remove containers)
- Created sandbox-cli.ts following existing CLI patterns
- Created commands/sandbox.ts with list and recreate logic
- Integrated into program.ts

Use case: After updating sandbox images or changing sandbox config,
run 'clawd sandbox recreate --all' to ensure fresh containers.
2026-01-09 13:22:51 +01:00
Peter SteinbergerandOnur ae6f268987 refactor(msteams): validate send recipient
Co-authored-by: Onur <onutc@users.noreply.github.com>
2026-01-09 11:22:59 +01:00
Peter Steinberger 08cceb6435 fix(config): add gateway remote ssh fields 2026-01-09 11:18:37 +01:00
Peter Steinberger 6b107e9e74 refactor(msteams): consolidate stores and send context 2026-01-09 11:18:33 +01:00
Peter Steinberger 6d223303eb refactor(msteams): extract sdk + storage helpers 2026-01-09 11:07:32 +01:00
Peter Steinberger 8875dbd449 refactor(msteams): split monitor handler and poll store 2026-01-09 11:07:32 +01:00
Peter Steinberger 475d598ecb fix: normalize poll providers and msteams deps 2026-01-09 11:07:32 +01:00
Peter Steinberger e55358c65d feat: finalize msteams polls + outbound parity 2026-01-09 11:07:32 +01:00
Onur a2bba7ef51 fix(msteams): add msteams to pairing provider labels 2026-01-09 11:07:32 +01:00
Onur 7f326ae4ae fix: sync doctor-prompter.ts with main 2026-01-09 11:07:17 +01:00
Onur 13b47e6047 fix(doctor): restore initialValue behavior in non-interactive mode
The repair/force commit added an early return that broke non-interactive
mode - migrations would return false instead of using initialValue.
2026-01-09 11:07:01 +01:00
Onur 0bc50abd73 style: format quickstart note and media-note test 2026-01-09 11:07:00 +01:00
Onur c469fac8ef Tests: speed up media note prompt setup 2026-01-09 11:07:00 +01:00
Onur a542a285a3 Wizard: preserve QuickStart gateway settings 2026-01-09 11:07:00 +01:00
Onur eb908985f7 Gateway: fix reload tests for MS Teams provider 2026-01-09 11:07:00 +01:00
Onur 1df7bfefe7 MSTeams: stop on shutdown and honor chunk limit 2026-01-09 11:07:00 +01:00
Onur 9f9b6044aa docs(msteams): expand setup guide with missing details
- Add Azure Bot deprecation notice (multi-tenant deprecated after 2025-07-31)
- Add RSC vs Graph API comparison table
- Add Known Limitations section (webhook timeouts, formatting limits)
- Add Reply Style section explaining threads vs posts channel UI
- Add Updating an existing app section with version bump steps
- Add icon sizes and zip instructions to manifest setup
- Expand troubleshooting with manifest upload errors
2026-01-09 11:07:00 +01:00
Onur 73c1d11eb9 Remove tmp files 2026-01-09 11:07:00 +01:00
Onur ea1cd2c734 wip 2026-01-09 11:07:00 +01:00
Onur 54d82e4e83 wip [skip ci] 2026-01-09 11:07:00 +01:00
Onur 3cb3460398 add doc 2026-01-09 11:07:00 +01:00
Onur 96b4d885ac wip [skip ci] 2026-01-09 11:07:00 +01:00
Onur 678d704341 image works in DM 2026-01-09 11:07:00 +01:00
Onur 15e6761035 wip [skip ci] 2026-01-09 11:06:49 +01:00
Onur e67ca92443 MS Teams: ingest inbound image attachments 2026-01-09 11:06:49 +01:00
Onur 2ab5890eab wip 2026-01-09 11:06:49 +01:00
Onur d6256a388e feat(msteams): wire up proactive messaging in routeReply for queued replies 2026-01-09 11:06:49 +01:00
Onur 04b1eb57eb MS Teams: fix top-level replies (agent reference) 2026-01-09 11:06:39 +01:00
Onur 8d096ef85d Tests: stabilize file watchers 2026-01-09 11:06:39 +01:00
Onur a2bab7d923 MS Teams: refactor provider + replyStyle + reliability 2026-01-09 11:06:38 +01:00
Onur 81f81be816 feat(msteams): add replyStyle config for thread vs top-level replies
- Add replyStyle config at global, team, and channel levels
- "thread" replies to the original message (for Posts layout channels)
- "top-level" posts as a new message (for Threads layout channels)
- Default based on requireMention: false → top-level, true → thread
- DMs always use thread style (direct reply)
2026-01-09 11:06:28 +01:00
Onur 269a3c4000 feat(msteams): add outbound sends and fix reply delivery
- Add sendMessageMSTeams for proactive messaging via CLI/gateway
- Wire msteams into outbound delivery, heartbeat targets, and gateway send
- Fix reply delivery to use SDK's getConversationReference() for proper
  bot info, avoiding "Activity Recipient undefined" errors
- Use proactive messaging for replies to post as top-level messages
  (not threaded) by omitting activityId from conversation reference
- Add lazy logger in send.ts to avoid test initialization issues
2026-01-09 11:06:14 +01:00
Onur 2c7d5c82f3 feat(msteams): add per-channel requireMention config
- Add teams/channels config structure to MSTeamsConfig
- Implement requireMention check in monitor.ts
  - Resolution order: channel > team > global > default (true)
- Update zod schema for validation
- Document RSC permissions for receiving all messages without @mention
- Document Graph API Proxy pattern for historical message access
- Document private channel limitations
- Document team/channel ID format (use URL path, not groupId)
2026-01-09 11:05:46 +01:00
Onur 7d72fcf7f8 add notes [skip ci] 2026-01-09 11:05:34 +01:00
Onur e0812f8c4d feat(msteams): add config reload, DM policy, proper shutdown
- Add msteams to config-reload.ts (ProviderKind, ReloadAction, rules)
- Add msteams to PairingProvider for pairing code support
- Create conversation-store.ts for storing ConversationReference
- Implement DM policy check (disabled/pairing/open/allowlist)
- Fix WasMentioned to check actual bot mentions via entities
- Fix server shutdown by using custom Express server with httpServer.close()
- Pass authConfig to CloudAdapter for outbound call authentication
- Improve error logging with JSON serialization
2026-01-09 11:05:34 +01:00
Onur 1c73d45106 feat(msteams): wire agent integration for Teams messages
- Integrate dispatchReplyFromConfig() for full agent routing
- Add msteams to TextChunkProvider and OriginatingChannelType
- Add msteams case to route-reply (proactive not yet supported)
- Strip @mention HTML tags from Teams messages
- Fix session key to exclude messageid suffix
- Add typing indicator support
- Add proper logging for debugging
2026-01-09 11:05:34 +01:00
Onur 951789e9fa wip [skip ci] 2026-01-09 11:05:34 +01:00
Onur d9cbecac7f feat(msteams): add MS Teams provider skeleton
- Add Microsoft 365 Agents SDK packages (@microsoft/agents-hosting,
  @microsoft/agents-hosting-express, @microsoft/agents-hosting-extensions-teams)
- Add MSTeamsConfig type and zod schema
- Create src/msteams/ provider with monitor, token, send, probe
- Wire provider into gateway (server-providers.ts, server.ts)
- Add msteams to all provider type unions (hooks, queue, cron, etc.)
- Update implementation guide with new SDK and progress
2026-01-09 11:05:34 +01:00
Onur 7274d6e757 docs: add detailed Azure Bot creation fields [skip ci]
- Direct link to Azure Bot creation page
- Field-by-field table for Project details
- Pricing tier options
- Microsoft App ID settings (Single Tenant, Create new)
- Note about SDK version requirement
2026-01-09 11:03:32 +01:00
Onur 0bb9756e0c docs: add useful links to Azure setup section [skip ci] 2026-01-09 11:03:32 +01:00
Onur 8e02c53df2 docs: add Azure Bot setup instructions
Added section 2.5 with step-by-step Azure setup:
- Create Azure Bot (single-tenant)
- Get credentials (appId, appPassword, tenantId)
- Configure messaging endpoint
- Enable Teams channel
- Local dev tunnel options (ngrok, Tailscale)
- Teams App manifest template
- Config example
2026-01-09 11:03:32 +01:00
Onur 051da852a2 docs: rename to msteams-implementation-guide.md 2026-01-09 11:03:32 +01:00
Onur 3b53a84459 docs: rewrite MS Teams research as implementation guide
Codex (gpt-5.2 xhigh) rewrote the doc:

- Added MVP scope definition
- Verified repo conventions against actual codebase
- Added 2025/2026 Microsoft guidance (CloudAdapter, single-tenant default)
- Concrete code examples (monitor, send, webhook, adapter)
- Detailed integration checklist (all files to modify)
- 9 MS Teams gotchas to plan for
- 12 actionable implementation steps
- Current references (2026-01)
2026-01-09 11:03:32 +01:00
Onur 01e737e90e docs: add MS Teams provider research document
Initial research and implementation guide for adding msteams as a new
messaging provider. Includes:

- Provider structure patterns from existing implementations
- Gateway integration requirements
- Config types and validation schemas
- Onboarding flow patterns
- MS Teams Bot Framework SDK considerations
- Files to create/modify checklist

This is exploratory work - implementation plan to follow.
2026-01-09 11:03:32 +01:00
Peter Steinberger fe69bc9439 fix(config): allow gateway.remote ssh fields 2026-01-09 10:50:19 +01:00
Peter Steinberger d258c68ca1 feat: add gateway dev config options 2026-01-09 10:39:00 +01:00
Luke 750dea3edf Merge branch 'main' into commands-list-clean 2026-01-09 04:21:47 -05:00
Luke K (pr-0f3t) 5f4df5e336 Auto-reply: drop native command flag 2026-01-09 04:14:36 -05:00
Peter Steinberger 0c167e85af style: sort configure imports 2026-01-09 10:03:52 +01:00
Peter Steinberger e0c310d056 chore: bump versions to 2026.1.9 2026-01-09 10:02:15 +01:00
Peter Steinberger 827e68eadd feat: improve auth setup flows 2026-01-09 09:59:58 +01:00
Peter Steinberger 3db52c972d fix: repair typing for thinking promotion 2026-01-09 08:37:38 +00:00
Peter Steinberger 17ccf53eb1 fix: normalize <think> reasoning blocks 2026-01-09 08:30:05 +00:00
Peter Steinberger 5b50c97939 feat(cli): improve gateway status output 2026-01-09 09:27:42 +01:00
Peter Steinberger 89132fdd25 chore(lint): biome import order 2026-01-09 09:27:42 +01:00
Peter Steinberger 8dd2286e3e docs: add message examples to cli index 2026-01-09 09:14:31 +01:00
Luke aa64abe84c Merge branch 'main' into commands-list-clean 2026-01-09 03:12:08 -05:00
Peter Steinberger 9998293dff docs: summarize message subcommands 2026-01-09 09:08:55 +01:00
Peter Steinberger 8d67bd2889 feat: apply lobster palette to prompts 2026-01-09 09:05:24 +01:00
Peter Steinberger f9be9ad426 feat: switch message cli to subcommands 2026-01-09 09:00:41 +01:00
Peter Steinberger 7ed53e243d fix(cli): clear lint warnings 2026-01-09 08:51:22 +01:00
Peter Steinberger 0a31112fb4 docs(changelog): mention gateway discover 2026-01-09 08:46:50 +01:00
Peter Steinberger 732972be2b style: format control ui assets log 2026-01-09 08:32:15 +01:00
Peter Steinberger 721183e259 feat: unify message cli and tools 2026-01-09 08:30:24 +01:00
Peter Steinberger 77d4bb8dfe feat: update token auth flow 2026-01-09 08:13:05 +01:00
Peter Steinberger a4d6638f89 fix: import outbound delivery types 2026-01-09 08:13:04 +01:00
Peter Steinberger c32e3c467d fix: map CLI deps for outbound sends 2026-01-09 08:13:04 +01:00
Peter Steinberger 185727c696 style: fix lint formatting 2026-01-09 08:13:04 +01:00
Peter Steinberger 37cbcc97d3 feat: support token auth profiles 2026-01-09 08:13:04 +01:00
Peter Steinberger eced473e05 feat: add models auth commands 2026-01-09 08:13:04 +01:00
Peter Steinberger af1f6fab29 chore: add lobster CLI banner art 2026-01-09 08:13:04 +01:00
Peter Steinberger 8808d8c84c docs: add gateway CLI doc 2026-01-09 08:13:04 +01:00
Peter Steinberger 266643bb94 feat(gateway): discover on local + wide-area DNS-SD 2026-01-09 08:13:04 +01:00
Peter Steinberger 2062165cd3 chore: add configure section hints 2026-01-09 08:13:04 +01:00
Peter Steinberger c807c7e72d chore: clarify multi-agent reporting 2026-01-09 08:13:04 +01:00
Peter Steinberger 581da80209 chore: log gateway reload and signals 2026-01-09 08:13:04 +01:00
Peter SteinbergerandAndrew Ting 394027dffb docs: add special thanks for OAuth fix
Co-authored-by: Andrew Ting <andrewtingnyc@gmail.com>
2026-01-09 08:13:04 +01:00
Peter Steinberger 60b282cf1d fix: improve web media fetch errors 2026-01-09 07:09:15 +00:00
Peter Steinberger c46bab35df fix: drop explicit ui:install step 2026-01-09 07:02:42 +00:00
Miles 27952bb94d imessage: isolate group-ish threads by chat_id 2026-01-09 00:01:01 -06:00
Shadow 95c2ccbd7b fix: remove incorrect discord limited warning 2026-01-08 23:59:40 -06:00
Peter Steinberger 458cbcfb41 docs: note restart disabled by default 2026-01-09 05:52:44 +00:00
Peter Steinberger 0a026fea1c fix: disable restart by default 2026-01-09 05:49:30 +00:00
Peter Steinberger db22207014 feat: add message tool and CLI 2026-01-09 06:44:20 +01:00
Peter Steinberger 48a1b07097 fix: promote selected auth profile order 2026-01-09 05:36:14 +00:00
Peter Steinberger 8e35ad5484 style: format after gate 2026-01-09 05:27:54 +00:00
Peter Steinberger 482364aa92 test: stabilize claude cli serialization test 2026-01-09 05:27:50 +00:00
Peter Steinberger 3aa5f45094 chore: update dependencies 2026-01-09 05:25:08 +00:00
Peter Steinberger 31a2830f7d fix: support /models alias for /model 2026-01-09 06:24:40 +01:00
Peter Steinberger 133124e023 fix: strip thinking tags in chat UI 2026-01-09 05:24:11 +00:00
Peter Steinberger 7842109609 fix: report auth label from configured order 2026-01-09 05:17:27 +00:00
Peter Steinberger f9fe067f68 fix: normalize anthropic model ids 2026-01-09 05:07:16 +00:00
Peter Steinberger 9114331218 fix: serialize claude cli runs 2026-01-09 04:58:21 +00:00
Peter Steinberger aa5e75e853 fix: align tool rename fallout 2026-01-09 05:54:34 +01:00
ClaudeandClaude Opus 4.5 333832c2e1 fix: bypass Anthropic OAuth token blocking for tool names
Anthropic blocks specific lowercase tool names (bash, read, write, edit)
when using OAuth tokens. This fix:

1. Renames blocked tools to capitalized versions (Bash, Read, Write, Edit)
   in pi-tools.ts via renameBlockedToolsForOAuth()

2. Passes all tools as customTools in splitSdkTools() to bypass
   pi-coding-agent's built-in tool filtering, which expects lowercase names

The capitalized names work with both OAuth tokens and regular API keys.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 05:48:54 +01:00
Peter Steinberger a69a863090 chore: update pi deps 2026-01-09 05:38:21 +01:00
Luke a4806e9417 Merge branch 'main' into commands-list-clean 2026-01-08 23:21:31 -05:00
Peter Steinberger ef1ce5d9a8 fix: avoid claude-cli session id collisions 2026-01-09 04:20:58 +00:00
Luke K (pr-0f3t) 5d34e27078 Revert: non-PR file edits 2026-01-08 23:19:44 -05:00
Peter Steinberger 64fc5fa9fc fix: allow default model outside allowlist 2026-01-09 04:18:30 +00:00
Luke K (pr-0f3t) 16f893c46b Tests: align google shared expectations 2026-01-08 23:12:37 -05:00
Peter Steinberger 7e4d5c9f84 test: align status and google-shared expectations 2026-01-09 05:03:12 +01:00
Luke 1be8a39442 Merge branch 'main' into commands-list-clean 2026-01-08 22:58:54 -05:00
Peter Steinberger 709d5d9cd6 fix: add spacing to daemon status output 2026-01-09 03:52:51 +00:00
Peter Steinberger 9a8fe4d683 feat(agent): add claude cli runner 2026-01-09 04:52:37 +01:00
Luke K (pr-0f3t) 1309cee124 Auto-reply: fix status command lint 2026-01-08 22:51:35 -05:00
Luke K (pr-0f3t) 8aa80fa464 Auto-reply: tidy help message 2026-01-08 22:48:46 -05:00
Peter Steinberger 8e27ea7371 feat: add raw stream logging flags 2026-01-09 03:45:21 +00:00
Peter Steinberger 69546d563d fix: combine status usage and cost line 2026-01-09 03:45:01 +00:00
Peter Steinberger 16cfd24967 fix: colorize daemon status output 2026-01-09 03:22:02 +00:00
Luke b97a9235e0 Merge branch 'main' into commands-list-clean 2026-01-08 22:19:52 -05:00
Peter Steinberger 3e19f82af2 fix: repair status reply call 2026-01-09 03:18:20 +00:00
Peter Steinberger d8a23cf5ab fix: restore emoji-rich status output 2026-01-09 03:15:33 +00:00
Luke K (pr-0f3t) 6c8b2a8dc9 Auto-reply: organize status imports 2026-01-08 22:14:24 -05:00
Peter Steinberger 875b849322 feat(ui): link sessions to chat page (#471) — thanks @HazAT 2026-01-09 04:07:56 +01:00
Daniel Griesser 2e1dee197a feat(ui): link sessions to chat page
- Session names in the Sessions table are now clickable links
- Clicking navigates to /chat?session=<key> with that session loaded
- Global sessions excluded (not real conversations)
- Added .session-link CSS styling (accent color, underline on hover)
- Chat page reads 'session' query param and cleans URL after applying
2026-01-09 04:05:33 +01:00
Peter Steinberger b3b84ffefa fix: fallback /model list when catalog is unavailable 2026-01-09 03:56:19 +01:00
Luke e52a2888cc Merge branch 'main' into commands-list-clean 2026-01-08 21:47:09 -05:00
Peter Steinberger 0cbc5fea93 fix: keep status for directive-only messages 2026-01-09 03:46:00 +01:00
Peter Steinberger dc81d0a649 fix: improve /status auth label 2026-01-09 03:39:02 +01:00
Peter Steinberger 75d193a284 test: update google shared expectations 2026-01-09 02:34:16 +00:00
Peter Steinberger 3c79d5c711 fix: keep /status usage filtering 2026-01-09 03:31:02 +01:00
Peter Steinberger 151523f47b feat: add usage cost reporting 2026-01-09 02:29:54 +00:00
Peter Steinberger dfbee10377 docs: clarify sandbox non-main behavior 2026-01-09 03:23:36 +01:00
Peter Steinberger 1a295d9460 fix: improve status usage filtering and directives 2026-01-09 03:18:47 +01:00
Peter Steinberger 84f668f9c5 test: align google-shared expectations with pi-ai 0.40.0 2026-01-09 03:12:32 +01:00
Peter Steinberger 468889abef fix: refine status usage and elevated directives 2026-01-09 03:09:56 +01:00
Peter Steinberger 8a3e100ad1 test: update google-shared expectations 2026-01-09 03:04:16 +01:00
Peter Steinberger fb1ec2cfbf Merge pull request #526 from neist/feat/system-prompt-messaging-guide
feat(system-prompt): add messaging guidance section
2026-01-09 02:02:04 +00:00
Peter Steinberger 362f62f325 docs: update changelog and clawtributors for #526 2026-01-09 03:00:42 +01:00
Kasper Neist 3b78870f33 feat(system-prompt): add messaging guidance section
Adds a brief 'Messaging' section to the system prompt to guide agents on:
- Reply in session = auto-routes to source provider
- Cross-session = use sessions_send
- Never use bash/curl for provider messaging

This helps prevent agents from using shell workarounds for messaging
when Clawdbot already handles routing internally.
2026-01-09 03:00:40 +01:00
Luke c678216ac2 Merge branch 'main' into commands-list-clean 2026-01-08 20:58:26 -05:00
Peter Steinberger 4827c283a6 docs: expand logging guidance 2026-01-09 02:56:29 +01:00
Peter Steinberger 0a722de65c test: cover log line parsing 2026-01-09 02:54:04 +01:00
Peter Steinberger 3a99ac7e9a fix: honor mention-bypass for group commands 2026-01-09 02:52:44 +01:00
Peter Steinberger efa5f0bfe0 feat: improve logs output and docs 2026-01-09 02:51:17 +01:00
Peter Steinberger e1789ba9e5 fix: set gemini default model for api-key auth (#489) - thanks @jonasjancarik 2026-01-09 02:49:42 +01:00
Jonáš Jančařík 302d51fd40 feat: add Gemini API key onboarding 2026-01-09 02:45:15 +01:00
Peter Steinberger 340d5d03f2 docs: note PR 541 and add clawtributor 2026-01-09 02:38:03 +01:00
Ogulcan Celik 7bb0841f42 fix: prevent systemd hang on restart with podman sandboxes
Add KillMode=process to generated systemd unit file. Without this,
podman's conmon processes (which monitor sandbox containers) block
shutdown since they're children of the gateway process.

This preserves the desired behavior of keeping containers alive
across restarts while preventing systemd from waiting indefinitely.
2026-01-09 02:37:00 +01:00
Peter Steinberger a738ade0ec chore: update dependencies 2026-01-09 02:34:39 +01:00
Kasper Neist 514b85ba33 feat(system-prompt): add messaging guidance section
Adds a brief 'Messaging' section to the system prompt to guide agents on:
- Reply in session = auto-routes to source provider
- Cross-session = use sessions_send
- Never use bash/curl for provider messaging

This helps prevent agents from using shell workarounds for messaging
when Clawdbot already handles routing internally.
2026-01-09 02:21:55 +01:00
Jake 0dbb569187 chore: bump Pi to 0.40.0 and revert PR #454 pi-ai patch
- Upgrade @mariozechner/pi-* packages from 0.38.0 to 0.40.0
- Remove pi-ai patch (upstream 429 fix in 0.40.0 supersedes PR #454)
- Update CHANGELOG
2026-01-09 02:12:19 +01:00
Peter Steinberger 75af0e61da docs: thank pasogott for Ansible guide (#545) 2026-01-09 02:10:19 +01:00
sheeek b8afbfbb44 docs: add Ansible installation guide 2026-01-09 02:09:02 +01:00
Peter Steinberger 14096fb629 docs: add model allowlist + reasoning safety notes 2026-01-09 02:07:33 +01:00
Peter Steinberger 9c33080f12 Merge pull request #534 from mcinteerj/fix/whatsapp-queued-routing
WhatsApp: fix routing for queued messages
2026-01-09 01:05:16 +00:00
Peter Steinberger 97635a8966 test: assert WhatsApp queued routing context (#534) thanks @mcinteerj 2026-01-09 02:04:05 +01:00
Peter Steinberger f2246df875 fix: route WhatsApp queued replies to sender (#534) 2026-01-09 02:03:30 +01:00
Jake a52e75d978 WhatsApp: fix routing for queued messages by using correct OriginatingTo 2026-01-09 02:01:33 +01:00
Peter Steinberger 7f12548615 docs: explain model allowlist errors 2026-01-09 01:59:22 +01:00
Peter Steinberger 57d3d6bd78 docs: clarify workspace vs sandbox 2026-01-09 01:54:28 +01:00
Peter Steinberger 455723375e fix: show directive options on query 2026-01-09 01:40:44 +01:00
Peter Steinberger 8ee7223587 docs: add clawdhub install/sync guidance 2026-01-09 01:38:19 +01:00
Peter Steinberger ae9f5ecc2d docs: clarify per-agent vs shared skills 2026-01-09 01:37:24 +01:00
Peter Steinberger 948ce5eb5f feat(models): add oauth auth health 2026-01-09 00:34:38 +00:00
L36 Server bcec534e5e docs: add authentication to gateway navigation 2026-01-09 00:34:38 +00:00
L36 Server 4ca2677815 docs: add authentication guide with 1-year token setup 2026-01-09 00:34:38 +00:00
L36 Server 6484195bfe scripts: add auth management and Termux widget scripts 2026-01-09 00:34:38 +00:00
Luke daafa9e3bc Merge branch 'main' into commands-list-clean 2026-01-08 19:33:56 -05:00
Peter Steinberger 8f21f34b1c docs(changelog): note webchat queue (#527) 2026-01-09 01:32:43 +01:00
Peter Steinberger 94c61aa19d feat(webchat): queue outgoing messages 2026-01-09 01:30:38 +01:00
Yurii ChukhlibandClaude e84cafec8a feat(webchat): change Enter key to send message
Changes webchat textarea behavior:
- Enter key now sends message (was Cmd/Ctrl+Enter)
- Shift+Enter allows line breaks
- Updated placeholder text to reflect new behavior

Fixes #411

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-09 01:30:37 +01:00
Jake bd4bc16192 WhatsApp: fix routing for queued messages by using correct OriginatingTo 2026-01-09 13:28:31 +13:00
Peter SteinbergerandJonathan D. Rhyne ab1896dc13 docs: add PR #519 changelog
Co-authored-by: Jonathan D. Rhyne <jonathan@pspdfkit.com>
2026-01-09 01:16:57 +01:00
Peter Steinberger 9b901889ee docs: rewrite TUI guide 2026-01-09 01:05:10 +01:00
Jonathan D. Rhyne debdd2aa95 fix(tui): status bar not updating after /verbose, /reasoning, /think commands
The status bar refresh failed because refreshSessionInfo() compared
currentSessionKey (e.g. 'main') against the canonical session keys
returned by the gateway (e.g. 'agent:default:main').

This fix uses parseAgentSessionKey to also match canonical keys by
their 'rest' component, allowing 'main' to match 'agent:default:main'.

Fixes: status bar showing stale values after setting changes
AI-assisted: yes (Claude)
Testing: lightly tested (build passes, logic verified)
2026-01-09 01:05:10 +01:00
Peter Steinberger 714e170c16 feat(tui): add agent picker and agents list rpc 2026-01-09 01:05:10 +01:00
Peter Steinberger a5f0f62e0d docs: fix models doc merge marker 2026-01-09 00:58:50 +01:00
Peter Steinberger e5dbba7b67 fix: sort imports for lint 2026-01-09 00:57:17 +01:00
Peter Steinberger 7a0614c850 Merge pull request #539 from clawdbot/fix/inline-directive-spacing
Preserve spacing after inline directives
2026-01-08 23:51:44 +00:00
Peter Steinberger 27adabbeea test: cover reasoning directive spacing (thanks @joshp123) (#539) 2026-01-09 00:51:05 +01:00
Peter Steinberger f666f60731 fix: preserve inline directive spacing tests (thanks @joshp123) (#539) 2026-01-09 00:37:40 +01:00
Josh Palmer 43545a4864 🤖 codex: preserve spacing after inline directives (issue-telegram-inline-spacing) 2026-01-09 00:37:40 +01:00
Peter Steinberger 4e92ccc0dd style: format 2026-01-09 00:33:09 +01:00
Peter Steinberger 71309c064a fix: drop redundant telegram audit union 2026-01-09 00:33:09 +01:00
Peter Steinberger 01b8f4582f fix: align discord monitor + whatsapp activity 2026-01-09 00:33:09 +01:00
Peter Steinberger e09708e82d feat: sync skills into sandbox workspace 2026-01-09 00:33:09 +01:00
Peter Steinberger 561fa99d95 feat(providers): show status spinner message 2026-01-08 23:30:38 +00:00
Peter Steinberger 7ece3717e6 refactor(pairing): centralize reply formatting 2026-01-08 23:29:23 +00:00
Peter Steinberger e952f7df96 docs: drop Updated headers 2026-01-09 00:20:32 +01:00
Peter Steinberger a5cb51282c docs: clarify WhatsApp number modes 2026-01-09 00:19:51 +01:00
Peter Steinberger e0439df4ce feat(pairing): show sender ids across providers 2026-01-08 23:19:13 +00:00
Peter Steinberger 7c7858a519 refactor(signal): normalize sender identity 2026-01-08 23:08:25 +00:00
Kasper Neist 9e9f2babeb fix(signal): use sourceUuid as fallback when sourceNumber is null
Users without a visible phone number (like some Signal users)
were being silently dropped. Now falls back to UUID for sender ID.

- Add sourceUuid to SignalEnvelope type
- Use sourceUuid when sourceNumber is not available
- Only check against bot account when sourceNumber exists (avoid UUID comparison issues)
2026-01-08 23:08:25 +00:00
Peter Steinberger 490bbd10fc feat(telegram): show user id in pairing 2026-01-09 00:01:23 +01:00
Peter Steinberger 829782c42c docs(changelog): note provider troubleshooting index 2026-01-08 23:56:54 +01:00
Peter Steinberger db7ea832a7 docs: add PR #476 to changelog 2026-01-08 23:52:55 +01:00
gupsammy 12231e3f59 chore: add .local to gitignore 2026-01-08 23:52:55 +01:00
gupsammy df1fdbeb2b docs: add 3 showcase entries from Discord 2026-01-08 23:52:55 +01:00
Shadow 9f8336c92b Discord: harden gateway resilience 2026-01-08 16:52:44 -06:00
Peter Steinberger 769b76cd40 test(provider-activity): restore real timers 2026-01-08 23:52:07 +01:00
Peter Steinberger 69f8af530d feat(providers): improve doctor + status probes 2026-01-08 23:48:37 +01:00
Peter Steinberger 41d484d239 test: cover browser and canvas verbose summaries 2026-01-08 23:48:37 +01:00
Peter Steinberger 2f1ce51b2c test: stabilize doctor + bash tool tests 2026-01-08 23:44:54 +01:00
Peter Steinberger 4360d1106d Merge pull request #532 from mcinteerj/docs/webhook-params-descriptions
docs: expand parameter descriptions for agent/wake hooks
2026-01-08 22:38:34 +00:00
Peter Steinberger 4655360546 chore: update changelog for #532 (thanks @mcinteerj) 2026-01-08 23:38:17 +01:00
Jake 89d01f15a5 docs: expand parameter descriptions for agent/wake hooks 2026-01-08 23:38:17 +01:00
Peter Steinberger b1d4bd0f78 docs: require code-verified answers 2026-01-08 23:33:41 +01:00
Peter Steinberger 6f6af83033 Merge pull request #521 from fishfisher/update-app-icons
chore(apps): rebrand app icons from Clawdis to Clawdbot
2026-01-08 22:31:55 +00:00
Peter Steinberger dc9fdf335d chore: update changelog and clawtributors (#521, thanks @fishfisher) 2026-01-08 23:31:24 +01:00
Erik von Essen FisherandClaude Sonnet 4.5 fb0c566b2a Rebrand app icons from Clawdis to Clawdbot
Replace app icons with new Clawdbot branding (lobster-in-phone-booth design) across iOS, Android, and macOS.

Changes:
- iOS: Updated all 14 icon sizes in AppIcon.appiconset (20px to 1024px)
- Android: Updated launcher icons for all density buckets (mdpi to xxxhdpi)
- macOS: Updated Icon.icon bundle and regenerated Clawdbot.icns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-08 23:31:24 +01:00
Peter Steinberger cdfae0b9d3 fix(daemon): normalize audit paths 2026-01-08 22:28:51 +00:00
Peter Steinberger 3965c5b4d2 docs: naming sweep and platform wording 2026-01-08 23:25:51 +01:00
Peter Steinberger ed80860c34 chore(format): apply biome fixes 2026-01-08 22:22:17 +00:00
Peter Steinberger 1f215848be chore(test): align auth choice options formatting 2026-01-08 23:18:33 +01:00
Peter Steinberger dc1992cbb5 docs: note elevated no-op when unsandboxed 2026-01-08 23:18:33 +01:00
Peter Steinberger b01d7e39d5 fix(onboarding): preflight claude cli keychain 2026-01-08 23:18:33 +01:00
Peter Steinberger d38a8d7076 docs: clarify elevated sandbox behavior 2026-01-08 23:18:33 +01:00
Peter Steinberger 36fa3c3cd3 fix: improve ws close diagnostics 2026-01-08 22:18:07 +00:00
Peter Steinberger 1cf8503017 fix(daemon): audit runtime best practices 2026-01-08 22:18:07 +00:00
Jake 95015ddea6 docs: expand parameter descriptions for agent/wake hooks 2026-01-09 11:14:52 +13:00
Peter Steinberger cd2f3bd355 feat(status): add Telegram/WhatsApp troubleshooting warnings 2026-01-08 23:14:11 +01:00
Peter Steinberger a33271d374 refactor: share paren-aware chunk break scan 2026-01-08 23:09:34 +01:00
Peter Steinberger 7392387ec1 feat(status): warn on Discord message content intent 2026-01-08 23:07:34 +01:00
Peter Steinberger a6c309824e docs: refresh and simplify docs 2026-01-08 23:07:20 +01:00
Peter Steinberger 88dca1afdf docs: fix Mintlify parse error 2026-01-08 23:05:26 +01:00
Peter Steinberger b48ed56635 test: add DM elevated regression coverage 2026-01-08 22:58:15 +01:00
Peter Steinberger 014667e00b fix: tighten group elevated targeting 2026-01-08 22:57:18 +01:00
Peter Steinberger cda2025c49 fix: align env config schema 2026-01-08 22:57:18 +01:00
Peter Steinberger 59f89678b2 feat: allow inline env vars in config 2026-01-08 22:57:18 +01:00
Peter Steinberger 9a1267b530 fix(doctor): run legacy migrations in non-interactive 2026-01-08 21:37:22 +00:00
Peter Steinberger 5b46214379 refactor(cron): centralize telegram targets + delivery helpers 2026-01-08 21:36:53 +00:00
Peter Steinberger 5939dff092 fix: avoid synthesizing agent defaults 2026-01-08 22:26:29 +01:00
Luke ab28eb5109 Merge branch 'main' into commands-list-clean 2026-01-08 16:25:56 -05:00
Peter Steinberger 2c7d64232e feat: enable adaptive context pruning by default 2026-01-08 22:23:03 +01:00
Luke 5965708f1d Merge branch 'main' into commands-list-clean 2026-01-08 16:22:25 -05:00
Peter Steinberger d58cdf7c9f fix: normalize gateway cli option strings 2026-01-08 22:18:06 +01:00
Peter Steinberger 35ba99c245 fix: honor non-interactive legacy migrations 2026-01-08 22:13:57 +01:00
Peter Steinberger e75ca23e7d test(cron): cover telegram topic delivery (#478) (thanks @nachoiacovino) 2026-01-08 21:13:39 +00:00
Nacho Iacovino 5391f1141e fix(cron): parse :topic: suffix for Telegram topic delivery
When cron jobs specify a Telegram topic in the 'to' field like
'-1003420657307:topic:60', the delivery code now correctly parses
this into separate chatId and messageThreadId values.

Previously, the entire string was passed as chatId, causing messages
to fail delivery to forum topics.
2026-01-08 21:12:15 +00:00
Peter Steinberger 699ba410fe fix: resolve WhatsApp LID inbound mapping 2026-01-08 22:08:54 +01:00
Peter Steinberger da48a9907b docs: add beginner paths for docker and linux vps 2026-01-08 21:56:47 +01:00
Josh Palmer 53b81d07c6 🤖 codex: add changelog entry for #210 2026-01-08 21:54:04 +01:00
Josh Palmer 2fae8eda66 🤖 codex: strip empty assistant blocks from history (#210) 2026-01-08 21:53:33 +01:00
Peter Steinberger 884e734809 feat(doctor): audit config + state permissions 2026-01-08 21:51:34 +01:00
Peter Steinberger 13ddd40a59 docs: clarify sandboxing config option 2026-01-08 21:51:21 +01:00
Peter Steinberger d55750189e docs: add sandboxing page and cross-links 2026-01-08 21:49:26 +01:00
Peter Steinberger 2d4ec35e1c feat(doctor): add repair/force flows 2026-01-08 21:47:35 +01:00
Peter Steinberger 712a7dddf6 docs(cron): clarify jobId vs id 2026-01-08 21:43:22 +01:00
Peter Steinberger 10e46c6d5e docs(cron): rewrite cron jobs documentation 2026-01-08 21:39:03 +01:00
Peter Steinberger 0373574f0b refactor(telegram): centralize target parsing 2026-01-08 21:38:59 +01:00
Peter Steinberger 88039a69a2 style(cli): format daemon config warnings 2026-01-08 21:38:54 +01:00
Peter Steinberger ea748f9440 fix(doctor): handle missing service readCommand 2026-01-08 21:38:51 +01:00
Luke a574ae5415 Merge branch 'main' into commands-list-clean 2026-01-08 15:35:17 -05:00
Peter Steinberger 23e34dd598 docs: clarify doctor + launchd service guidance 2026-01-08 21:34:28 +01:00
Peter Steinberger 01641b34ea feat(doctor): audit supervisor config + docs 2026-01-08 21:28:48 +01:00
Peter Steinberger d0c4ce6749 docs(changelog): note PR #483 (thanks @AbhisekBasu1) 2026-01-08 20:26:18 +00:00
Peter Steinberger 58a9e35233 style(cron): format telegram target tests 2026-01-08 21:23:07 +01:00
Peter Steinberger 5fea53d89c fix(cron): parse telegram targets with prefixes (thanks @mitschabaude-bot) 2026-01-08 21:23:07 +01:00
Gregor's Bot 2afb75d508 fix(cron): support Telegram topic/thread ID in delivery target
When delivering cron job output to Telegram, the 'to' field now supports
specifying a topic (forum thread) ID in addition to the chat ID.

Supported formats:
- chatId (plain chat ID or @username)
- chatId:topicId (chat ID with numeric topic ID)
- chatId:topic:topicId (alternative format with explicit marker)

This enables cron jobs to deliver messages to specific forum topics
rather than always going to the main/general topic.

Adds parseTelegramTarget helper function with unit tests.

(cherry picked from commit 24a6595e81)
2026-01-08 21:23:07 +01:00
Tobias Bischoff 7cffa65376 Merge branch 'main' into main 2026-01-08 21:16:47 +01:00
Peter Steinberger f63496b389 Merge pull request #479 from azade-c/docs/systemd-service-comments
fix(daemon): align systemd unit with documentation
2026-01-08 20:16:32 +00:00
Peter Steinberger 9632ebed7b docs: note systemd unit alignment (#479) - thanks @azade-c 2026-01-08 21:16:19 +01:00
Azade 3a8d8bc6a4 fix(daemon): align systemd unit with documentation
Align generated systemd service file with docs:
https://docs.clawd.bot/gateway#supervision-systemd-user-unit

Adds:
- After=network-online.target
- Wants=network-online.target
- RestartSec=5
2026-01-08 21:16:19 +01:00
Tobias Bischoff 634f1389a4 Merge branch 'main' into main 2026-01-08 21:15:06 +01:00
Erik von Essen FisherandClaude Sonnet 4.5 7363bd1ad7 Rebrand app icons from Clawdis to Clawdbot
Replace app icons with new Clawdbot branding (lobster-in-phone-booth design) across iOS, Android, and macOS.

Changes:
- iOS: Updated all 14 icon sizes in AppIcon.appiconset (20px to 1024px)
- Android: Updated launcher icons for all density buckets (mdpi to xxxhdpi)
- macOS: Updated Icon.icon bundle and regenerated Clawdbot.icns

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-08 21:15:02 +01:00
Peter Steinberger 0c74ef25d6 refactor: normalize skills UI status keys 2026-01-08 20:13:24 +00:00
Peter Steinberger 5a2242ee92 docs(changelog): follow-up #516 (thanks @YuriNachos) 2026-01-08 21:08:34 +01:00
Peter Steinberger 31fb5867e8 refactor(outbound): centralize telegram account defaults 2026-01-08 21:05:01 +01:00
Peter Steinberger e90c00768f Merge pull request #445 from pkrmf/ui-skill-install-feedback
UI: improve skill install feedback
2026-01-08 20:04:32 +00:00
Peter Steinberger dc4c174f4e docs: add changelog for skill install feedback (PR #445, thanks @pkrmf) 2026-01-08 20:04:13 +00:00
Peter Steinberger 7905a27416 style: format gateway-cli log lines 2026-01-08 20:04:10 +00:00
Marc Terns 965615a46c UI: improve skill install feedback 2026-01-08 20:01:38 +00:00
Peter Steinberger 04e0e10411 fix: restore hidden gateway-daemon alias 2026-01-08 19:54:23 +00:00
Peter Steinberger 28e3e51335 Merge pull request #516 from YuriNachos/fix/heartbeat-telegram-account-id
fix(heartbeat): pass accountId for Telegram delivery
2026-01-08 19:48:44 +00:00
Peter Steinberger 5372c1ab54 Merge branch 'main' into fix/heartbeat-telegram-account-id 2026-01-08 20:48:29 +01:00
Peter Steinberger 871c9e5286 fix(heartbeat): telegram accountId + cron jobId compat (#516, thanks @YuriNachos) 2026-01-08 20:47:22 +01:00
Yurii ChukhlibandClaude 9d42972b8a fix(heartbeat): pass accountId for Telegram delivery
Heartbeat Telegram delivery was failing when the bot token was
configured only via telegram.botToken in config (without TELEGRAM_BOT_TOKEN
environment variable).

Root cause: deliverOutboundPayloads was called without accountId parameter,
so sendMessageTelegram couldn't determine which account to use and couldn't
find the token from config.

Fix: Resolve default Telegram accountId when provider is "telegram" and pass
it to deliverOutboundPayloads. This follows the same pattern used elsewhere in
the codebase (e.g., cron uses resolveTelegramToken).

Changes:
- Added import for resolveDefaultTelegramAccountId
- Added accountId resolution for telegram provider
- Updated deliverOutboundPayloads call to include accountId

Fixes #318

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 20:47:22 +01:00
Yurii ChukhlibandClaude 4dac298ae2 fix(cron): use jobId parameter instead of id for AI tool schema
Fixes parameter mismatch between AI tool schema and internal validation.
The TypeBox schema now uses `jobId` for update/remove/run/runs actions,
matching what users expect based on the returned job objects.

Changes:
- Changed parameter from `id` to `jobId` in TypeBox schema for update/remove/run/runs
- Updated execute function to read `jobId` parameter
- Updated tests to use `jobId` in input parameters

The gateway protocol still uses `id` internally - the tool now maps
`jobId` from the AI to `id` for the gateway call.

Fixes #185

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 20:47:22 +01:00
Yurii ChukhlibandClaude 2f9306b98a fix(heartbeat): pass accountId for Telegram delivery
Heartbeat Telegram delivery was failing when the bot token was
configured only via telegram.botToken in config (without TELEGRAM_BOT_TOKEN
environment variable).

Root cause: deliverOutboundPayloads was called without accountId parameter,
so sendMessageTelegram couldn't determine which account to use and couldn't
find the token from config.

Fix: Resolve default Telegram accountId when provider is "telegram" and pass
it to deliverOutboundPayloads. This follows the same pattern used elsewhere in
the codebase (e.g., cron uses resolveTelegramToken).

Changes:
- Added import for resolveDefaultTelegramAccountId
- Added accountId resolution for telegram provider
- Updated deliverOutboundPayloads call to include accountId

Fixes #318

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 20:40:00 +01:00
Peter Steinberger 28a0864de8 Merge pull request #499 from philipp-spiess/fix/paren-chunking
Avoid splitting outbound chunks inside parentheses
2026-01-08 19:39:52 +00:00
Peter Steinberger 4a918ccf47 fix: avoid splitting outbound chunks inside parentheses (#499) — thanks @philipp-spiess 2026-01-08 20:39:28 +01:00
Philipp Spiess 4082b90aa4 Chunking: avoid splits inside parentheses 2026-01-08 20:38:00 +01:00
Peter Steinberger 491f928a2e Merge pull request #473 from gupsammy/fix/macos-app-crash-bundle-resources
fix(macos): prevent crash from missing ClawdbotKit resources and Swift 6.2 library
2026-01-08 19:28:12 +00:00
Yurii ChukhlibandClaude 265ef6d0c6 fix(cron): use jobId parameter instead of id for AI tool schema
Fixes parameter mismatch between AI tool schema and internal validation.
The TypeBox schema now uses `jobId` for update/remove/run/runs actions,
matching what users expect based on the returned job objects.

Changes:
- Changed parameter from `id` to `jobId` in TypeBox schema for update/remove/run/runs
- Updated execute function to read `jobId` parameter
- Updated tests to use `jobId` in input parameters

The gateway protocol still uses `id` internally - the tool now maps
`jobId` from the AI to `id` for the gateway call.

Fixes #185

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 20:28:03 +01:00
Peter Steinberger 9052160af3 Merge pull request #512 from YuriNachos/fix/telegram-fetch-type-errors
fix(telegram): resolve fetch type errors with grammY Bot constructor
2026-01-08 19:27:20 +00:00
Peter Steinberger f1bc178141 fix: land macos resource bundle guard (#473) — thanks @gupsammy 2026-01-08 19:24:20 +00:00
gupsammyandClaude Opus 4.5 29e9a574b0 fix(macos): prevent crash from missing ClawdbotKit resources and Swift library
The macOS app was crashing in two scenarios:

1. Bundle.module crash (fixes #213): When the first tool event arrived,
   ToolDisplayRegistry tried to load config via ClawdbotKitResources.bundle,
   which used Bundle.module directly. In packaged apps, Bundle.module
   couldn't find the resource bundle at the expected path, causing a
   fatal assertion failure after ~40-80 minutes of runtime.

2. dyld crash (fixes #417): Swift 6.2 requires libswiftCompatibilitySpan.dylib
   but SwiftPM doesn't bundle it automatically, causing immediate crash on
   launch with "Library not loaded" error.

Changes:
- ClawdbotKitResources.swift: Replace direct Bundle.module access with a
  safe locator that checks multiple paths and falls back gracefully
- package-mac-app.sh: Copy ClawdbotKit_ClawdbotKit.bundle to Resources
- package-mac-app.sh: Copy libswiftCompatibilitySpan.dylib from Xcode
  toolchain to Frameworks

Tested on macOS 26.2 with Swift 6.2 - app launches and runs without crashes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 19:24:20 +00:00
Peter Steinberger ba19173b96 test: cover status model provider prefix 2026-01-08 20:23:38 +01:00
Peter Steinberger 393c414e90 refactor: split discord gateway helpers 2026-01-08 20:23:06 +01:00
Peter SteinbergerandYuri Chukhlib bf67b29a0e fix(telegram): resolve grammY fetch type mismatch (#512)
Co-authored-by: Yuri Chukhlib <yuri.v.chu@gmail.com>
2026-01-08 20:18:17 +01:00
Peter Steinberger 43e0462405 docs: add changelog for #506 2026-01-08 20:13:45 +01:00
Keith the Silly Goose 7866203c5c fix(status): include provider prefix in model display
The /status command was showing 'anthropic/claude-sonnet-4-5' even when
using 'google-antigravity/claude-sonnet-4-5' because buildStatusMessage
received only the model name without the provider prefix.

When resolveConfiguredModelRef parsed a model string without a slash,
it fell back to DEFAULT_PROVIDER ('anthropic'), causing the misleading
display.

Fix: Pass the full 'provider/model' string to buildStatusMessage so
the provider is correctly extracted and displayed.

🪿 Investigated and submitted by Keith the Silly Goose
2026-01-08 20:11:37 +01:00
Yuri Chukhlib d1ccdeb5f5 Merge branch 'main' into fix/telegram-fetch-type-errors 2026-01-08 20:09:00 +01:00
Peter Steinberger c54f2a122a fix: update changelog + prompt test 2026-01-08 20:08:27 +01:00
Josh Palmer b7c900739e 🤖 codex: handle discord gateway error events (#504) 2026-01-08 20:06:02 +01:00
Yurii Chukhlib d05de07fdd fix(telegram): resolve fetch type errors with grammY Bot constructor
Add proper type assertion for ApiClientOptions["fetch"] to resolve
TypeScript compilation errors when passing fetch implementation to
grammY's Bot constructor. This follows the same pattern already used
in bot.ts.

Fixes #465
2026-01-08 19:31:06 +01:00
Josh Palmer cc94db458c 🤖 codex: fix block reply ordering (#503)
What: serialize block reply sends, make typing non-blocking, add timeout fallback + abort-aware routing, and add regression tests.
Why: prevent out-of-order streamed blocks while keeping final fallback on timeouts.
Tests: ./node_modules/.bin/vitest run src/auto-reply/reply.block-streaming.test.ts src/auto-reply/reply/route-reply.test.ts
Tests: corepack pnpm lint && corepack pnpm build (pass). corepack pnpm test (ran locally; failure observed during run).

Co-authored-by: Josh Palmer <joshp123@users.noreply.github.com>
2026-01-08 19:30:24 +01:00
Gregor's Bot 7450aed663 fix: rename ClaudeBot to Clawdbot in system prompt (#502) 2026-01-08 11:24:00 -06:00
Philipp Spiess 5fe39307cd Chunking: avoid splits inside parentheses 2026-01-08 16:52:50 +01:00
Luke K (pr-0f3t) 713eb898f6 Removed compact command changes from elsewhere 2026-01-08 10:41:09 -05:00
Luke 8445c9a5e8 Removing stray commits 2026-01-08 10:33:11 -05:00
LK 559e175b38 feat(commands): add /commands slash list 2026-01-08 16:23:36 +01:00
Tobias Bischoff 3149d6d331 Telegram: cast fetch to grammy client type 2026-01-08 15:16:53 +01:00
Tobias Bischoff ecd4c9c4f5 Onboarding: add MiniMax hosted API key option 2026-01-08 15:10:18 +01:00
Peter Steinberger c20a12aa7b fix: format onboarding hint 2026-01-08 12:25:39 +01:00
Azade 1f7dbb6a48 fix(daemon): align systemd unit with documentation
Align generated systemd service file with docs:
https://docs.clawd.bot/gateway#supervision-systemd-user-unit

Adds:
- After=network-online.target
- Wants=network-online.target
- RestartSec=5
2026-01-08 11:24:47 +00:00
Peter Steinberger 4c5440ccab chore: bump version to 2026.1.8-2 2026-01-08 12:23:01 +01:00
Peter Steinberger a564558a67 chore: drop bun from daemon runtime options 2026-01-08 12:20:45 +01:00
Peter Steinberger 51c2d29693 feat: auto-install gateway in quickstart 2026-01-08 12:19:45 +01:00
Peter Steinberger 8ed412db22 feat: streamline quickstart provider selection 2026-01-08 12:19:16 +01:00
Peter Steinberger e210069dbd fix: soften codex oauth expiry hint 2026-01-08 12:16:09 +01:00
Peter Steinberger 4dde870121 docs: split onboarding mode hints 2026-01-08 12:14:36 +01:00
Peter Steinberger 959a19d8ae fix: clean lint issues 2026-01-08 11:07:11 +00:00
Peter Steinberger 63bad210c9 docs: tighten onboarding quickstart hint 2026-01-08 12:06:45 +01:00
Peter Steinberger 4c09acb2a9 feat: add telegram topic delivery for cron 2026-01-08 11:05:28 +00:00
Peter Steinberger 874c7465a5 fix: surface control ui config in daemon status 2026-01-08 11:05:28 +00:00
Peter Steinberger e7b3cc3d3d fix: add provider account metadata 2026-01-08 11:05:28 +00:00
Peter Steinberger fd04bdb9cf chore: tighten self-update guidance 2026-01-08 12:04:29 +01:00
Peter Steinberger 63ff5819b1 fix: retry telegram poll conflicts 2026-01-08 12:04:21 +01:00
Peter Steinberger 03605bfa6a chore: refine control ui links and composer 2026-01-08 12:04:11 +01:00
Peter Steinberger d42444928b chore: add provider logs command 2026-01-08 12:01:06 +01:00
Peter Steinberger a483e58860 feat: add quickstart onboarding defaults 2026-01-08 11:54:40 +01:00
Peter Steinberger f24a4626e3 fix(config): reject shared agentDir 2026-01-08 11:54:26 +01:00
Peter Steinberger aa34d7d5f2 chore: sync pnpm lockfile 2026-01-08 10:46:12 +00:00
Peter Steinberger af20abf78d fix(ui): start logs at bottom 2026-01-08 11:42:28 +01:00
Peter Steinberger 390b102563 chore: show dm policy + allowlist preview 2026-01-08 11:25:17 +01:00
gupsammyandClaude Opus 4.5 908fb94f0b fix(macos): prevent crash from missing ClawdbotKit resources and Swift library
The macOS app was crashing in two scenarios:

1. Bundle.module crash (fixes #213): When the first tool event arrived,
   ToolDisplayRegistry tried to load config via ClawdbotKitResources.bundle,
   which used Bundle.module directly. In packaged apps, Bundle.module
   couldn't find the resource bundle at the expected path, causing a
   fatal assertion failure after ~40-80 minutes of runtime.

2. dyld crash (fixes #417): Swift 6.2 requires libswiftCompatibilitySpan.dylib
   but SwiftPM doesn't bundle it automatically, causing immediate crash on
   launch with "Library not loaded" error.

Changes:
- ClawdbotKitResources.swift: Replace direct Bundle.module access with a
  safe locator that checks multiple paths and falls back gracefully
- package-mac-app.sh: Copy ClawdbotKit_ClawdbotKit.bundle to Resources
- package-mac-app.sh: Copy libswiftCompatibilitySpan.dylib from Xcode
  toolchain to Frameworks

Tested on macOS 26.2 with Swift 6.2 - app launches and runs without crashes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 15:51:07 +05:30
Peter Steinberger 8803787e48 chore: fallback providers status when gateway down 2026-01-08 11:05:03 +01:00
Peter Steinberger a851444a1d chore: expand provider details in status 2026-01-08 11:02:13 +01:00
Peter Steinberger e8698fb9ef chore: show dashboard url in status 2026-01-08 10:51:55 +01:00
Peter Steinberger 840c64084e docs: refresh daemon troubleshooting guidance 2026-01-08 10:42:23 +01:00
Peter Steinberger af2accf5e3 test: force bun fetch path in telegram send 2026-01-08 10:37:29 +01:00
Peter Steinberger fa501e7730 fix: import path in doctor 2026-01-08 10:36:20 +01:00
Peter Steinberger dab3b3442f chore: bump version to 2026.1.8-1 2026-01-08 10:35:35 +01:00
Peter Steinberger c410e2fa47 docs: expand Telegram setup notes 2026-01-08 09:34:05 +00:00
Peter Steinberger 2567281846 docs: document hook model overrides 2026-01-08 09:34:05 +00:00
Peter Steinberger 73988506fe feat: allow hook model overrides 2026-01-08 09:33:42 +00:00
Peter Steinberger e6f8e1e531 fix(telegram): restrict native fetch to bun 2026-01-08 10:31:16 +01:00
Peter Steinberger 4019fafc2c chore: add spacing to status output 2026-01-08 10:30:57 +01:00
Peter Steinberger 21ef1761c6 chore: prompt to install or start gateway 2026-01-08 10:29:48 +01:00
Peter Steinberger f6b1dc452a chore: surface daemon status help 2026-01-08 10:29:44 +01:00
Peter Steinberger a54442866a chore: refine daemon runtime hint 2026-01-08 10:29:40 +01:00
Peter Steinberger b80b3a38bf chore: clarify telegram docs links 2026-01-08 10:29:36 +01:00
Peter Steinberger 1232eb6f5e docs: prefer npm for global install 2026-01-08 10:07:13 +01:00
Peter Steinberger 712cfa94aa docs: recommend global install and daemon onboarding 2026-01-08 09:59:10 +01:00
Peter Steinberger bfd3eb46c7 docs: drop legacy telegram pairing mention 2026-01-08 09:57:03 +01:00
Peter Steinberger bce3cc992f fix: group /model list output 2026-01-08 09:44:59 +01:00
Peter Steinberger 6c9dd731a9 docs: add makeup alias 2026-01-08 09:39:20 +01:00
Peter Steinberger 0b611fc354 docs: add OAuth deep dive 2026-01-08 09:29:29 +01:00
Marc Terns 871e888283 UI: improve skill install feedback 2026-01-07 21:21:32 -06:00
743 changed files with 59347 additions and 12389 deletions

No files matched your search

+64 -3
View File
@@ -6,7 +6,7 @@ on:
jobs:
checks:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
@@ -91,7 +91,7 @@ jobs:
run: ${{ matrix.command }}
checks-windows:
runs-on: windows-latest
runs-on: blacksmith-4vcpu-windows-2025
defaults:
run:
shell: bash
@@ -169,6 +169,67 @@ jobs:
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
run: ${{ matrix.command }}
checks-macos:
if: github.event_name == 'pull_request'
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
include:
- task: test
command: pnpm test
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: false
- name: Checkout submodules (retry)
run: |
set -euo pipefail
git submodule sync --recursive
for attempt in 1 2 3 4 5; do
if git -c protocol.version=2 submodule update --init --force --depth=1 --recursive; then
exit 0
fi
echo "Submodule update failed (attempt $attempt/5). Retrying…"
sleep $((attempt * 10))
done
exit 1
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 24
check-latest: true
- name: Runtime versions
run: |
node -v
npm -v
- name: Capture node path
run: echo "NODE_BIN=$(dirname \"$(node -p \"process.execPath\")\")" >> "$GITHUB_ENV"
- name: Enable corepack and pin pnpm
run: |
corepack enable
corepack prepare pnpm@10.23.0 --activate
pnpm -v
- name: Install dependencies
env:
CI: true
run: |
export PATH="$NODE_BIN:$PATH"
which node
node -v
pnpm -v
pnpm install --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true || pnpm install --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true
- name: Run ${{ matrix.task }}
run: ${{ matrix.command }}
macos-app:
if: github.event_name == 'pull_request'
runs-on: macos-latest
@@ -412,7 +473,7 @@ jobs:
PY
android:
runs-on: ubuntu-latest
runs-on: blacksmith-4vcpu-ubuntu-2404
strategy:
fail-fast: false
matrix:
+37
View File
@@ -0,0 +1,37 @@
name: Workflow Sanity
on:
pull_request:
push:
jobs:
no-tabs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fail on tabs in workflow files
run: |
python - <<'PY'
from __future__ import annotations
import pathlib
import sys
root = pathlib.Path(".github/workflows")
bad: list[str] = []
for path in sorted(root.rglob("*.yml")):
if b"\t" in path.read_bytes():
bad.append(str(path))
for path in sorted(root.rglob("*.yaml")):
if b"\t" in path.read_bytes():
bad.append(str(path))
if bad:
print("Tabs found in workflow file(s):")
for path in bad:
print(f"- {path}")
sys.exit(1)
PY
+4
View File
@@ -50,3 +50,7 @@ apps/ios/*.dSYM.zip
# provisioning profiles (local)
apps/ios/*.mobileprovision
.env
# Local untracked files
.local/
.vscode/
+1 -1
View File
@@ -48,4 +48,4 @@
--allman false
# Exclusions
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,apps/macos/Sources/ClawdisProtocol,apps/macos/Sources/ClawdbotProtocol
--exclude .build,.swiftpm,DerivedData,node_modules,dist,coverage,xcuserdata,Peekaboo,Swabble,apps/android,apps/ios,apps/shared,apps/macos/Sources/ClawdisProtocol,apps/macos/Sources/ClawdbotProtocol
+2
View File
@@ -17,6 +17,8 @@ excluded:
- dist
- coverage
- "*.playground"
# Generated (protocol-gen-swift.ts)
- apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift
analyzer_rules:
- unused_declaration
+13 -5
View File
@@ -28,6 +28,7 @@
- Add brief code comments for tricky or non-obvious logic.
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
- Naming: use **Clawdbot** for product/app/docs headings; use `clawdbot` for CLI command, package/binary, paths, and config keys.
## Testing Guidelines
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
@@ -62,6 +63,9 @@
- Rebrand/migration issues (Clawdis → Clawdbot) or legacy config/service warnings: run `clawdbot doctor` (see `docs/gateway/doctor.md`).
## Agent-Specific Notes
- Vocabulary: "makeup" = "mac app".
- When answering questions, respond with high-confidence answers only: verify in code; do not guess.
- Never update the Carbon dependency.
- Gateway currently runs only as the menubar app; there is no separate LaunchAgent/helper label installed. Restart via the Clawdbot Mac app or `scripts/restart-mac.sh`; to verify/kill use `launchctl print gui/$UID | grep clawdbot` rather than assuming a fixed label. **When debugging on macOS, start/stop the gateway via the app, not ad-hoc tmux sessions; kill any temporary tunnels before handoff.**
- macOS logs: use `./scripts/clawlog.sh` (aka `vtlog`) to query unified logs for the Clawdbot subsystem; it supports follow/tail/category filters and expects passwordless sudo for `/usr/bin/log`.
- If shared guardrails are available locally, review them; otherwise follow this repo's guidance.
@@ -80,24 +84,28 @@
- **Multi-agent safety:** do **not** switch branches / check out a different branch unless explicitly requested.
- **Multi-agent safety:** running multiple agents is OK as long as each agent has its own session.
- **Multi-agent safety:** when you see unrecognized files, keep going; focus on your changes and commit only those.
- When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
- Lobster seam: use the shared CLI palette in `src/terminal/palette.ts` (no hardcoded colors); apply palette to onboarding/config prompts and other TTY UI output as needed.
- **Multi-agent safety:** focus reports on your edits; avoid guard-rail disclaimers unless truly blocked; when multiple agents touch the same file, continue if safe; end with a brief “other files present” note only if relevant.
- Bug investigations: read source code of relevant npm dependencies and all related local code before concluding; aim for high-confidence root cause.
- Code style: add brief comments for tricky logic; keep files under ~500 LOC when feasible (split/refactor as needed).
- When asked to open a “session” file, open the Pi session logs under `~/.clawdbot/agents/main/sessions/*.jsonl` (newest unless a specific ID is given), not the default `sessions.json`. If logs are needed from another machine, SSH via Tailscale and read the same path there.
- Menubar dimming + restart flow mirrors Trimmy: use `scripts/restart-mac.sh` (kills all Clawdbot variants, runs `swift build`, packages, relaunches). Icon dimming depends on MenuBarExtraAccess wiring in AppMain; keep `appearsDisabled` updates intact when touching the status item.
- Do not rebuild the macOS app over SSH; rebuilds must be run directly on the Mac.
- Never send streaming/partial replies to external messaging surfaces (WhatsApp, Telegram); only final replies should be delivered there. Streaming/tool events may still go to internal UIs/control channel.
- Voice wake forwarding tips:
- Command template should stay `clawdbot-mac agent --message "${text}" --thinking low`; `VoiceWakeForwarder` already shell-escapes `${text}`. Don’t add extra quotes.
- launchd PATH is minimal; ensure the app’s launch agent PATH includes standard system paths plus your pnpm bin (typically `$HOME/Library/pnpm`) so `pnpm`/`clawdbot` binaries resolve when invoked via `clawdbot-mac`.
- For manual `clawdbot send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
- For manual `clawdbot message send` messages that include `!`, use the heredoc pattern noted below to avoid the Bash tool’s escaping.
## Exclamation Mark Escaping Workaround
The Claude Code Bash tool escapes `!` to `\\!` in command arguments. When using `clawdbot send` with messages containing exclamation marks, use heredoc syntax:
The Claude Code Bash tool escapes `!` to `\\!` in command arguments. When using `clawdbot message send` with messages containing exclamation marks, use heredoc syntax:
```bash
# WRONG - will send "Hello\\!" with backslash
clawdbot send --to "+1234" --message 'Hello!'
clawdbot message send --to "+1234" --message 'Hello!'
# CORRECT - use heredoc to avoid escaping
clawdbot send --to "+1234" --message "$(cat <<'EOF'
clawdbot message send --to "+1234" --message "$(cat <<'EOF'
Hello!
EOF
)"
+179 -2
View File
@@ -2,7 +2,184 @@
## Unreleased
- (nothing yet)
- Models/Auth: add OpenCode Zen (multi-model proxy) onboarding. (#623) — thanks @magimetal
- WhatsApp: refactor vCard parsing helper and improve empty contact card summaries. (#624) — thanks @steipete
- WhatsApp: include phone numbers when multiple contacts are shared. (#625) — thanks @mahmoudashraf93
- Agents: warn on small context windows (<32k) and block unusable ones (<16k). — thanks @steipete
- Pairing: cap pending DM pairing requests at 3 per provider and avoid pairing replies for outbound DMs. — thanks @steipete
- macOS: replace relay smoke test with version check in packaging script. (#615) — thanks @YuriNachos
- macOS: avoid clearing Launch at Login during app initialization. (#607) — thanks @wes-davis
- Onboarding: skip systemd checks/daemon installs when systemd user services are unavailable; add onboarding flags to skip flow steps and stabilize Docker E2E. (#573) — thanks @steipete
- Onboarding: QuickStart provider picker uses single-select to avoid accidental Telegram token prompts when choosing WhatsApp. (#485) — thanks @frankstallone
- macOS: add node bridge heartbeat pings to detect half-open sockets and reconnect cleanly. (#572) — thanks @ngutman
- Node bridge: harden keepalive + heartbeat handling (TCP keepalive, better disconnects, and keepalive config tests). (#577) — thanks @steipete
- Control UI: improve mobile responsiveness. (#558) — thanks @carlulsoe
- Control UI: persist per-session verbose off and hide tool cards unless verbose is on. (#262) — thanks @steipete
- Gateway: centralize verbose overrides and gate tool stream events at the server. (#262) — thanks @steipete
- CLI: add `sandbox list` and `sandbox recreate` commands for managing Docker sandbox containers after image/config updates. (#563) — thanks @pasogott
- Sandbox: allow `session_status` tool in sandboxed sessions by default. — thanks @steipete
- CLI: add `clawdbot config --section <name>` to jump straight into a wizard section (repeatable).
- Docs: add Hetzner Docker VPS guide. (#556) — thanks @Iamadig
- Docs: link Hetzner guide from install + platforms docs. (#592) — thanks @steipete
- Providers: add Microsoft Teams provider with polling, attachments, and CLI send support. (#404) — thanks @onutc
- Slack: honor reply tags + replyToMode while keeping threaded replies in-thread. (#574) — thanks @bolismauro
- Slack: configurable reply threading (`slack.replyToMode`) + proper mrkdwn formatting for outbound messages. (#464) — thanks @austinm911
- Discord: avoid category parent overrides for channel allowlists and refactor thread context helpers. (#588) — thanks @steipete
- Discord: fix forum thread starters and cache channel lookups for thread context. (#585) — thanks @thewilloftheshadow
- Discord: log gateway disconnect/reconnect events at info and add verbose gateway metrics. (#595) — thanks @steipete
- Commands: accept /models as an alias for /model.
- Commands: add `/usage` as an alias for `/status`. (#492) — thanks @lc0rp
- Models/Auth: add MiniMax Anthropic-compatible API onboarding (minimax-api). (#590) — thanks @mneves75
- Models: centralize model override validation + hooks Gmail warnings in doctor. (#602) — thanks @steipete
- Agents: avoid base-to-string error stringification in model fallback. (#604) — thanks @steipete
- Agents: `sessions_spawn` inherits the requester's provider for child runs (avoid WhatsApp fallback). (#528) — thanks @rlmestre
- Agents: sub-agent context now injects only AGENTS.md + TOOLS.md (omits identity/user/soul/heartbeat/bootstrap). — thanks @steipete
- Gateway/CLI: harden agent provider routing + validation (Slack/MS Teams + aliases). (follow-up #528) — thanks @steipete
- Agents: treat billing/insufficient-credits errors as failover-worthy so model fallbacks kick in. (#486) — thanks @steipete
- Auth: default billing disable backoff to 5h (doubling, 24h cap) and surface disabled/cooldown profiles in `models list` + doctor. (#486) — thanks @steipete
- Commands: harden slash command registry and list text-only commands in `/commands`.
- Models/Auth: show per-agent auth candidates in `/model status`, and add `clawdbot models auth order {get,set,clear}` (per-agent auth rotation overrides). — thanks @steipete
- Telegram: keep streamMode draft-only; avoid forcing block streaming. (#619) — thanks @rubyrunsstuff
- Debugging: add raw model stream logging flags and document gateway watch mode.
- Gateway: decode dns-sd escaped UTF-8 in discovery output and show scan progress immediately. — thanks @steipete
- Agent: add claude-cli/opus-4.5 runner via Claude CLI with resume support (tools disabled).
- CLI: move `clawdbot message` to subcommands (`message send|poll|…`), fold Discord/Slack/Telegram/WhatsApp tools into `message`, and require `--provider` unless only one provider is configured.
- CLI: improve `logs` output (pretty/plain/JSONL), add gateway unreachable hint, and document logging.
- Hooks: default hook agent delivery to true. (#533) — thanks @mcinteerj
- Hooks: normalize hook agent providers (aliases + msteams support).
- WhatsApp: route queued replies to the original sender instead of the bot's own number. (#534) — thanks @mcinteerj
- WhatsApp: improve "no active web listener" errors (include account + relink hint). (#612) — thanks @YuriNachos
- WhatsApp: add broadcast groups for multi-agent replies. (#547) — thanks @pasogott
- WhatsApp: resolve @lid inbound senders via auth-dir mapping fallback + shared resolver. (#365)
- WhatsApp: treat shared contact cards as inbound messages (prefer vCard FN). (#622) — thanks @mahmoudashraf93
- iMessage: isolate group-ish threads by chat_id. (#535) — thanks @mdahmann
- Models: add OAuth expiry checks in doctor, expanded `models status` auth output (missing auth + `--check` exit codes). (#538) — thanks @latitudeki5223
- Deps: bump Pi to 0.40.0 and drop pi-ai patch (upstream 429 fix). (#543) — thanks @mcinteerj
- Agent: skip empty error assistant messages when rebuilding session context to avoid tool-chain corruption. (#561) — thanks @mukhtharcm
- Security: per-agent mention patterns and group elevated directives now require explicit mention to avoid cross-agent toggles.
- Config: support inline env vars in config (`env.*` / `env.vars`) and document env precedence.
- Config: write `clawdbot.json` atomically (temp file + replace) and keep a best-effort `.bak` backup.
- Agent: enable adaptive context pruning by default for tool-result trimming.
- Agent: drop empty error assistant messages when sanitizing session history. (#591) — thanks @steipete
- Agent: inject eligible skills list into the system prompt so bundled skills load from their actual locations. (#551) — thanks @gabriel-trigo
- Doctor: check config/state permissions and offer to tighten them. — thanks @steipete
- Doctor/Daemon: audit supervisor configs, add --repair/--force flows, surface service config audits in daemon status, and document user vs system services. — thanks @steipete
- Doctor: repair gateway service entrypoint when switching between npm and git installs; add Docker e2e coverage. — thanks @steipete
- Daemon: align generated systemd unit with docs for network-online + restart delay. (#479) — thanks @azade-c
- Daemon: add KillMode=process to systemd units to avoid podman restart hangs. (#541) — thanks @ogulcancelik
- WhatsApp: make inbound media size cap configurable (default 50 MB). (#505) — thanks @koala73
- Doctor: run legacy state migrations in non-interactive mode without prompts.
- Cron: parse Telegram topic targets for isolated delivery. (#478) — thanks @nachoiacovino
- Cron: enqueue main-session system events under the resolved main session key. (#510)
- Mobile: centralize main session key normalization for iOS/Android runtime helpers. — thanks @steipete
- Chat UI: stop pinning hardcoded `main` session in the recent list; prefer active session if missing. — thanks @steipete
- Outbound: default Telegram account selection for config-only tokens; remove heartbeat-specific accountId handling. (follow-up #516) — thanks @YuriNachos
- Cron: allow Telegram delivery targets with topic/thread IDs (e.g. `-100…:topic:123`). (#474) — thanks @mitschabaude-bot
- Heartbeat: resolve Telegram account IDs from config-only tokens; cron tool accepts canonical `jobId` and legacy `id` for job actions. (#516) — thanks @YuriNachos
- Discord: stop provider when gateway reconnects are exhausted and surface errors. (#514) — thanks @joshp123
- Agents: strip empty assistant text blocks from session history to avoid Claude API 400s. (#210)
- Agents: scrub unsupported JSON Schema keywords from tool schemas for Cloud Code Assist API compatibility. (#567) — thanks @erikpr1994
- Agents: sanitize Cloud Code Assist tool call IDs and detect format/quota errors for failover. (#544) — thanks @jeffersonwarrior
- Agents: simplify session tool schemas for Gemini compatibility. (#599) — thanks @mcinteerj
- Agents: require `raw` for gateway `config.apply` tool calls while keeping schema 2020-12 compatible. (#566) — thanks @sircrumpet
- Agents: add `session_status` agent tool for `/status`-equivalent status (incl. usage/cost) + per-session model overrides. — thanks @steipete
- Auto-reply: preserve block reply ordering with timeout fallback for streaming. (#503) — thanks @joshp123
- Auto-reply: block reply ordering fix (duplicate PR superseded by #503). (#483) — thanks @AbhisekBasu1
- Auto-reply: avoid splitting outbound chunks inside parentheses. (#499) — thanks @philipp-spiess
- Auto-reply: preserve spacing when stripping inline directives. (#539) — thanks @joshp123
- Auto-reply: relax reply tag parsing to allow whitespace. (#560) — thanks @mcinteerj
- Auto-reply: add per-provider block streaming toggles and coalesce streamed blocks to reduce line spam. (#536) — thanks @mcinteerj
- Auto-reply: suppress `<think>` leakage in block streaming and emit `/reasoning` as a separate `Reasoning:` message. (#614) — thanks @zknicker
- Auto-reply: default block streaming off for non-Telegram providers unless explicitly enabled, and avoid splitting on forced flushes below max.
- Auto-reply: raise default coalesce minChars for Signal/Slack/Discord and clarify streaming vs draft streaming in docs.
- Auto-reply: default block streaming coalesce idle to 1s to reduce tiny chunks. — thanks @steipete
- Auto-reply: fix /status usage summary filtering for the active provider.
- Auto-reply: deduplicate followup queue entries using message id/routing to avoid duplicate replies. (#600) — thanks @samratjha96
- Status: show provider prefix in /status model display. (#506) — thanks @mcinteerj
- Status: compact /status with session token usage + estimated cost, add `/cost` per-response usage lines (tokens-only for OAuth).
- Status: show active auth profile and key snippet in /status.
- Status: show provider usage windows when auth uses token-based OAuth (e.g. Claude setup-token).
- Agent: promote `<think>`/`<thinking>` tag reasoning into structured thinking blocks so `/reasoning` works consistently for OpenAI-compat providers.
- macOS: package ClawdbotKit resources and Swift 6.2 compatibility dylib to avoid launch/tool crashes. (#473) — thanks @gupsammy
- WhatsApp: group `/model list` output by provider for scannability. (#456) - thanks @mcinteerj
- Hooks: allow per-hook model overrides for webhook/Gmail runs (e.g. GPT 5 Mini).
- Control UI: logs tab opens at the newest entries (bottom).
- Control UI: default to relative paths for control UI assets. (#569) — thanks @bjesuiter
- Control UI: add Docs link, remove chat composer divider, and add New session button.
- Control UI: link sessions list to chat view. (#471) — thanks @HazAT
- Sessions: support session `label` in store/list/UI and allow `sessions_send` lookup by label. (#570) — thanks @azade-c
- Sessions: clarify `sessions_send` delivery semantics, log announce failures, and enforce Discord request timeouts. (#507) — thanks @steipete
- Control UI: show/patch per-session reasoning level and render extracted reasoning in chat.
- Control UI: queue outgoing chat messages, add Enter-to-send, and show queued items. (#527) — thanks @YuriNachos
- Control UI: refactor chat layout with tool sidebar, grouped messages, and nav improvements. (#475) — thanks @rahthakor
- Control UI: drop explicit `ui:install` step; `ui:build` now auto-installs UI deps (docs + update flow).
- Telegram: retry long-polling conflicts with backoff to avoid fatal exits.
- Telegram: fix grammY fetch type mismatch when injecting `fetch`. (#512) — thanks @YuriNachos
- Telegram: add inline keyboard buttons (capability-gated) and route callback query payloads as messages. (#491) — thanks @azade-c
- WhatsApp: resolve @lid JIDs via Baileys mapping to unblock inbound messages. (#415)
- Pairing: replies now include sender ids for Discord/Slack/Signal/iMessage/WhatsApp; pairing list labels them explicitly.
- Messages: default inbound/outbound prefixes from the routed agent’s `identity.name` when set. (#578) — thanks @p6l-richard
- Signal: accept UUID-only senders for pairing/allowlists/routing when sourceNumber is missing. (#523) — thanks @neist
- Signal: ignore reaction-only messages so they don't surface as unknown media. (#616) — thanks @neist
- Signal: add reaction notifications with allowlist support. — thanks @steipete
- Agent system prompt: avoid automatic self-updates unless explicitly requested.
- Onboarding: tighten QuickStart hint copy for configuring later.
- Onboarding: set Gemini 3 Pro as the default model for Gemini API key auth. (#489) — thanks @jonasjancarik
- Onboarding: avoid “token expired” for Codex CLI when expiry is heuristic.
- Onboarding: QuickStart jumps straight into provider selection with Telegram preselected when unset.
- Onboarding: QuickStart auto-installs the Gateway daemon with Node (no runtime picker).
- Onboarding: clarify WhatsApp owner number prompt and label pairing phone number.
- Auto-reply: normalize routed replies to drop NO_REPLY and apply response prefixes.
- Commands: add /debug for runtime config overrides (memory-only).
- Daemon runtime: remove Bun from selection options.
- CLI: restore hidden `gateway-daemon` alias for legacy launchd configs.
- Onboarding/Configure: add OpenAI API key flow that stores in shared `~/.clawdbot/.env` for launchd; simplify Anthropic token prompt order.
- Configure/Onboarding: show Control UI docs with gateway reachability status and only offer to open when a gateway is detected; default model prompt now prefers Opus 4.5 for Anthropic auth.
- Control UI: show skill install progress + per-skill results, hide install once binaries present. (#445) — thanks @pkrmf
- Providers/Doctor: surface Discord privileged intent (Message Content) misconfiguration with actionable warnings.
- Providers/Doctor: warn when Telegram config expects unmentioned group messages but Bot API privacy mode is likely enabled; surface WhatsApp login/disconnect hints.
- Providers/Doctor: add last inbound/outbound activity timestamps in `providers status` and extend `--probe` with Discord channel permission + Telegram group membership audits.
- Docs: add provider troubleshooting index (`/providers/troubleshooting`) and link it from the main troubleshooting guide.
- Docs: clarify model allowlist errors and add safety notes for verbose/reasoning in groups.
- Docs: add Ansible installation guide. (#545) — thanks @pasogott
- Telegram: include the user id in DM pairing messages and label it clearly in `clawdbot pairing list --provider telegram`.
- Apps: refresh iOS/Android/macOS app icons for Clawdbot branding. (#521) — thanks @fishfisher
- Docs: expand parameter descriptions for agent/wake hooks. (#532) — thanks @mcinteerj
- Docs: add community showcase entries from Discord. (#476) — thanks @gupsammy
- TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne
- TUI: stop overriding agent timeout so config defaults apply; warn on invalid `--timeout-ms`. (#549)
- Status: show Verbose/Elevated only when enabled.
- Status: filter usage summary to the active model provider.
- Status: map model providers to usage sources so unrelated usage doesn’t appear.
- Status: fix Claude usage snapshots when `anthropic:default` is a setup-token lacking `user:profile` by preferring `anthropic:claude-cli`; optional claude.ai fallback via `CLAUDE_AI_SESSION_KEY` / `CLAUDE_WEB_COOKIE`.
- Commands: allow /elevated off in groups without a mention; keep /elevated on mention-gated.
- Commands: keep multi-directive messages from clearing directive handling.
- Commands: warn when /elevated runs in direct (unsandboxed) runtime.
- Commands: treat mention-bypassed group command messages as mentioned so elevated directives respond.
- Commands: return /status in directive-only multi-line messages.
- Models: fall back to configured models when the provider catalog is unavailable.
- Agent system prompt: add messaging guidance for reply routing and cross-session sends. (#526) — thanks @neist
- Agent: bypass Anthropic OAuth tool-name blocks by capitalizing built-ins and keeping pruning tool matching case-insensitive. (#553) — thanks @andrewting19
- Commands/Tools: disable /restart and gateway restart tool by default (enable with commands.restart=true).
- Gateway/CLI: add `clawdbot gateway discover` (Bonjour scan on `local.` + `clawdbot.internal.`) with `--timeout` and `--json`. — thanks @steipete
- Gateway/CLI: make `clawdbot gateway status` human-readable by default, add `--json`, and probe localhost + configured remote (warn on multiple gateways). — thanks @steipete
- Gateway/CLI: support remote loopback gateways via SSH tunnel in `clawdbot gateway status` (`--ssh` / `--ssh-auto`). — thanks @steipete
- Gateway/Discovery: include `gatewayPort`/`sshPort`/`cliPath` in wide-area Bonjour records, and add a tailnet DNS fallback for `gateway discover` when split DNS isn’t configured. — thanks @steipete
- CLI: add global `--no-color` (and respect `NO_COLOR=1`) to disable ANSI output. — thanks @steipete
- CLI: centralize lobster palette + apply it to onboarding/config prompts. — thanks @steipete
- Gateway/CLI: add `clawdbot gateway --dev/--reset` to auto-create a dev config/workspace with a robot identity (no BOOTSTRAP.md), and reset wipes config/creds/sessions/workspace (subcommand --dev no longer collides with global --dev profile). — thanks @steipete
- Configure: stop prompting to open the Control UI (still shown in onboarding). — thanks @steipete
- Configure: add wizard mode to remove a provider config block. — thanks @steipete
- Onboarding/TUI: prompt to start TUI (best option) when BOOTSTRAP.md exists and add `tui --message` to auto-send the first prompt. — thanks @steipete
- Telegram: suppress grammY getUpdates stack traces; log concise retry message instead. — thanks @steipete
- Gateway/CLI: allow dev profile (`clawdbot --dev`) to auto-create the dev config + workspace. — thanks @steipete
- Dev templates: ship C-3PO dev workspace defaults as docs templates and use them for dev bootstrap. — thanks @steipete
- Config: fix Minimax hosted onboarding to write `agents.defaults` and allow `msteams` as a heartbeat target. — thanks @steipete
- Discord: add channel/category management actions (create/edit/move/delete + category removal). (#487) - thanks @NicholasSpisak
- Docs: split CLI install commands into separate code blocks. (#601) — thanks @martinpucik
- WhatsApp: record outbound provider activity using the active account id. (#537) — thanks @Nachx639
- Discord: add gateway HELLO timeout to detect zombie connections. (#608) — thanks @NicholasSpisak
- Docker: cache dependency layer for faster rebuilds. (#605) — thanks @zknicker
## 2026.1.8
@@ -19,7 +196,7 @@
- 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"`).
- To keep old “open to everyone” behavior: set `dmPolicy="open"` and include `"*"` in the relevant `allowFrom` (Discord/Slack: `discord.dm.allowFrom` / `slack.dm.allowFrom`).
- Approve requests via `clawdbot pairing list --provider <provider>` + `clawdbot pairing approve --provider <provider> <code>` (Telegram also supports `clawdbot telegram pairing ...`).
- Approve requests via `clawdbot pairing list --provider <provider>` + `clawdbot pairing approve --provider <provider> <code>`.
- Sandbox: default `agent.sandbox.scope` to `"agent"` (one container/workspace per agent). Use `"session"` for per-session isolation; `"shared"` disables cross-session isolation.
- Timestamps in agent envelopes are now UTC (compact `YYYY-MM-DDTHH:mmZ`); removed `messages.timestampPrefix`. Add `agent.userTimezone` to tell the model the user’s local time (system prompt only).
- Model config schema changes (auth profiles + model lists); doctor auto-migrates and the gateway rewrites legacy configs on startup.
+6 -1
View File
@@ -8,9 +8,14 @@ RUN corepack enable
WORKDIR /app
COPY . .
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY ui/package.json ./ui/package.json
COPY patches ./patches
COPY scripts ./scripts
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
RUN pnpm ui:install
RUN pnpm ui:build
+48 -34
View File
@@ -37,25 +37,18 @@ Model note: while any model is supported, I strongly recommend **Anthropic Pro/M
- Models config + CLI: [Models](https://docs.clawd.bot/models)
- Auth profile rotation (OAuth vs API keys) + fallbacks: [Model failover](https://docs.clawd.bot/model-failover)
## Recommended setup (from source)
## Install (recommended)
Do **not** download prebuilt binaries. Run from source.
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
Runtime: **Node ≥22**.
```bash
# Clone this repo
git clone https://github.com/clawdbot/clawdbot.git
cd clawdbot
npm install -g clawdbot@latest
# or: pnpm add -g clawdbot@latest
pnpm install
pnpm ui:install
pnpm ui:build
pnpm build
pnpm clawdbot onboard
clawdbot onboard --install-daemon
```
Note: `pnpm clawdbot ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `clawdbot` binary.
The wizard installs the Gateway daemon (launchd/systemd user service) so it stays running.
## Quick start (TL;DR)
@@ -64,23 +57,38 @@ Runtime: **Node ≥22**.
Full beginner guide (auth, pairing, providers): [Getting started](https://docs.clawd.bot/getting-started)
```bash
pnpm clawdbot onboard
clawdbot onboard --install-daemon
pnpm clawdbot gateway --port 18789 --verbose
# Dev loop (auto-reload on TS changes)
pnpm gateway:watch
clawdbot gateway --port 18789 --verbose
# Send a message
pnpm clawdbot send --to +1234567890 --message "Hello from Clawdbot"
clawdbot message send --to +1234567890 --message "Hello from Clawdbot"
# Talk to the assistant (optionally deliver back to WhatsApp/Telegram/Slack/Discord)
pnpm clawdbot agent --message "Ship checklist" --thinking high
clawdbot agent --message "Ship checklist" --thinking high
```
Upgrading? [Updating guide](https://docs.clawd.bot/updating) (and run `clawdbot doctor`).
If you run from source, prefer `pnpm clawdbot …` (not global `clawdbot`).
## From source (development)
Prefer `pnpm` for builds from source. Bun is optional for running TypeScript directly.
```bash
git clone https://github.com/clawdbot/clawdbot.git
cd clawdbot
pnpm install
pnpm ui:build # auto-installs UI deps on first run
pnpm build
pnpm clawdbot onboard --install-daemon
# Dev loop (auto-reload on TS changes)
pnpm gateway:watch
```
Note: `pnpm clawdbot ...` runs TypeScript directly (via `tsx`). `pnpm build` produces `dist/` for running via Node / the packaged `clawdbot` binary.
## Security defaults (DM access)
@@ -231,11 +239,12 @@ ClawdHub is a minimal skill registry. With ClawdHub enabled, the agent can searc
Send these in WhatsApp/Telegram/Slack/WebChat (group commands are owner-only):
- `/status` — health + session info (group shows activation mode)
- `/status` — compact session status (model + tokens, cost when available)
- `/new` or `/reset` — reset the session
- `/compact` — compact session context (summary)
- `/think <level>` — off|minimal|low|medium|high
- `/verbose on|off`
- `/cost on|off` — append per-response token/cost usage lines
- `/restart` — restart the gateway (owner-only in groups)
- `/activation mention|always` — group activation toggle (groups only)
@@ -275,7 +284,7 @@ Runbook: [iOS connect](https://docs.clawd.bot/ios).
## Agent workspace + skills
- Workspace root: `~/clawd` (configurable via `agent.workspace`).
- Workspace root: `~/clawd` (configurable via `agents.defaults.workspace`).
- Injected prompt files: `AGENTS.md`, `SOUL.md`, `TOOLS.md`.
- Skills: `~/clawd/skills/<skill>/SKILL.md`.
@@ -296,7 +305,7 @@ Minimal `~/.clawdbot/clawdbot.json` (model + defaults):
## Security model (important)
- **Default:** tools run on the host for the **main** session, so the agent has full access when it’s just you.
- **Group/channel safety:** set `agent.sandbox.mode: "non-main"` to run **non‑main sessions** (groups/channels) inside per‑session Docker sandboxes; bash then runs in Docker for those sessions.
- **Group/channel safety:** set `agents.defaults.sandbox.mode: "non-main"` to run **non‑main sessions** (groups/channels) inside per‑session Docker sandboxes; bash then runs in Docker for those sessions.
- **Sandbox defaults:** allowlist `bash`, `process`, `read`, `write`, `edit`, `sessions_list`, `sessions_history`, `sessions_send`, `sessions_spawn`; denylist `browser`, `canvas`, `nodes`, `cron`, `discord`, `gateway`.
Details: [Security guide](https://docs.clawd.bot/security) · [Docker + sandboxing](https://docs.clawd.bot/docker) · [Sandbox config](https://docs.clawd.bot/configuration)
@@ -443,17 +452,22 @@ by Peter Steinberger and the community.
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
AI/vibe-coded PRs welcome! 🤖
Special thanks to @andrewting19 for the Anthropic OAuth tool-name fix.
Thanks to all clawtributors:
<p align="left">
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a>
<a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a>
<a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a>
<a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a>
<a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a>
<a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a>
<a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/conhecendocontato"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendocontato" title="conhecendocontato"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a>
<a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a>
<a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a>
<a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/search?q=Tobias%20Bischoff"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a>
<a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a>
<a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a>
<a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a>
<a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a>
<a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a> <a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a>
<a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a>
<a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a>
<a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a>
<a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/conhecendocontato"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendocontato" title="conhecendocontato"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a>
<a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a> <a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a>
<a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a>
<a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a>
</p>
+2 -2
View File
@@ -21,8 +21,8 @@ android {
applicationId = "com.clawdbot.android"
minSdk = 31
targetSdk = 36
versionCode = 20260108
versionName = "2026.1.8"
versionCode = 20260109
versionName = "2026.1.9"
}
buildTypes {
@@ -700,8 +700,8 @@ class NodeRuntime(context: Context) {
val ui = config?.get("ui").asObjectOrNull()
val raw = ui?.get("seamColor").asStringOrNull()?.trim()
val sessionCfg = config?.get("session").asObjectOrNull()
val rawMainKey = sessionCfg?.get("mainKey").asStringOrNull()?.trim()
_mainSessionKey.value = rawMainKey?.takeIf { it.isNotEmpty() } ?: "main"
val mainKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull())
_mainSessionKey.value = mainKey
val parsed = parseHexColorArgb(raw)
_seamColorArgb.value = parsed ?: DEFAULT_SEAM_COLOR_ARGB
@@ -0,0 +1,6 @@
package com.clawdbot.android
internal fun normalizeMainKey(raw: String?): String {
val trimmed = raw?.trim()
return if (!trimmed.isNullOrEmpty()) trimmed else "main"
}
@@ -21,6 +21,7 @@ import android.speech.tts.UtteranceProgressListener
import android.util.Log
import androidx.core.content.ContextCompat
import com.clawdbot.android.bridge.BridgeSession
import com.clawdbot.android.normalizeMainKey
import java.net.HttpURLConnection
import java.net.URL
import java.util.UUID
@@ -814,7 +815,7 @@ class TalkModeManager(
val config = root?.get("config").asObjectOrNull()
val talk = config?.get("talk").asObjectOrNull()
val sessionCfg = config?.get("session").asObjectOrNull()
val mainKey = sessionCfg?.get("mainKey").asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() } ?: "main"
val mainKey = normalizeMainKey(sessionCfg?.get("mainKey").asStringOrNull())
val voice = talk?.get("voiceId")?.asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() }
val aliases =
talk?.get("voiceAliases").asObjectOrNull()?.entries?.mapNotNull { (key, value) ->
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 46 KiB

+2 -2
View File
@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.1.8</string>
<string>2026.1.9</string>
<key>CFBundleVersion</key>
<string>20260108</string>
<string>20260109</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoadsInWebContent</key>
+1 -2
View File
@@ -330,8 +330,7 @@ final class NodeAppModel {
let ui = config["ui"] as? [String: Any]
let raw = (ui?["seamColor"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let session = config["session"] as? [String: Any]
let rawMainKey = (session?["mainKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
let mainKey = rawMainKey.isEmpty ? "main" : rawMainKey
let mainKey = SessionKey.normalizeMainKey(session?["mainKey"] as? String)
await MainActor.run {
self.seamColorHex = raw.isEmpty ? nil : raw
self.mainSessionKey = mainKey
+8
View File
@@ -0,0 +1,8 @@
import Foundation
enum SessionKey {
static func normalizeMainKey(_ raw: String?) -> String {
let trimmed = (raw ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed.isEmpty ? "main" : trimmed
}
}
+1 -2
View File
@@ -651,8 +651,7 @@ final class TalkModeManager: NSObject {
guard let config = json["config"] as? [String: Any] else { return }
let talk = config["talk"] as? [String: Any]
let session = config["session"] as? [String: Any]
let rawMainKey = (session?["mainKey"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
self.mainSessionKey = rawMainKey.isEmpty ? "main" : rawMainKey
self.mainSessionKey = SessionKey.normalizeMainKey(session?["mainKey"] as? String)
self.defaultVoiceId = (talk?["voiceId"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines)
if let aliases = talk?["voiceAliases"] as? [String: Any] {
var resolved: [String: String] = [:]
+2 -2
View File
@@ -17,8 +17,8 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>2026.1.8</string>
<string>2026.1.9</string>
<key>CFBundleVersion</key>
<string>20260108</string>
<string>20260109</string>
</dict>
</plist>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

+4 -1
View File
@@ -29,7 +29,10 @@ final class AppState {
}
var launchAtLogin: Bool {
didSet { self.ifNotPreview { Task { AppStateStore.updateLaunchAtLogin(enabled: self.launchAtLogin) } } }
didSet {
guard !self.isInitializing else { return }
self.ifNotPreview { Task { AppStateStore.updateLaunchAtLogin(enabled: self.launchAtLogin) } }
}
}
var onboardingSeen: Bool {
@@ -81,22 +81,33 @@ enum ClawdbotConfigFile {
static func agentWorkspace() -> String? {
let root = self.loadDict()
let agent = root["agent"] as? [String: Any]
return agent?["workspace"] as? String
let agents = root["agents"] as? [String: Any]
let defaults = agents?["defaults"] as? [String: Any]
return defaults?["workspace"] as? String
}
static func setAgentWorkspace(_ workspace: String?) {
var root = self.loadDict()
var agent = root["agent"] as? [String: Any] ?? [:]
var agents = root["agents"] as? [String: Any] ?? [:]
var defaults = agents["defaults"] as? [String: Any] ?? [:]
let trimmed = workspace?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if trimmed.isEmpty {
agent.removeValue(forKey: "workspace")
defaults.removeValue(forKey: "workspace")
} else {
agent["workspace"] = trimmed
defaults["workspace"] = trimmed
}
if defaults.isEmpty {
agents.removeValue(forKey: "defaults")
} else {
agents["defaults"] = defaults
}
if agents.isEmpty {
root.removeValue(forKey: "agents")
} else {
root["agents"] = agents
}
root["agent"] = agent
self.saveDict(root)
self.logger.debug("agent workspace updated set=\(!trimmed.isEmpty)")
self.logger.debug("agents.defaults.workspace updated set=\(!trimmed.isEmpty)")
}
static func gatewayPassword() -> String? {
@@ -387,13 +387,20 @@ struct ConfigSettings: View {
private func loadConfig() async {
let parsed = await ConfigStore.load()
let agent = parsed["agent"] as? [String: Any]
let heartbeatMinutes = agent?["heartbeatMinutes"] as? Int
let heartbeatBody = agent?["heartbeatBody"] as? String
let agents = parsed["agents"] as? [String: Any]
let defaults = agents?["defaults"] as? [String: Any]
let heartbeat = defaults?["heartbeat"] as? [String: Any]
let heartbeatEvery = heartbeat?["every"] as? String
let heartbeatBody = heartbeat?["prompt"] as? String
let browser = parsed["browser"] as? [String: Any]
let talk = parsed["talk"] as? [String: Any]
let loadedModel = (agent?["model"] as? String) ?? ""
let loadedModel: String = {
if let raw = defaults?["model"] as? String { return raw }
if let modelDict = defaults?["model"] as? [String: Any],
let primary = modelDict["primary"] as? String { return primary }
return ""
}()
if !loadedModel.isEmpty {
self.configModel = loadedModel
self.customModel = loadedModel
@@ -402,7 +409,13 @@ struct ConfigSettings: View {
self.customModel = SessionLoader.fallbackModel
}
if let heartbeatMinutes { self.heartbeatMinutes = heartbeatMinutes }
if let heartbeatEvery {
let digits = heartbeatEvery.trimmingCharacters(in: .whitespacesAndNewlines)
.prefix { $0.isNumber }
if let minutes = Int(digits) {
self.heartbeatMinutes = minutes
}
}
if let heartbeatBody, !heartbeatBody.isEmpty { self.heartbeatBody = heartbeatBody }
if let browser {
@@ -480,25 +493,49 @@ struct ConfigSettings: View {
@MainActor
private static func buildAndSaveConfig(_ draft: ConfigDraft) async -> String? {
var root = await ConfigStore.load()
var agent = root["agent"] as? [String: Any] ?? [:]
var agents = root["agents"] as? [String: Any] ?? [:]
var defaults = agents["defaults"] as? [String: Any] ?? [:]
var browser = root["browser"] as? [String: Any] ?? [:]
var talk = root["talk"] as? [String: Any] ?? [:]
let chosenModel = (draft.configModel == "__custom__" ? draft.customModel : draft.configModel)
.trimmingCharacters(in: .whitespacesAndNewlines)
let trimmedModel = chosenModel
if !trimmedModel.isEmpty { agent["model"] = trimmedModel }
if !trimmedModel.isEmpty {
var model = defaults["model"] as? [String: Any] ?? [:]
model["primary"] = trimmedModel
defaults["model"] = model
var models = defaults["models"] as? [String: Any] ?? [:]
if models[trimmedModel] == nil {
models[trimmedModel] = [:]
}
defaults["models"] = models
}
if let heartbeatMinutes = draft.heartbeatMinutes {
agent["heartbeatMinutes"] = heartbeatMinutes
var heartbeat = defaults["heartbeat"] as? [String: Any] ?? [:]
heartbeat["every"] = "\(heartbeatMinutes)m"
defaults["heartbeat"] = heartbeat
}
let trimmedBody = draft.heartbeatBody.trimmingCharacters(in: .whitespacesAndNewlines)
if !trimmedBody.isEmpty {
agent["heartbeatBody"] = trimmedBody
var heartbeat = defaults["heartbeat"] as? [String: Any] ?? [:]
heartbeat["prompt"] = trimmedBody
defaults["heartbeat"] = heartbeat
}
root["agent"] = agent
if defaults.isEmpty {
agents.removeValue(forKey: "defaults")
} else {
agents["defaults"] = defaults
}
if agents.isEmpty {
root.removeValue(forKey: "agents")
} else {
root["agents"] = agents
}
browser["enabled"] = draft.browserEnabled
let trimmedUrl = draft.browserControlUrl.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -1,6 +1,7 @@
import ClawdbotKit
import Foundation
import Network
import OSLog
actor MacNodeBridgeSession {
private struct TimeoutError: LocalizedError {
@@ -15,14 +16,18 @@ actor MacNodeBridgeSession {
case failed(message: String)
}
private let logger = Logger(subsystem: "com.clawdbot", category: "node.bridge-session")
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
private let clock = ContinuousClock()
private var connection: NWConnection?
private var queue: DispatchQueue?
private var buffer = Data()
private var pendingRPC: [String: CheckedContinuation<BridgeRPCResponse, Error>] = [:]
private var serverEventSubscribers: [UUID: AsyncStream<BridgeEventFrame>.Continuation] = [:]
private var pingTask: Task<Void, Never>?
private var lastPongAt: ContinuousClock.Instant?
private(set) var state: State = .idle
@@ -38,6 +43,12 @@ actor MacNodeBridgeSession {
let params = NWParameters.tcp
params.includePeerToPeer = true
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 30
tcpOptions.keepaliveInterval = 15
tcpOptions.keepaliveCount = 3
params.defaultProtocolStack.transportProtocol = tcpOptions
let connection = NWConnection(to: endpoint, using: params)
let queue = DispatchQueue(label: "com.clawdbot.macos.bridge-session")
self.connection = connection
@@ -47,6 +58,10 @@ actor MacNodeBridgeSession {
connection.start(queue: queue)
try await Self.waitForReady(stateStream, timeoutSeconds: 6)
connection.stateUpdateHandler = { [weak self] state in
guard let self else { return }
Task { await self.handleConnectionState(state) }
}
try await AsyncTimeout.withTimeout(
seconds: 6,
@@ -77,6 +92,7 @@ actor MacNodeBridgeSession {
if base.type == "hello-ok" {
let ok = try self.decoder.decode(BridgeHelloOk.self, from: data)
self.state = .connected(serverName: ok.serverName)
self.startPingLoop()
await onConnected?(ok.serverName)
} else if base.type == "error" {
let err = try self.decoder.decode(BridgeErrorFrame.self, from: data)
@@ -113,6 +129,10 @@ actor MacNodeBridgeSession {
let ping = try self.decoder.decode(BridgePing.self, from: nextData)
try await self.send(BridgePong(type: "pong", id: ping.id))
case "pong":
let pong = try self.decoder.decode(BridgePong.self, from: nextData)
self.notePong(pong)
case "invoke":
let req = try self.decoder.decode(BridgeInvokeRequest.self, from: nextData)
let res = await onInvoke(req)
@@ -182,6 +202,10 @@ actor MacNodeBridgeSession {
}
func disconnect() async {
self.pingTask?.cancel()
self.pingTask = nil
self.lastPongAt = nil
self.connection?.cancel()
self.connection = nil
self.queue = nil
@@ -239,12 +263,17 @@ actor MacNodeBridgeSession {
}
private func send(_ obj: some Encodable) async throws {
guard let connection = self.connection else {
throw NSError(domain: "Bridge", code: 15, userInfo: [
NSLocalizedDescriptionKey: "not connected",
])
}
let data = try self.encoder.encode(obj)
var line = Data()
line.append(data)
line.append(0x0A)
try await withCheckedThrowingContinuation(isolation: self) { (cont: CheckedContinuation<Void, Error>) in
self.connection?.send(content: line, completion: .contentProcessed { err in
connection.send(content: line, completion: .contentProcessed { err in
if let err { cont.resume(throwing: err) } else { cont.resume(returning: ()) }
})
}
@@ -280,6 +309,75 @@ actor MacNodeBridgeSession {
}
}
private func startPingLoop() {
self.pingTask?.cancel()
self.lastPongAt = self.clock.now
self.pingTask = Task { [weak self] in
guard let self else { return }
await self.runPingLoop()
}
}
private func runPingLoop() async {
let interval: Duration = .seconds(15)
let timeout: Duration = .seconds(45)
while !Task.isCancelled {
try? await Task.sleep(for: interval)
guard self.connection != nil else { return }
if let last = self.lastPongAt {
let now = self.clock.now
if now > last.advanced(by: timeout) {
let age = last.duration(to: now)
let ageDescription = String(describing: age)
let message =
"Node bridge heartbeat timed out; disconnecting " +
"(age: \(ageDescription, privacy: .public))."
self.logger.warning(message)
await self.disconnect()
return
}
}
let id = UUID().uuidString
do {
try await self.send(BridgePing(type: "ping", id: id))
} catch {
let errorDescription = String(describing: error)
let message =
"Node bridge ping send failed; disconnecting " +
"(error: \(errorDescription, privacy: .public))."
self.logger.warning(message)
await self.disconnect()
return
}
}
}
private func notePong(_ pong: BridgePong) {
_ = pong
self.lastPongAt = self.clock.now
}
private func handleConnectionState(_ state: NWConnection.State) async {
switch state {
case let .failed(error):
let errorDescription = String(describing: error)
let message =
"Node bridge connection failed; disconnecting " +
"(error: \(errorDescription, privacy: .public))."
self.logger.warning(message)
await self.disconnect()
case .cancelled:
self.logger.warning("Node bridge connection cancelled; disconnecting.")
await self.disconnect()
default:
break
}
}
private static func makeStateStream(
for connection: NWConnection) -> AsyncStream<NWConnection.State>
{
@@ -607,7 +607,7 @@ extension OnboardingView {
let saved = await self.saveAgentWorkspace(AgentWorkspace.displayPath(for: url))
if saved {
self.workspaceStatus =
"Saved to ~/.clawdbot/clawdbot.json (agent.workspace)"
"Saved to ~/.clawdbot/clawdbot.json (agents.defaults.workspace)"
}
}
}
@@ -69,8 +69,9 @@ extension OnboardingView {
private func loadAgentWorkspace() async -> String? {
let root = await ConfigStore.load()
let agent = root["agent"] as? [String: Any]
return agent?["workspace"] as? String
let agents = root["agents"] as? [String: Any]
let defaults = agents?["defaults"] as? [String: Any]
return defaults?["workspace"] as? String
}
@discardableResult
@@ -86,17 +87,23 @@ extension OnboardingView {
@MainActor
private static func buildAndSaveWorkspace(_ workspace: String?) async -> (Bool, String?) {
var root = await ConfigStore.load()
var agent = root["agent"] as? [String: Any] ?? [:]
var agents = root["agents"] as? [String: Any] ?? [:]
var defaults = agents["defaults"] as? [String: Any] ?? [:]
let trimmed = workspace?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
if trimmed.isEmpty {
agent.removeValue(forKey: "workspace")
defaults.removeValue(forKey: "workspace")
} else {
agent["workspace"] = trimmed
defaults["workspace"] = trimmed
}
if agent.isEmpty {
root.removeValue(forKey: "agent")
if defaults.isEmpty {
agents.removeValue(forKey: "defaults")
} else {
root["agent"] = agent
agents["defaults"] = defaults
}
if agents.isEmpty {
root.removeValue(forKey: "agents")
} else {
root["agents"] = agents
}
do {
try await ConfigStore.save(root)
Binary file not shown.
@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.1.8</string>
<string>2026.1.9</string>
<key>CFBundleVersion</key>
<string>20260108</string>
<string>20260109</string>
<key>CFBundleIconFile</key>
<string>Clawdbot</string>
<key>CFBundleURLTypes</key>
@@ -426,6 +426,8 @@ public struct AgentParams: Codable, Sendable {
public let lane: String?
public let extrasystemprompt: String?
public let idempotencykey: String
public let label: String?
public let spawnedby: String?
public init(
message: String,
@@ -438,7 +440,9 @@ public struct AgentParams: Codable, Sendable {
timeout: Int?,
lane: String?,
extrasystemprompt: String?,
idempotencykey: String
idempotencykey: String,
label: String?,
spawnedby: String?
) {
self.message = message
self.to = to
@@ -451,6 +455,8 @@ public struct AgentParams: Codable, Sendable {
self.lane = lane
self.extrasystemprompt = extrasystemprompt
self.idempotencykey = idempotencykey
self.label = label
self.spawnedby = spawnedby
}
private enum CodingKeys: String, CodingKey {
case message
@@ -464,6 +470,8 @@ public struct AgentParams: Codable, Sendable {
case lane
case extrasystemprompt = "extraSystemPrompt"
case idempotencykey = "idempotencyKey"
case label
case spawnedby = "spawnedBy"
}
}
@@ -663,35 +671,78 @@ public struct SessionsListParams: Codable, Sendable {
public let activeminutes: Int?
public let includeglobal: Bool?
public let includeunknown: Bool?
public let label: String?
public let spawnedby: String?
public let agentid: String?
public init(
limit: Int?,
activeminutes: Int?,
includeglobal: Bool?,
includeunknown: Bool?,
spawnedby: String?
label: String?,
spawnedby: String?,
agentid: String?
) {
self.limit = limit
self.activeminutes = activeminutes
self.includeglobal = includeglobal
self.includeunknown = includeunknown
self.label = label
self.spawnedby = spawnedby
self.agentid = agentid
}
private enum CodingKeys: String, CodingKey {
case limit
case activeminutes = "activeMinutes"
case includeglobal = "includeGlobal"
case includeunknown = "includeUnknown"
case label
case spawnedby = "spawnedBy"
case agentid = "agentId"
}
}
public struct SessionsResolveParams: Codable, Sendable {
public let key: String?
public let label: String?
public let agentid: String?
public let spawnedby: String?
public let includeglobal: Bool?
public let includeunknown: Bool?
public init(
key: String?,
label: String?,
agentid: String?,
spawnedby: String?,
includeglobal: Bool?,
includeunknown: Bool?
) {
self.key = key
self.label = label
self.agentid = agentid
self.spawnedby = spawnedby
self.includeglobal = includeglobal
self.includeunknown = includeunknown
}
private enum CodingKeys: String, CodingKey {
case key
case label
case agentid = "agentId"
case spawnedby = "spawnedBy"
case includeglobal = "includeGlobal"
case includeunknown = "includeUnknown"
}
}
public struct SessionsPatchParams: Codable, Sendable {
public let key: String
public let label: AnyCodable?
public let thinkinglevel: AnyCodable?
public let verboselevel: AnyCodable?
public let reasoninglevel: AnyCodable?
public let responseusage: AnyCodable?
public let elevatedlevel: AnyCodable?
public let model: AnyCodable?
public let spawnedby: AnyCodable?
@@ -700,9 +751,11 @@ public struct SessionsPatchParams: Codable, Sendable {
public init(
key: String,
label: AnyCodable?,
thinkinglevel: AnyCodable?,
verboselevel: AnyCodable?,
reasoninglevel: AnyCodable?,
responseusage: AnyCodable?,
elevatedlevel: AnyCodable?,
model: AnyCodable?,
spawnedby: AnyCodable?,
@@ -710,9 +763,11 @@ public struct SessionsPatchParams: Codable, Sendable {
groupactivation: AnyCodable?
) {
self.key = key
self.label = label
self.thinkinglevel = thinkinglevel
self.verboselevel = verboselevel
self.reasoninglevel = reasoninglevel
self.responseusage = responseusage
self.elevatedlevel = elevatedlevel
self.model = model
self.spawnedby = spawnedby
@@ -721,9 +776,11 @@ public struct SessionsPatchParams: Codable, Sendable {
}
private enum CodingKeys: String, CodingKey {
case key
case label
case thinkinglevel = "thinkingLevel"
case verboselevel = "verboseLevel"
case reasoninglevel = "reasoningLevel"
case responseusage = "responseUsage"
case elevatedlevel = "elevatedLevel"
case model
case spawnedby = "spawnedBy"
@@ -1100,6 +1157,51 @@ public struct WebLoginWaitParams: Codable, Sendable {
}
}
public struct AgentSummary: Codable, Sendable {
public let id: String
public let name: String?
public init(
id: String,
name: String?
) {
self.id = id
self.name = name
}
private enum CodingKeys: String, CodingKey {
case id
case name
}
}
public struct AgentsListParams: Codable, Sendable {
}
public struct AgentsListResult: Codable, Sendable {
public let defaultid: String
public let mainkey: String
public let scope: AnyCodable
public let agents: [AgentSummary]
public init(
defaultid: String,
mainkey: String,
scope: AnyCodable,
agents: [AgentSummary]
) {
self.defaultid = defaultid
self.mainkey = mainkey
self.scope = scope
self.agents = agents
}
private enum CodingKeys: String, CodingKey {
case defaultid = "defaultId"
case mainkey = "mainKey"
case scope
case agents
}
}
public struct ModelChoice: Codable, Sendable {
public let id: String
public let name: String
@@ -0,0 +1,18 @@
import Testing
@testable import Clawdbot
@Suite
struct MacNodeBridgeSessionTests {
@Test func sendEventThrowsWhenNotConnected() async {
let session = MacNodeBridgeSession()
do {
try await session.sendEvent(event: "test", payloadJSON: "{}")
Issue.record("Expected sendEvent to throw when disconnected")
} catch {
let ns = error as NSError
#expect(ns.domain == "Bridge")
#expect(ns.code == 15)
}
}
}
@@ -112,24 +112,19 @@ public final class ClawdbotChatViewModel {
recent.append(entry)
}
let mainKey = "main"
var result: [ClawdbotChatSessionEntry] = []
var included = Set<String>()
if let main = sorted.first(where: { $0.key == mainKey }) {
result.append(main)
included.insert(mainKey)
} else if self.sessionKey == mainKey {
result.append(self.placeholderSession(key: mainKey))
included.insert(mainKey)
}
for entry in recent where !included.contains(entry.key) {
result.append(entry)
included.insert(entry.key)
}
if !included.contains(self.sessionKey) {
result.append(self.placeholderSession(key: self.sessionKey))
if let current = sorted.first(where: { $0.key == self.sessionKey }) {
result.append(current)
} else {
result.append(self.placeholderSession(key: self.sessionKey))
}
}
return result
@@ -1,5 +1,75 @@
import Foundation
public enum ClawdbotKitResources {
public static let bundle: Bundle = .module
/// Resource bundle for ClawdbotKit.
///
/// Locates the SwiftPM-generated resource bundle, checking multiple locations:
/// 1. Inside Bundle.main (packaged apps)
/// 2. Bundle.module (SwiftPM development/tests)
/// 3. Falls back to Bundle.main if not found (resource lookups will return nil)
///
/// This avoids a fatal crash when Bundle.module can't locate its resources
/// in packaged .app bundles where the resource bundle path differs from
/// SwiftPM's expectations.
public static let bundle: Bundle = locateBundle()
private static let bundleName = "ClawdbotKit_ClawdbotKit"
private static func locateBundle() -> Bundle {
// 1. Check inside Bundle.main (packaged apps copy resources here)
if let mainResourceURL = Bundle.main.resourceURL {
let bundleURL = mainResourceURL.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: bundleURL) {
return bundle
}
}
// 2. Check Bundle.main directly for embedded resources
if Bundle.main.url(forResource: "tool-display", withExtension: "json") != nil {
return Bundle.main
}
// 3. Try Bundle.module (works in SwiftPM development/tests)
// Wrap in a function to defer the fatalError until actually called
if let moduleBundle = loadModuleBundleSafely() {
return moduleBundle
}
// 4. Fallback: return Bundle.main (resource lookups will return nil gracefully)
return Bundle.main
}
private static func loadModuleBundleSafely() -> Bundle? {
// Bundle.module is generated by SwiftPM and will fatalError if not found.
// We check likely locations manually to avoid the crash.
let candidates: [URL?] = [
Bundle.main.resourceURL,
Bundle.main.bundleURL,
Bundle(for: BundleLocator.self).resourceURL,
Bundle(for: BundleLocator.self).bundleURL,
]
for candidate in candidates {
guard let baseURL = candidate else { continue }
// Direct path
let directURL = baseURL.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: directURL) {
return bundle
}
// Inside Resources/
let resourcesURL = baseURL
.appendingPathComponent("Resources")
.appendingPathComponent("\(bundleName).bundle")
if let bundle = Bundle(url: resourcesURL) {
return bundle
}
}
return nil
}
}
// Helper class for bundle lookup via Bundle(for:)
private final class BundleLocator {}
@@ -0,0 +1,16 @@
import ClawdbotKit
import Foundation
import Testing
@Suite struct ToolDisplayRegistryTests {
@Test func loadsToolDisplayConfigFromBundle() {
let url = ClawdbotKitResources.bundle.url(forResource: "tool-display", withExtension: "json")
#expect(url != nil)
}
@Test func resolvesKnownToolFromConfig() {
let summary = ToolDisplayRegistry.resolve(name: "bash", args: nil)
#expect(summary.emoji == "🛠️")
#expect(summary.title == "Bash")
}
}
+6
View File
@@ -5,6 +5,9 @@ services:
HOME: /home/node
TERM: xterm-256color
CLAWDBOT_GATEWAY_TOKEN: ${CLAWDBOT_GATEWAY_TOKEN}
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
volumes:
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
@@ -30,6 +33,9 @@ services:
HOME: /home/node
TERM: xterm-256color
BROWSER: echo
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
CLAUDE_WEB_COOKIE: ${CLAUDE_WEB_COOKIE}
volumes:
- ${CLAWDBOT_CONFIG_DIR}:/home/node/.clawdbot
- ${CLAWDBOT_WORKSPACE_DIR}:/home/node/clawd
+1 -1
View File
@@ -98,7 +98,7 @@ echo " - Gateway token: $CLAWDBOT_GATEWAY_TOKEN"
echo " - Tailscale exposure: Off"
echo " - Install Gateway daemon: No"
echo ""
docker compose -f "$COMPOSE_FILE" run --rm clawdbot-cli onboard
docker compose -f "$COMPOSE_FILE" run --rm clawdbot-cli onboard --no-install-daemon
echo ""
echo "==> Provider setup (optional)"
+1 -1
View File
@@ -22,7 +22,7 @@ defaults:
nav:
- title: "Home"
url: "/"
- title: "Clawd Setup"
- title: "Clawdbot Assistant"
url: "/start/clawd/"
- title: "Gateway"
url: "/gateway/"
Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

+41
View File
@@ -0,0 +1,41 @@
---
summary: "Monitor OAuth expiry for model providers"
read_when:
- Setting up auth expiry monitoring or alerts
- Automating Claude Code / Codex OAuth refresh checks
---
# Auth monitoring
Clawdbot exposes OAuth expiry health via `clawdbot models status`. Use that for
automation and alerting; scripts are optional extras for phone workflows.
## Preferred: CLI check (portable)
```bash
clawdbot models status --check
```
Exit codes:
- `0`: OK
- `1`: expired or missing credentials
- `2`: expiring soon (within 24h)
This works in cron/systemd and requires no extra scripts.
## Optional scripts (ops / phone workflows)
These live under `scripts/` and are **optional**. They assume SSH access to the
gateway host and are tuned for systemd + Termux.
- `scripts/claude-auth-status.sh` now uses `clawdbot models status --json` as the
source of truth (falling back to direct file reads if the CLI is unavailable),
so keep `clawdbot` on `PATH` for timers.
- `scripts/auth-monitor.sh`: cron/systemd timer target; sends alerts (ntfy or phone).
- `scripts/systemd/clawdbot-auth-monitor.{service,timer}`: systemd user timer.
- `scripts/claude-auth-status.sh`: Claude Code + Clawdbot auth checker (full/json/simple).
- `scripts/mobile-reauth.sh`: guided re‑auth flow over SSH.
- `scripts/termux-quick-auth.sh`: one‑tap widget status + open auth URL.
- `scripts/termux-auth-widget.sh`: full guided widget flow.
- `scripts/termux-sync-widget.sh`: sync Claude Code creds → Clawdbot.
If you don’t need phone automation or systemd timers, skip these scripts.
+89 -46
View File
@@ -6,62 +6,85 @@ read_when:
---
# Cron jobs (Gateway scheduler)
Cron runs inside the Gateway and schedules background work so Clawdbot can
wake itself up, run isolated agent jobs, and deliver reminders on time.
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.
## Update checklist (internal)
- [x] Audit cron + heartbeat behavior in code
- [x] Rewrite cron doc as user-facing feature
- [x] Update heartbeat docs + templates
- [x] Update cron links in docs
- [x] Update changelog
- [x] Run full gate (lint/build/test/docs)
If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
cron is the mechanism.
## What cron is
- **Gateway-owned scheduler** that persists jobs under `~/.clawdbot/cron/`.
- **Two execution modes**:
- **Main session jobs** enqueue `System:` events and rely on the heartbeat runner.
- **Isolated jobs** run a dedicated agent turn in `cron:<jobId>` sessions.
- **Wakeups** are first-class: a job can trigger the next heartbeat or run it now.
## TL;DR
- Cron runs **inside the Gateway** (not inside the model).
- Jobs persist under `~/.clawdbot/cron/` so restarts don’t lose schedules.
- Two execution styles:
- **Main session**: enqueue a system event, then run on the next heartbeat.
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
## When to use it
- Recurring reminders: “every weekday at 7:30” or “every 2h.”
- Background chores: summarize inboxes, check dashboards, watch logs.
- Automation that should not pollute the main chat history.
- Scheduled wakeups that drive the heartbeat pipeline.
## Concepts
## Schedules
### 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).
Jobs are identified by a stable `jobId` (used by CLI/Gateway APIs).
In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibility.
### Schedules
Cron supports three schedule kinds:
- `at`: one-shot timestamp in ms.
- `at`: one-shot timestamp (ms since epoch).
- `every`: fixed interval (ms).
- `cron`: 5-field cron expression, optional IANA timezone.
- `cron`: 5-field cron expression with optional IANA timezone.
Cron expressions use `croner` under the hood. If a timezone is omitted, the
server’s local timezone is used.
Cron expressions use `croner`. If a timezone is omitted, the Gateway host’s
local timezone is used.
## Job types
### Main vs isolated execution
### Main session jobs
#### Main session jobs (system events)
Main jobs enqueue a system event and optionally wake the heartbeat runner.
They **must** use `payload.kind = "systemEvent"`.
They must use `payload.kind = "systemEvent"`.
- **`wakeMode: "next-heartbeat"`** (default): the event waits for the next
scheduled heartbeat.
- **`wakeMode: "now"`**: the event triggers an immediate heartbeat run.
- `wakeMode: "next-heartbeat"` (default): event waits for the next scheduled heartbeat.
- `wakeMode: "now"`: event triggers an immediate heartbeat run.
### Isolated jobs
Isolated jobs run a dedicated agent turn in session `cron:<jobId>` and can
optionally deliver a message.
This is the best fit when you want the normal heartbeat prompt + main-session context.
See [Heartbeat](/gateway/heartbeat).
#### Isolated jobs (dedicated cron sessions)
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
Key behaviors:
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
- A summary is posted to the main session with prefix `Cron` (or
`isolation.postToMainPrefix`).
- A summary is posted to the main session (prefix `Cron`, configurable).
- `wakeMode: "now"` triggers an immediate heartbeat after posting the summary.
- `payload.deliver: true` sends output to a provider; otherwise it stays internal.
- If `payload.deliver: true`, output is delivered to a provider; otherwise it stays internal.
Use isolated jobs for noisy, frequent, or “background chores” that shouldn’t spam
your main chat history.
### Delivery (provider + target)
Isolated jobs can deliver output to a provider. The job payload can specify:
- `provider`: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage` / `last`
- `to`: provider-specific recipient target
If `provider` or `to` is omitted, cron can fall back to the main session’s “last route”
(the last place the agent replied).
#### 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:
- `-1001234567890` (chat id only)
- `-1001234567890:topic:123` (preferred: explicit topic marker)
- `-1001234567890:123` (shorthand: numeric suffix)
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
- `telegram:group:-1001234567890:topic:123`
## Storage & history
- Job store: `~/.clawdbot/cron/jobs.json` (JSON, Gateway-managed).
- Job store: `~/.clawdbot/cron/jobs.json` (Gateway-managed JSON).
- Run history: `~/.clawdbot/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
- Override store path: `cron.store` in config.
@@ -70,16 +93,16 @@ Key behaviors:
```json5
{
cron: {
enabled: true, // default true
enabled: true, // default true
store: "~/.clawdbot/cron/jobs.json",
maxConcurrentRuns: 1 // default 1
maxConcurrentRuns: 1 // default 1
}
}
```
Disable cron entirely:
- `cron.enabled: false` (config)
- or `CLAWDBOT_SKIP_CRON=1` (env)
- `CLAWDBOT_SKIP_CRON=1` (env)
## CLI quickstart
@@ -106,6 +129,19 @@ clawdbot cron add \
--to "+15551234567"
```
Recurring isolated job (deliver to a Telegram topic):
```bash
clawdbot cron add \
--name "Nightly summary (topic)" \
--cron "0 22 * * *" \
--tz "America/Los_Angeles" \
--session isolated \
--message "Summarize today; send to the nightly topic." \
--deliver \
--provider telegram \
--to "-1001234567890:topic:123"
```
Manual run (debug):
```bash
clawdbot cron run <jobId> --force
@@ -121,12 +157,19 @@ Immediate wake without creating a job:
clawdbot wake --mode now --text "Next heartbeat: check battery."
```
## API surface (Gateway)
## Gateway API surface
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
- `cron.run` (force or due), `cron.runs`
- `wake` (enqueue system event + optional heartbeat)
## Tips
- Use **main session jobs** when you want the heartbeat prompt + existing context.
- Use **isolated jobs** for noisy, frequent, or long-running work.
- Keep messages short; cron turns are full agent runs and can burn tokens.
## Troubleshooting
### “Nothing runs”
- Check cron is enabled: `cron.enabled` and `CLAWDBOT_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:<id>` 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.
+25 -2
View File
@@ -49,6 +49,7 @@ that sets `deliver` + optional `provider`/`to`:
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}}",
model: "openai/gpt-5.2-mini",
deliver: true,
provider: "last"
// to: "+15551234567"
@@ -61,6 +62,28 @@ that sets `deliver` + optional `provider`/`to`:
If you want a fixed channel, set `provider` + `to`. Otherwise `provider: "last"`
uses the last delivery route (falls back to WhatsApp).
To force a cheaper model for Gmail runs, set `model` in the mapping
(`provider/model` or alias). If you enforce `agents.defaults.models`, include it there.
To set a default model and thinking level specifically for Gmail hooks, add
`hooks.gmail.model` / `hooks.gmail.thinking` in your config:
```json5
{
hooks: {
gmail: {
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
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.
To customize payload handling further, add `hooks.mappings` or a JS/TS transform module
under `hooks.transformsDir` (see [`docs/webhook.md`](https://docs.clawd.bot/automation/webhook)).
@@ -167,9 +190,9 @@ Notes:
Recommended: `clawdbot hooks gmail run` wraps the same flow and auto-renews the watch.
## Expose the handler (dev, unsupported hack)
## Expose the handler (advanced, unsupported)
If you insist on a non-Tailscale tunnel, wire it manually and use the public URL in the push
If you need a non-Tailscale tunnel, wire it manually and use the public URL in the push
subscription (unsupported, no guardrails):
```bash
+22 -11
View File
@@ -6,28 +6,36 @@ read_when:
---
# Polls
Updated: 2026-01-06
## Supported providers
- WhatsApp (web provider)
- Discord
- MS Teams (Adaptive Cards)
## CLI
```bash
# WhatsApp
clawdbot poll --to +15555550123 -q "Lunch today?" -o "Yes" -o "No" -o "Maybe"
clawdbot poll --to 123456789@g.us -q "Meeting time?" -o "10am" -o "2pm" -o "4pm" -s 2
clawdbot message poll --to +15555550123 \
--poll-question "Lunch today?" --poll-option "Yes" --poll-option "No" --poll-option "Maybe"
clawdbot message poll --to 123456789@g.us \
--poll-question "Meeting time?" --poll-option "10am" --poll-option "2pm" --poll-option "4pm" --poll-multi
# Discord
clawdbot poll --to channel:123456789 -q "Snack?" -o "Pizza" -o "Sushi" --provider discord
clawdbot poll --to channel:123456789 -q "Plan?" -o "A" -o "B" --provider discord --duration-hours 48
clawdbot message poll --provider discord --to channel:123456789 \
--poll-question "Snack?" --poll-option "Pizza" --poll-option "Sushi"
clawdbot message poll --provider discord --to channel:123456789 \
--poll-question "Plan?" --poll-option "A" --poll-option "B" --poll-duration-hours 48
# MS Teams
clawdbot message poll --provider msteams --to conversation:19:abc@thread.tacv2 \
--poll-question "Lunch?" --poll-option "Pizza" --poll-option "Sushi"
```
Options:
- `--provider`: `whatsapp` (default) or `discord`
- `--max-selections`: how many choices a voter can select (default: 1)
- `--duration-hours`: Discord-only (defaults to 24 when omitted)
- `--provider`: `whatsapp` (default), `discord`, or `msteams`
- `--poll-multi`: allow selecting multiple options
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
## Gateway RPC
@@ -45,8 +53,11 @@ Params:
## Provider 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 (Clawdbot-managed). No native poll API; `durationHours` is ignored.
## Agent tool (Discord)
The Discord tool action `poll` still uses `question`, `answers`, optional `allowMultiselect`, `durationHours`, and `content`. The gateway/CLI poll model maps `allowMultiselect` to `maxSelections > 1`.
## Agent tool (Message)
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `provider`).
Note: Discord has no “pick exactly N” mode; `maxSelections` is treated as a boolean (`> 1` = multiselect).
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
Teams polls are rendered as Adaptive Cards and require the gateway to stay online
to record votes in `~/.clawdbot/msteams-polls.json`.
+27 -12
View File
@@ -41,8 +41,8 @@ Payload:
{ "text": "System line", "mode": "now" }
```
- `text` required (string)
- `mode` optional: `now` | `next-heartbeat` (default `now`)
- `text` **required** (string): The description of the event (e.g., "New email received").
- `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
@@ -57,23 +57,25 @@ Payload:
"name": "Email",
"sessionKey": "hook:email:msg-123",
"wakeMode": "now",
"deliver": false,
"deliver": true,
"provider": "last",
"to": "+15551234567",
"model": "openai/gpt-5.2-mini",
"thinking": "low",
"timeoutSeconds": 120
}
```
- `message` required (string)
- `name` optional (used in the summary prefix)
- `sessionKey` optional (default random `hook:<uuid>`)
- `wakeMode` optional: `now` | `next-heartbeat` (default `now`)
- `deliver` optional (default `false`)
- `provider` optional: `last` | `whatsapp` | `telegram`
- `to` optional (provider-specific target)
- `thinking` optional (override)
- `timeoutSeconds` optional
- `message` **required** (string): The prompt or message for the agent to process.
- `name` optional (string): Human-readable name for the hook (e.g., "GitHub"), used as a prefix in session summaries.
- `sessionKey` optional (string): The key used to identify the agent's session. Defaults to a random `hook:<uuid>`. Using a consistent key allows for a multi-turn conversation within the hook context.
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging provider. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
- `provider` optional (string): The messaging service for delivery. One of: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `msteams`. Defaults to `last`.
- `to` optional (string): The recipient identifier for the provider (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack). Defaults to the last recipient in the main session.
- `model` optional (string): Model override (e.g., `anthropic/claude-3-5-sonnet` or an alias). Must be in the allowed model list if restricted.
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
Effect:
- Runs an **isolated** agent turn (own session key)
@@ -121,6 +123,19 @@ curl -X POST http://127.0.0.1:18789/hooks/agent \
-d '{"message":"Summarize inbox","name":"Email","wakeMode":"next-heartbeat"}'
```
### Use a different model
Add `model` to the agent payload (or mapping) to override the model for that run:
```bash
curl -X POST http://127.0.0.1:18789/hooks/agent \
-H 'x-clawdbot-token: SECRET' \
-H 'Content-Type: application/json' \
-d '{"message":"Summarize inbox","name":"Email","model":"openai/gpt-5.2-mini"}'
```
If you enforce `agents.defaults.models`, make sure the override model is included there.
```bash
curl -X POST http://127.0.0.1:18789/hooks/gmail \
-H 'Authorization: Bearer SECRET' \
+395
View File
@@ -0,0 +1,395 @@
# Broadcast Groups
**Status:** Experimental
**Version:** Added in 2026.1.9
## Overview
Broadcast Groups enable multiple agents to process and respond to the same message simultaneously. This allows you to create specialized agent teams that work together in a single WhatsApp group, DM, or channel - all using one phone number.
Broadcast groups are evaluated after provider allowlists and group activation rules. In WhatsApp groups, this means broadcasts happen when Clawdbot would normally reply (for example: on mention, depending on your group settings).
## Use Cases
### 1. Specialized Agent Teams
Deploy multiple agents with atomic, focused responsibilities:
```
Group: "Development Team"
Agents:
- CodeReviewer (reviews code snippets)
- DocumentationBot (generates docs)
- SecurityAuditor (checks for vulnerabilities)
- TestGenerator (suggests test cases)
```
Each agent processes the same message and provides its specialized perspective.
### 2. Multi-Language Support
```
Group: "International Support"
Agents:
- Agent_EN (responds in English)
- Agent_DE (responds in German)
- Agent_ES (responds in Spanish)
```
### 3. Quality Assurance Workflows
```
Group: "Customer Support"
Agents:
- SupportAgent (provides answer)
- QAAgent (reviews quality, only responds if issues found)
```
### 4. Task Automation
```
Group: "Project Management"
Agents:
- TaskTracker (updates task database)
- TimeLogger (logs time spent)
- ReportGenerator (creates summaries)
```
## Configuration
### Basic Setup
Add a top-level `broadcast` section (next to `bindings`):
```json
{
"broadcast": {
"120363403215116621@g.us": ["alfred", "baerbel", "assistant3"]
}
}
```
**Result:** When Clawdbot would reply in this chat, it will run all three agents.
### Processing Strategy
Control how agents process messages:
#### Parallel (Default)
All agents process simultaneously:
```json
{
"broadcast": {
"strategy": "parallel",
"120363403215116621@g.us": ["alfred", "baerbel"]
}
}
```
#### Sequential
Agents process in order (one waits for previous to finish):
```json
{
"broadcast": {
"strategy": "sequential",
"120363403215116621@g.us": ["alfred", "baerbel"]
}
}
```
### Complete Example
```json
{
"agents": {
"list": [
{
"id": "code-reviewer",
"name": "Code Reviewer",
"workspace": "/path/to/code-reviewer",
"sandbox": { "mode": "all" }
},
{
"id": "security-auditor",
"name": "Security Auditor",
"workspace": "/path/to/security-auditor",
"sandbox": { "mode": "all" }
},
{
"id": "docs-generator",
"name": "Documentation Generator",
"workspace": "/path/to/docs-generator",
"sandbox": { "mode": "all" }
}
]
},
"broadcast": {
"strategy": "parallel",
"120363403215116621@g.us": ["code-reviewer", "security-auditor", "docs-generator"],
"120363424282127706@g.us": ["support-en", "support-de"],
"+15555550123": ["assistant", "logger"]
}
}
```
## How It Works
### Message Flow
1. **Incoming message** arrives in a WhatsApp group
2. **Broadcast check**: System checks if peer ID is in `broadcast`
3. **If in broadcast list**:
- All listed agents process the message
- Each agent has its own session key and isolated context
- Agents process in parallel (default) or sequentially
4. **If not in broadcast list**:
- Normal routing applies (first matching binding)
Note: broadcast groups do not bypass provider allowlists or group activation rules (mentions/commands/etc). They only change *which agents run* when a message is eligible for processing.
### Session Isolation
Each agent in a broadcast group maintains completely separate:
- **Session keys** (`agent:alfred:whatsapp:group:120363...` vs `agent:baerbel:whatsapp:group:120363...`)
- **Conversation history** (agent doesn't see other agents' messages)
- **Workspace** (separate sandboxes if configured)
- **Tool access** (different allow/deny lists)
- **Memory/context** (separate IDENTITY.md, SOUL.md, etc.)
- **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)
- Different skills installed
### Example: Isolated Sessions
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]
Workspace: /Users/pascal/clawd-alfred/
Tools: read, write, bash
```
**Bärbel's context:**
```
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
History: [user message, baerbel's previous responses]
Workspace: /Users/pascal/clawd-baerbel/
Tools: read only
```
## Best Practices
### 1. Keep Agents Focused
Design each agent with a single, clear responsibility:
```json
{
"broadcast": {
"DEV_GROUP": ["formatter", "linter", "tester"]
}
}
```
✅ **Good:** Each agent has one job
❌ **Bad:** One generic "dev-helper" agent
### 2. Use Descriptive Names
Make it clear what each agent does:
```json
{
"agents": {
"security-scanner": { "name": "Security Scanner" },
"code-formatter": { "name": "Code Formatter" },
"test-generator": { "name": "Test Generator" }
}
}
```
### 3. Configure Different Tool Access
Give agents only the tools they need:
```json
{
"agents": {
"reviewer": {
"tools": { "allow": ["read", "bash"] } // Read-only
},
"fixer": {
"tools": { "allow": ["read", "write", "edit", "bash"] } // Read-write
}
}
}
```
### 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
### 5. Handle Failures Gracefully
Agents fail independently. One agent's error doesn't block others:
```
Message → [Agent A ✓, Agent B ✗ error, Agent C ✓]
Result: Agent A and C respond, Agent B logs error
```
## Compatibility
### Providers
Broadcast groups currently work with:
- ✅ WhatsApp (implemented)
- 🚧 Telegram (planned)
- 🚧 Discord (planned)
- 🚧 Slack (planned)
### Routing
Broadcast groups work alongside existing routing:
```json
{
"bindings": [
{ "match": { "provider": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, "agentId": "alfred" }
],
"broadcast": {
"GROUP_B": ["agent1", "agent2"]
}
}
```
- `GROUP_A`: Only alfred responds (normal routing)
- `GROUP_B`: agent1 AND agent2 respond (broadcast)
**Precedence:** `broadcast` takes priority over `bindings`.
## Troubleshooting
### 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 ~/.clawdbot/logs/gateway.log | grep broadcast
```
### Only One Agent Responding
**Cause:** Peer ID might be in `bindings` but not `broadcast`.
**Fix:** Add to broadcast config or remove from bindings.
### Performance Issues
**If slow with many agents:**
- Reduce number of agents per group
- Use lighter models (sonnet instead of opus)
- Check sandbox startup time
## Examples
### Example 1: Code Review Team
```json
{
"broadcast": {
"strategy": "parallel",
"120363403215116621@g.us": [
"code-formatter",
"security-scanner",
"test-coverage",
"docs-checker"
]
},
"agents": {
"list": [
{ "id": "code-formatter", "workspace": "~/agents/formatter", "tools": { "allow": ["read", "write"] } },
{ "id": "security-scanner", "workspace": "~/agents/security", "tools": { "allow": ["read", "bash"] } },
{ "id": "test-coverage", "workspace": "~/agents/testing", "tools": { "allow": ["read", "bash"] } },
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
]
}
}
```
**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"
- docs-checker: "Missing docstring for function `process_data`"
### Example 2: Multi-Language Support
```json
{
"broadcast": {
"strategy": "sequential",
"+15555550123": ["detect-language", "translator-en", "translator-de"]
},
"agents": {
"list": [
{ "id": "detect-language", "workspace": "~/agents/lang-detect" },
{ "id": "translator-en", "workspace": "~/agents/translate-en" },
{ "id": "translator-de", "workspace": "~/agents/translate-de" }
]
}
}
```
## API Reference
### Config Schema
```typescript
interface ClawdbotConfig {
broadcast?: {
strategy?: "parallel" | "sequential";
[peerId: string]: string[];
};
}
```
### Fields
- `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
## Limitations
1. **Max agents:** No hard limit, but 10+ agents may be slow
2. **Shared context:** Agents don't see each other's responses (by design)
3. **Message ordering:** Parallel responses may arrive in any order
4. **Rate limits:** All agents count toward WhatsApp rate limits
## 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)
- [ ] Agent priorities (some agents respond before others)
## See Also
- [Multi-Agent Configuration](/multi-agent-sandbox-tools)
- [Routing Configuration](/concepts/provider-routing)
- [Session Management](/concepts/sessions)
+145
View File
@@ -0,0 +1,145 @@
---
summary: "Clawdbot Gateway CLI (`clawdbot gateway`) — run, query, and discover gateways"
read_when:
- Running the Gateway from the CLI (dev or servers)
- Debugging Gateway auth, bind modes, and connectivity
- Discovering gateways via Bonjour (LAN + tailnet)
---
# Gateway CLI
The Gateway is Clawdbot’s WebSocket server (providers, nodes, sessions, hooks).
Subcommands in this page live under `clawdbot gateway …`.
Related docs:
- [/gateway/bonjour](/gateway/bonjour)
- [/gateway/discovery](/gateway/discovery)
- [/gateway/configuration](/gateway/configuration)
## Run the Gateway
Run a local Gateway process:
```bash
clawdbot gateway
```
Notes:
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.clawdbot/clawdbot.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
- Binding beyond loopback without auth is blocked (safety guardrail).
- `SIGUSR1` triggers an in-process restart (useful without a supervisor).
### Options
- `--port <port>`: WebSocket port (default comes from config/env; usually `18789`).
- `--bind <loopback|lan|tailnet|auto>`: listener bind mode.
- `--auth <token|password>`: auth mode override.
- `--token <token>`: token override (also sets `CLAWDBOT_GATEWAY_TOKEN` for the process).
- `--password <password>`: password override (also sets `CLAWDBOT_GATEWAY_PASSWORD` for the process).
- `--tailscale <off|serve|funnel>`: expose the Gateway via Tailscale.
- `--tailscale-reset-on-exit`: reset Tailscale serve/funnel config on shutdown.
- `--dev`: create a dev config + workspace if missing (skips BOOTSTRAP.md).
- `--reset`: reset dev config + credentials + sessions + workspace (requires `--dev`).
- `--force`: kill any existing listener on the selected port before starting.
- `--verbose`: verbose logs.
- `--claude-cli-logs`: only show claude-cli logs in the console (and enable its stdout/stderr).
- `--ws-log <auto|full|compact>`: websocket log style (default `auto`).
- `--compact`: alias for `--ws-log compact`.
- `--raw-stream`: log raw model stream events to jsonl.
- `--raw-stream-path <path>`: raw stream jsonl path.
## Query a running Gateway
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 <url>`: Gateway WebSocket URL.
- `--token <token>`: Gateway token.
- `--password <password>`: Gateway password.
- `--timeout <ms>`: timeout/budget (varies per command).
- `--expect-final`: wait for a “final” response (agent calls).
### `gateway health`
```bash
clawdbot gateway health --url ws://127.0.0.1:18789
```
### `gateway status`
`gateway status` is the “debug everything” command. It always probes:
- your configured remote gateway (if set), and
- localhost (loopback) **even if remote is configured**.
If multiple gateways are reachable, it prints all of them and warns this is an unconventional setup (usually you want only one gateway).
```bash
clawdbot gateway status
clawdbot gateway status --json
```
#### Remote over SSH (Mac app parity)
The macOS app “Remote over SSH” mode uses a local port-forward so the remote gateway (which may be bound to loopback only) becomes reachable at `ws://127.0.0.1:<port>`.
CLI equivalent:
```bash
clawdbot gateway status --ssh steipete@peters-mac-studio-1
```
Options:
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
- `--ssh-identity <path>`: identity file.
- `--ssh-auto`: pick the first discovered bridge host as SSH target (LAN/WAB only).
Config (optional, used as defaults):
- `gateway.remote.sshTarget`
- `gateway.remote.sshIdentity`
### `gateway call <method>`
Low-level RPC helper.
```bash
clawdbot gateway call status
clawdbot gateway call logs.tail --params '{"sinceMs": 60000}'
```
## Discover gateways (Bonjour)
`gateway discover` scans for Gateway bridge beacons (`_clawdbot-bridge._tcp`).
- Multicast DNS-SD: `local.`
- Unicast DNS-SD (Wide-Area Bonjour): `clawdbot.internal.` (requires split DNS + DNS server; see [/gateway/bonjour](/gateway/bonjour))
Only gateways with the **bridge enabled** will advertise the discovery beacon.
Wide-Area discovery records include (TXT):
- `gatewayPort` (WebSocket port, usually `18789`)
- `sshPort` (SSH port; defaults to `22` if not present)
- `tailnetDns` (MagicDNS hostname, when available)
- `cliPath` (optional hint for remote installs)
### `gateway discover`
```bash
clawdbot gateway discover
```
Options:
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
- `--json`: machine-readable output (also disables styling/spinner).
Examples:
```bash
clawdbot gateway discover --timeout 4000
clawdbot gateway discover --json | jq '.beacons[].wsUrl'
```
+77 -41
View File
@@ -1,5 +1,5 @@
---
summary: "CLI reference for clawdbot commands, subcommands, and options"
summary: "Clawdbot CLI reference for `clawdbot` commands, subcommands, and options"
read_when:
- Adding or modifying CLI commands or options
- Documenting new command surfaces
@@ -7,13 +7,13 @@ read_when:
# CLI reference
This page mirrors `src/cli/*` and is the source of truth for CLI behavior.
If you change the CLI code, update this doc.
This page describes the current CLI behavior. If commands change, update this doc.
## Global flags
- `--dev`: isolate state under `~/.clawdbot-dev` and shift default ports.
- `--profile <name>`: isolate state under `~/.clawdbot-<name>`.
- `--no-color`: disable ANSI colors.
- `-V`, `--version`, `-v`: print version and exit.
## Output styling
@@ -21,11 +21,12 @@ If you change the CLI code, update this doc.
- ANSI colors and progress indicators only render in TTY sessions.
- OSC-8 hyperlinks render as clickable links in supported terminals; otherwise we fall back to plain URLs.
- `--json` (and `--plain` where supported) disables styling for clean output.
- `--no-color` disables ANSI styling; `NO_COLOR=1` is also respected.
- Long-running commands show a progress indicator (OSC 9;4 when supported).
## Color palette
Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/theme.ts`.
Clawdbot uses a lobster palette for CLI output.
- `accent` (#FF5A2D): headings, provider labels, primary highlights.
- `accentBright` (#FF7A3D): command names, emphasis.
@@ -36,6 +37,8 @@ Clawdbot uses a lobster palette for CLI output. Source of truth: `src/terminal/t
- `error` (#E23D2D): errors, failures.
- `muted` (#8B7F77): de-emphasis, metadata.
Palette source of truth: `src/terminal/palette.ts` (aka “lobster seam”).
## Command tree
```
@@ -55,8 +58,7 @@ clawdbot [--dev] [--profile <name>] <command>
list
info
check
send
poll
message
agent
agents
list
@@ -69,6 +71,7 @@ clawdbot [--dev] [--profile <name>] <command>
call
health
status
discover
models
list
status
@@ -144,6 +147,14 @@ clawdbot [--dev] [--profile <name>] <command>
tui
```
## Chat slash commands
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
Highlights:
- `/status` for quick diagnostics.
- `/debug` for runtime-only config overrides (memory, not disk).
## Setup + onboarding
### `setup`
@@ -166,8 +177,16 @@ Options:
- `--workspace <dir>`
- `--non-interactive`
- `--mode <local|remote>`
- `--auth-choice <oauth|openai-codex|antigravity|apiKey|minimax|skip>`
- `--auth-choice <setup-token|claude-cli|token|openai-codex|openai-api-key|codex-cli|antigravity|gemini-api-key|apiKey|minimax-cloud|minimax-api|minimax|opencode-zen|skip>`
- `--token-provider <id>` (non-interactive; used with `--auth-choice token`)
- `--token <token>` (non-interactive; used with `--auth-choice token`)
- `--token-profile-id <id>` (non-interactive; default: `<provider>:manual`)
- `--token-expires-in <duration>` (non-interactive; e.g. `365d`, `12h`)
- `--anthropic-api-key <key>`
- `--openai-api-key <key>`
- `--gemini-api-key <key>`
- `--minimax-api-key <key>`
- `--opencode-zen-api-key <key>`
- `--gateway-port <port>`
- `--gateway-bind <loopback|lan|tailnet|auto>`
- `--gateway-auth <off|token|password>`
@@ -202,8 +221,9 @@ Options:
Manage chat provider accounts (WhatsApp/Telegram/Discord/Slack/Signal/iMessage).
Subcommands:
- `providers list`: show configured chat providers and auth profiles (Claude CLI + Codex CLI sync included).
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials; use `status --deep` for local-only probes).
- `providers list`: show configured chat providers and auth profiles (Claude Code + Codex CLI OAuth sync included).
- `providers status`: check gateway reachability and provider health (`--probe` to verify credentials and run small provider audits; use `status --deep` for local-only probes).
- Tip: `providers status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `clawdbot doctor`).
- `providers add`: wizard-style setup when no flags are passed; flags switch to non-interactive mode.
- `providers remove`: disable by default; pass `--delete` to remove config entries without prompts.
- `providers login`: interactive provider login (WhatsApp Web only).
@@ -227,6 +247,14 @@ Common options:
- `--no-usage`: skip provider usage/quota snapshots (OAuth/API-backed only).
- `--json`: output JSON (includes usage unless `--no-usage` is set).
OAuth sync sources:
- Claude Code → `anthropic:claude-cli`
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
- Linux/Windows: `~/.claude/.credentials.json`
- `~/.codex/auth.json` → `openai-codex:codex-cli`
More detail: [/concepts/oauth](/concepts/oauth)
Examples:
```bash
clawdbot providers add --provider telegram --account alerts --name "Alerts Bot" --token $TELEGRAM_BOT_TOKEN
@@ -273,37 +301,25 @@ Options:
## Messaging + agent
### `send`
Send a message through a provider.
### `message`
Unified outbound messaging + provider actions.
Required:
- `--to <dest>`
- `--message <text>`
See: [/cli/message](/cli/message)
Options:
- `--media <path-or-url>`
- `--gif-playback`
- `--provider <whatsapp|telegram|discord|slack|signal|imessage>`
- `--account <id>` (WhatsApp)
- `--dry-run`
- `--json`
- `--verbose`
Subcommands:
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
- `message thread <create|list|reply>`
- `message emoji <list|upload>`
- `message sticker <send|upload>`
- `message role <info|add|remove>`
- `message channel <info|list>`
- `message member info`
- `message voice status`
- `message event <list|create>`
### `poll`
Create a poll (WhatsApp or Discord).
Required:
- `--to <id>`
- `--question <text>`
- `--option <choice>` (repeat 2-12 times)
Options:
- `--max-selections <n>`
- `--duration-hours <n>` (Discord)
- `--provider <whatsapp|discord>`
- `--dry-run`
- `--json`
- `--verbose`
Examples:
- `clawdbot message send --to +15555550123 --message "Hi"`
- `clawdbot message poll --provider discord --to channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
### `agent`
Run one agent turn via the Gateway (or `--local` embedded).
@@ -366,7 +382,7 @@ Options:
Clawdbot can surface provider usage/quota when OAuth/API creds are available.
Surfaces:
- `/status` (adds a short usage line when available)
- `/status` (alias: `/usage`; adds a short usage line when available)
- `clawdbot status --usage` (prints full provider breakdown)
- macOS menu bar (Usage section under Context)
@@ -407,6 +423,8 @@ Options:
- `--tailscale <off|serve|funnel>`
- `--tailscale-reset-on-exit`
- `--allow-unconfigured`
- `--dev`
- `--reset` (reset dev config + credentials + sessions + workspace)
- `--force` (kill existing listener on port)
- `--verbose`
- `--ws-log <auto|full|compact>`
@@ -434,10 +452,17 @@ Notes:
### `logs`
Tail Gateway file logs via RPC.
Notes:
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
- `--json` emits line-delimited JSON (one log event per line).
Examples:
```bash
clawdbot logs --follow
clawdbot logs --limit 200
clawdbot logs --plain
clawdbot logs --json
clawdbot logs --no-color
```
### `gateway <subcommand>`
@@ -456,6 +481,13 @@ Common RPCs:
See [/concepts/models](/concepts/models) for fallback behavior and scanning strategy.
Preferred Anthropic auth (CLI token, not API key):
```bash
claude setup-token
clawdbot models status
```
### `models` (root)
`clawdbot models` is an alias for `models status`.
@@ -471,12 +503,15 @@ Options:
Options:
- `--json`
- `--plain`
- `--check` (exit 1=expired/missing, 2=expiring)
Always includes the auth overview and OAuth expiry status for profiles in the auth store.
### `models set <model>`
Set `agent.model.primary`.
Set `agents.defaults.model.primary`.
### `models set-image <model>`
Set `agent.imageModel.primary`.
Set `agents.defaults.imageModel.primary`.
### `models aliases list|add|remove`
Options:
@@ -638,5 +673,6 @@ Options:
- `--session <key>`
- `--deliver`
- `--thinking <level>`
- `--timeout-ms <ms>`
- `--message <text>`
- `--timeout-ms <ms>` (defaults to `agents.defaults.timeoutSeconds`)
- `--history-limit <n>`
+183
View File
@@ -0,0 +1,183 @@
---
summary: "CLI reference for `clawdbot message` (send + provider actions)"
read_when:
- Adding or modifying message CLI actions
- Changing outbound provider behavior
---
# `clawdbot message`
Single outbound command for sending messages and provider actions
(Discord/Slack/Telegram/WhatsApp/Signal/iMessage/MS Teams).
## Usage
```
clawdbot message <subcommand> [flags]
```
Provider selection:
- `--provider` required if more than one provider is configured.
- If exactly one provider is configured, it becomes the default.
- Values: `whatsapp|telegram|discord|slack|signal|imessage|msteams`
Target formats (`--to`):
- WhatsApp: E.164 or group JID
- Telegram: chat id or `@username`
- Discord/Slack: `channel:<id>` or `user:<id>` (raw id is ambiguous for Discord)
- Signal: E.164, `group:<id>`, or `signal:+E.164`
- iMessage: handle or `chat_id:<id>`
- MS Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`
## Common flags
- `--provider <name>`
- `--account <id>`
- `--json`
- `--dry-run`
- `--verbose`
## Actions
### Core
- `send`
- Required: `--to`, `--message`
- Optional: `--media`, `--reply-to`, `--thread-id`, `--gif-playback`
- Telegram only: `--buttons-json` (requires `"inlineButtons"` in `telegram.capabilities` or `telegram.accounts.<id>.capabilities`)
- `poll`
- Required: `--to`, `--poll-question`, `--poll-option` (repeat)
- Optional: `--poll-multi`, `--poll-duration-hours`, `--message`
- `react`
- Required: `--to`, `--message-id`
- Optional: `--emoji`, `--remove`, `--participant`, `--from-me`, `--channel-id`
- `reactions`
- Required: `--to`, `--message-id`
- Optional: `--limit`, `--channel-id`
- `read`
- Required: `--to`
- Optional: `--limit`, `--before`, `--after`, `--around`, `--channel-id`
- `edit`
- Required: `--to`, `--message-id`, `--message`
- Optional: `--channel-id`
- `delete`
- Required: `--to`, `--message-id`
- Optional: `--channel-id`
- `pin` / `unpin`
- Required: `--to`, `--message-id`
- Optional: `--channel-id`
- `pins` (list)
- Required: `--to`
- Optional: `--channel-id`
- `permissions`
- Required: `--to`
- Optional: `--channel-id`
- `search`
- Required: `--guild-id`, `--query`
- Optional: `--channel-id`, `--channel-ids` (repeat), `--author-id`, `--author-ids` (repeat), `--limit`
### Threads
- `thread create`
- Required: `--thread-name`, `--to` (channel id) or `--channel-id`
- Optional: `--message-id`, `--auto-archive-min`
- `thread list`
- Required: `--guild-id`
- Optional: `--channel-id`, `--include-archived`, `--before`, `--limit`
- `thread reply`
- Required: `--to` (thread id), `--message`
- Optional: `--media`, `--reply-to`
### Emojis
- `emoji list`
- Discord: `--guild-id`
- `emoji upload`
- Required: `--guild-id`, `--emoji-name`, `--media`
- Optional: `--role-ids` (repeat)
### Stickers
- `sticker send`
- Required: `--to`, `--sticker-id` (repeat)
- Optional: `--message`
- `sticker upload`
- Required: `--guild-id`, `--sticker-name`, `--sticker-desc`, `--sticker-tags`, `--media`
### Roles / Channels / Members / Voice
- `role info` (Discord): `--guild-id`
- `role add` / `role remove` (Discord): `--guild-id`, `--user-id`, `--role-id`
- `channel info` (Discord): `--channel-id`
- `channel list` (Discord): `--guild-id`
- `member info` (Discord/Slack): `--user-id` (+ `--guild-id` for Discord)
- `voice status` (Discord): `--guild-id`, `--user-id`
### Events
- `event list` (Discord): `--guild-id`
- `event create` (Discord): `--guild-id`, `--event-name`, `--start-time`
- Optional: `--end-time`, `--desc`, `--channel-id`, `--location`, `--event-type`
### Moderation (Discord)
- `timeout`: `--guild-id`, `--user-id` (+ `--duration-min` or `--until`)
- `kick`: `--guild-id`, `--user-id`
- `ban`: `--guild-id`, `--user-id` (+ `--delete-days`)
## Examples
Send a Discord reply:
```
clawdbot message send --provider discord \
--to channel:123 --message "hi" --reply-to 456
```
Create a Discord poll:
```
clawdbot message poll --provider discord \
--to channel:123 \
--poll-question "Snack?" \
--poll-option Pizza --poll-option Sushi \
--poll-multi --poll-duration-hours 48
```
Send a Teams proactive message:
```
clawdbot message send --provider msteams \
--to conversation:19:abc@thread.tacv2 --message "hi"
```
Create a Teams poll:
```
clawdbot message poll --provider msteams \
--to conversation:19:abc@thread.tacv2 \
--poll-question "Lunch?" \
--poll-option Pizza --poll-option Sushi
```
React in Slack:
```
clawdbot message react --provider slack \
--to C123 --message-id 456 --emoji "✅"
```
Send Telegram inline buttons:
```
clawdbot message send --provider telegram --to @mychat --message "Choose:" \
--buttons-json '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
```
+118
View File
@@ -0,0 +1,118 @@
# Sandbox CLI
Manage Docker-based sandbox containers for isolated agent execution.
## Overview
ClawdBot can run agents in isolated Docker containers for security. The `sandbox` commands help you manage these containers, especially after updates or configuration changes.
## Commands
### `clawdbot sandbox list`
List all sandbox containers with their status and configuration.
```bash
clawdbot sandbox list
clawdbot sandbox list --browser # List only browser containers
clawdbot sandbox list --json # JSON output
```
**Output includes:**
- Container name and status (running/stopped)
- Docker image and whether it matches config
- Age (time since creation)
- Idle time (time since last use)
- Associated session/agent
### `clawdbot sandbox recreate`
Remove sandbox containers to force recreation with updated images/config.
```bash
clawdbot sandbox recreate --all # Recreate all containers
clawdbot sandbox recreate --session main # Specific session
clawdbot sandbox recreate --agent mybot # Specific agent
clawdbot sandbox recreate --browser # Only browser containers
clawdbot sandbox recreate --all --force # Skip confirmation
```
**Options:**
- `--all`: Recreate all sandbox containers
- `--session <key>`: Recreate container for specific session
- `--agent <id>`: Recreate containers for specific agent
- `--browser`: Only recreate browser containers
- `--force`: Skip confirmation prompt
**Important:** Containers are automatically recreated when the agent is next used.
## Use Cases
### After updating Docker images
```bash
# Pull new image
docker pull clawdbot-sandbox:latest
docker tag clawdbot-sandbox:latest clawdbot-sandbox:bookworm-slim
# Update config to use new image
# Edit clawdbot.config.json: agent.sandbox.docker.image
# Recreate containers
clawdbot sandbox recreate --all
```
### After changing sandbox configuration
```bash
# Edit clawdbot.config.json: agent.sandbox.*
# Recreate to apply new config
clawdbot sandbox recreate --all
```
### For a specific agent only
```bash
# Update only one agent's containers
clawdbot sandbox recreate --agent alfred
```
## Why is this needed?
**Problem:** When you update sandbox Docker images or configuration:
- Existing containers continue running with old settings
- Containers are only pruned after 24h of inactivity
- Regularly-used agents keep old containers running indefinitely
**Solution:** Use `clawdbot sandbox recreate` to force removal of old containers. They'll be recreated automatically with current settings when next needed.
## Configuration
Sandbox settings are in `clawdbot.config.json`:
```jsonc
{
"agent": {
"sandbox": {
"mode": "all", // off, non-main, all
"scope": "agent", // session, agent, shared
"docker": {
"image": "clawdbot-sandbox:bookworm-slim",
"containerPrefix": "clawdbot-sbx-"
// ... more Docker options
},
"prune": {
"idleHours": 24, // Auto-prune after 24h idle
"maxAgeDays": 7 // Auto-prune after 7 days
}
}
}
}
```
## See Also
- [Sandbox Documentation](/gateway/sandboxing)
- [Agent Configuration](/concepts/agent-workspace)
- [Doctor Command](/gateway/doctor) - Check sandbox setup
+8 -18
View File
@@ -3,13 +3,13 @@ summary: "Agent loop lifecycle, streams, and wait semantics"
read_when:
- You need an exact walkthrough of the agent loop or lifecycle events
---
# Agent Loop (Clawdis)
# Agent Loop (Clawdbot)
Short, exact flow of one agent run. Source of truth: current code in `src/`.
Short, exact flow of one agent run.
## Entry points
- Gateway RPC: `agent` and `agent.wait` in [`src/gateway/server-methods/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent.ts).
- CLI: `agentCommand` in [`src/commands/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/agent.ts).
- Gateway RPC: `agent` and `agent.wait`.
- CLI: `agent` command.
## High-level flow
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
@@ -23,7 +23,7 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
- streams assistant deltas + tool events
- enforces timeout -> aborts run if exceeded
- returns payloads + usage metadata
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Clawdis `agent` stream:
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to Clawdbot `agent` stream:
- tool events => `stream: "tool"`
- assistant deltas => `stream: "assistant"`
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
@@ -37,25 +37,15 @@ Short, exact flow of one agent run. Source of truth: current code in `src/`.
- `tool`: streamed tool events from pi-agent-core
## Chat provider handling
- `createAgentEventHandler` in [`src/gateway/server-chat.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-chat.ts):
- buffers assistant deltas
- emits chat `delta` messages
- emits chat `final` when **lifecycle end/error** arrives
- Assistant deltas are buffered into chat `delta` messages.
- A chat `final` is emitted on **lifecycle end/error**.
## Timeouts
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
- Agent runtime: `agent.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
## Where things can end early
- Agent timeout (abort)
- AbortSignal (cancel)
- Gateway disconnect or RPC timeout
- `agent.wait` timeout (wait-only, does not stop agent)
## Files
- [`src/gateway/server-methods/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent.ts)
- [`src/gateway/server-methods/agent-job.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/agent-job.ts)
- [`src/commands/agent.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/agent.ts)
- [`src/agents/pi-embedded-runner.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/agents/pi-embedded-runner.ts)
- [`src/agents/pi-embedded-subscribe.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/agents/pi-embedded-subscribe.ts)
- [`src/gateway/server-chat.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-chat.ts)
+11 -4
View File
@@ -12,6 +12,13 @@ file tools and for workspace context. Keep it private and treat it as memory.
This is separate from `~/.clawdbot/`, which stores config, credentials, and
sessions.
**Important:** the workspace is the **default cwd**, not a hard sandbox. Tools
resolve relative paths against the workspace, but absolute paths can still reach
elsewhere on the host unless sandboxing is enabled. If you need isolation, use
[`agents.defaults.sandbox`](/gateway/sandboxing) (and/or per‑agent sandbox config).
When sandboxing is enabled and `workspaceAccess` is not `"rw"`, tools operate
inside a sandbox workspace under `~/.clawdbot/sandboxes`, not your host workspace.
## Default location
- Default: `~/clawd`
@@ -46,7 +53,7 @@ only one workspace is active at a time.
**Recommendation:** keep a single active workspace. If you no longer use the
legacy folders, archive or move them to Trash (for example `trash ~/clawdis`).
If you intentionally keep multiple workspaces, make sure
`agent.workspace` points to the active one.
`agents.defaults.workspace` points to the active one.
`clawdbot doctor` warns when it detects legacy workspace directories.
@@ -200,7 +207,7 @@ Suggested `.gitignore` starter:
## Moving the workspace to a new machine
1. Clone the repo to the desired path (default `~/clawd`).
2. Set `agent.workspace` to that path in `~/.clawdbot/clawdbot.json`.
2. Set `agents.defaults.workspace` to that path in `~/.clawdbot/clawdbot.json`.
3. Run `clawdbot setup --workspace <path>` to seed any missing files.
4. If you need sessions, copy `~/.clawdbot/agents/<agentId>/sessions/` from the
old machine separately.
@@ -209,5 +216,5 @@ Suggested `.gitignore` starter:
- Multi-agent routing can use different workspaces per agent. See
`docs/provider-routing.md` for routing configuration.
- If `agent.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agent.sandbox.workspaceRoot`.
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agents.defaults.sandbox.workspaceRoot`.
+15 -24
View File
@@ -5,23 +5,23 @@ read_when:
---
# Agent Runtime 🤖
CLAWDBOT runs a single embedded agent runtime derived from **p-mono** (internal name: **p**).
CLAWDBOT runs a single embedded agent runtime derived from **p-mono**.
## Workspace (required)
CLAWDBOT uses a single agent workspace directory (`agent.workspace`) as the agent’s **only** working directory (`cwd`) for tools and context.
CLAWDBOT uses a single agent workspace directory (`agents.defaults.workspace`) as the agent’s **only** working directory (`cwd`) for tools and context.
Recommended: use `clawdbot setup` to create `~/.clawdbot/clawdbot.json` if missing and initialize the workspace files.
Full workspace layout + backup guide: [`docs/agent-workspace.md`](/concepts/agent-workspace)
If `agent.sandbox` is enabled, non-main sessions can override this with
per-session workspaces under `agent.sandbox.workspaceRoot` (see
If `agents.defaults.sandbox` is enabled, non-main sessions can override this with
per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see
[`docs/configuration.md`](/gateway/configuration)).
## Bootstrap files (injected)
Inside `agent.workspace`, CLAWDBOT expects these user-editable files:
Inside `agents.defaults.workspace`, CLAWDBOT expects these user-editable files:
- `AGENTS.md` — operating instructions + “memory”
- `SOUL.md` — persona, boundaries, tone
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
@@ -43,9 +43,9 @@ To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
{ agent: { skipBootstrap: true } }
```
## Built-in tools (internal)
## Built-in tools
p’s embedded core tools (read/bash/edit/write and related internals) are defined in code and always available. `TOOLS.md` does **not** control which tools exist; it’s guidance for how *you* want them used.
Core tools (read/bash/edit/write and related system tools) are always available. `TOOLS.md` does **not** control which tools exist; it’s guidance for how *you* want them used.
## Skills
@@ -63,18 +63,6 @@ Clawdbot reuses pieces of the p-mono codebase (models/tools), but **session mana
- No p-coding agent runtime.
- No `~/.pi/agent` or `<workspace>/.pi` settings are consulted.
## Peter @ steipete (only)
Apply these notes **only** when the user is Peter Steinberger at steipete.
- Gateway runs on the **Mac Studio in London**.
- Primary work computer: **MacBook Pro**.
- Peter travels between **Vienna** and **London**; there are two networks bridged via **Tailscale**.
- For debugging, connect to the Mac Studio (London) or MacBook Pro (primary).
- There is also an **M1 MacBook Pro** on the Vienna tailnet you can use to access the Vienna network.
- Nodes can be accessed via the `clawdbot` binary (`pnpm clawdbot` in `~/Projects/clawdbot`).
- See also `skills/clawdbot*` for node/browser/canvas/cron usage.
## Sessions
Session transcripts are stored as JSONL at:
@@ -95,11 +83,14 @@ When queue mode is `followup` or `collect`, inbound messages are held until the
current turn ends, then a new agent turn starts with the queued payloads. See
[`docs/queue.md`](/concepts/queue) for mode + debounce/cap behavior.
Block streaming sends completed assistant blocks as soon as they finish; disable
via `agent.blockStreamingDefault: "off"` if you only want the final response.
Tune the boundary via `agent.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text_end).
Control soft block chunking with `agent.blockStreamingChunk` (defaults to
Block streaming sends completed assistant blocks as soon as they finish; it is
**off by default** (`agents.defaults.blockStreamingDefault: "off"`).
Tune the boundary via `agents.defaults.blockStreamingBreak` (`text_end` vs `message_end`; defaults to text_end).
Control soft block chunking with `agents.defaults.blockStreamingChunk` (defaults to
800–1200 chars; prefers paragraph breaks, then newlines; sentences last).
Coalesce streamed chunks with `agents.defaults.blockStreamingCoalesce` to reduce
single-line spam (idle-based merging before send). Non-Telegram providers require
explicit `*.blockStreaming: true` to enable block replies.
Verbose tool summaries are emitted at tool start (no debounce); Control UI
streams tool output via agent events when available.
More details: [Streaming + chunking](/concepts/streaming).
@@ -107,7 +98,7 @@ More details: [Streaming + chunking](/concepts/streaming).
## Configuration (minimal)
At minimum, set:
- `agent.workspace`
- `agents.defaults.workspace`
- `whatsapp.allowFrom` (strongly recommended)
---
+64 -78
View File
@@ -3,67 +3,55 @@ summary: "WebSocket gateway architecture, components, and client flows"
read_when:
- Working on gateway protocol, clients, or transports
---
# Gateway Architecture
# Gateway architecture
Last updated: 2026-01-05
## Overview
- A single long-lived **Gateway** process owns all messaging surfaces (WhatsApp via Baileys, Telegram via grammY, Slack via Bolt, Discord via discord.js, Signal via signal-cli, iMessage via imsg, WebChat) and the control/event plane.
- All clients (macOS app, CLI, web UI, automations) connect to the Gateway over one transport: **WebSocket on the configured bind host** (default `127.0.0.1:18789`; tunnel or VPN for remote).
- One Gateway per host; it is the only place that is allowed to open a WhatsApp session. All sends/agent runs go through it.
- By default: the Gateway exposes a Canvas host on `canvasHost.port` (default `18793`), serving `~/clawd/canvas` at `/__clawdbot__/canvas/` with live-reload; disable via `canvasHost.enabled=false` or `CLAWDBOT_SKIP_CANVAS_HOST=1`.
## Implementation snapshot (current code)
### TypeScript Gateway ([`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts))
- Single HTTP + WebSocket server (default `18789`); bind policy `loopback|lan|tailnet|auto`. Refuses non-loopback binds without auth; Tailscale serve/funnel requires loopback.
- Handshake: first frame must be a `connect` request; AJV validates request + params against TypeBox schemas; protocol negotiated via `minProtocol`/`maxProtocol`.
- `hello-ok` includes snapshot (presence/health/stateVersion/uptime/configPath/stateDir), features (methods/events), policy (max payload/buffer/tick), and `canvasHostUrl` when available.
- Events emitted: `agent`, `chat`, `presence`, `tick`, `health`, `heartbeat`, `cron`, `talk.mode`, `node.pair.requested`, `node.pair.resolved`, `voicewake.changed`, `shutdown`.
- Idempotency keys are required for `send`, `agent`, `chat.send`, and node invokes; the dedupe cache avoids double-sends on reconnects. Payload sizes are capped per connection.
- Optional node bridge ([`src/infra/bridge/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bridge/server.ts)): TCP JSONL frames (`hello`, `pair-request`, `req/res`, `event`, `invoke`, `ping`). Node connect/disconnect updates presence and flows into the session bus.
- Control UI + Canvas host: HTTP serves Control UI (base path configurable) and can host the A2UI canvas via [`src/canvas-host/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/canvas-host/server.ts) (live reload). Canvas host URL is advertised to nodes + clients.
### iOS node (`apps/ios`)
- Discovery + pairing: `BridgeDiscoveryModel` uses `NWBrowser` Bonjour discovery and reads TXT fields for LAN/tailnet host hints plus gateway/bridge/canvas ports.
- Auto-connect: `BridgeConnectionController` uses stored `node.instanceId` + Keychain token; supports manual host/port; performs `pair-and-hello`.
- Bridge runtime: `BridgeSession` actor owns an `NWConnection`, JSONL frames, `hello`/`hello-ok`, ping/pong, `req/res`, server `event`s, and `invoke` callbacks; stores `canvasHostUrl`.
- Commands: `NodeAppModel` executes `canvas.*`, `canvas.a2ui.*`, `camera.*`, `screen.record`, `location.get`. Canvas/camera/screen are blocked when backgrounded.
- Canvas + actions: `WKWebView` with A2UI action bridge; accepts actions from local-network or trusted file URLs; intercepts `clawdbot://` deep links and forwards `agent.request` to the bridge.
- Voice/talk: voice wake sends `voice.transcript` events and syncs triggers via `voicewake.get` + `voicewake.changed`; Talk Mode attaches to the bridge.
### Android node (`apps/android`)
- Discovery + pairing: `BridgeDiscovery` uses mDNS/NSD to find `_clawdbot-bridge._tcp`, with manual host/port fallback.
- Auto-connect: `NodeRuntime` restores a stored token, performs `pair-and-hello`, and reconnects to the last discovered or manual bridge.
- Bridge runtime: `BridgeSession` owns the TCP JSONL session (`hello`/`hello-ok`, ping/pong, `req/res`, `event`, `invoke`); stores `canvasHostUrl`.
- Commands: `NodeRuntime` executes `canvas.*`, `canvas.a2ui.*`, `camera.*`, and chat/session events; foreground-only for canvas/camera.
- A single long‑lived **Gateway** owns all messaging surfaces (WhatsApp via
Baileys, Telegram via grammY, Slack, Discord, Signal, iMessage, WebChat).
- All clients (macOS app, CLI, web UI, automations) connect to the Gateway over
**one transport: WebSocket** on the configured bind host (default
`127.0.0.1:18789`).
- One Gateway per host; it is the only place that opens a WhatsApp session.
- A **bridge** (default `18790`) is used for nodes (macOS/iOS/Android).
- A **canvas host** (default `18793`) serves agent‑editable HTML and A2UI.
## Components and flows
- **Gateway (daemon)**
- Maintains WhatsApp (Baileys), Telegram (grammY), Slack (Bolt), Discord (discord.js), Signal (signal-cli), and iMessage (imsg) connections.
- Exposes a typed WS API (req/resp + server push events).
- Validates every inbound frame against JSON Schema; rejects anything before a mandatory `connect`.
- **Clients (mac app / CLI / web admin)**
- One WS connection per client.
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`, toggles) and subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
- On macOS, the app can also be invoked via deep links (`clawdbot://agent?...`) which translate into the same Gateway `agent` request path (see [`docs/macos.md`](/platforms/macos)).
- **Agent process (Pi)**
- Spawned by the Gateway on demand for `agent` calls; streams events back over the same WS connection.
- **WebChat**
- Serves static assets locally.
- Holds a single WS connection to the Gateway for control/data; all sends/agent runs go through the Gateway WS.
- Remote use goes through the same SSH/Tailscale tunnel as other clients.
### Gateway (daemon)
- Maintains provider connections.
- Exposes a typed WS API (requests, responses, server‑push events).
- Validates inbound frames against JSON Schema.
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
### Clients (mac app / CLI / web admin)
- One WS connection per client.
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
### Nodes (macOS / iOS / Android)
- Connect to the **bridge** (TCP JSONL) rather than the WS server.
- Pair with the Gateway to receive a token.
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
### WebChat
- Static UI that uses the Gateway WS API for chat history and sends.
- In remote setups, connects through the same SSH/Tailscale tunnel as other
clients.
## Connection lifecycle (single client)
```
Client Gateway
| |
|---- req:connect -------->|
|<------ res (ok) ---------| (or res error + close)
| (payload=hello-ok carries snapshot: presence + health)
| (payload=hello-ok carries snapshot: presence + health)
| |
|<------ event:presence ---| (deltas)
|<------ event:tick -------| (keepalive/no-op)
|<------ event:presence ---|
|<------ event:tick -------|
| |
|------- req:agent ------->|
|<------ res:agent --------| (ack: {runId,status:"accepted"})
@@ -71,44 +59,42 @@ Client Gateway
|<------ res:agent --------| (final: {runId,status,summary})
| |
```
## Wire protocol (summary)
- Transport: WebSocket, text frames with JSON payloads.
- First frame must be `req {type:"req", id, method:"connect", params:{minProtocol, maxProtocol, client:{name,version,platform,mode,instanceId}, caps, auth?, locale?, userAgent? } }`.
- Server replies `res {type:"res", id, ok:true, payload: hello-ok }` or `ok:false` then closes.
- After handshake:
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
- Events: `{type:"event", event:"agent"|"presence"|"tick"|"shutdown", payload, seq?, stateVersion?}`
- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token` must match; otherwise the socket closes with policy violation.
- Presence payload is structured, not free text: `{host, ip, version, mode, lastInputSeconds?, ts, reason?, tags?[], instanceId? }`.
- Agent runs are acked `{runId,status:"accepted"}` then complete with a final res `{runId,status,summary}`; streamed output arrives as `event:"agent"`.
- Protocol versions are bumped on breaking changes; clients must match `minClient`; Gateway chooses within client’s min/max.
- Idempotency keys are required for side-effecting methods (`send`, `agent`) to safely retry; server keeps a short-lived dedupe cache.
- Policy in `hello-ok` communicates payload/queue limits and tick interval.
- First frame **must** be `connect`.
- After handshake:
- Requests: `{type:"req", id, method, params}` → `{type:"res", id, ok, payload|error}`
- Events: `{type:"event", event, payload, seq?, stateVersion?}`
- If `CLAWDBOT_GATEWAY_TOKEN` (or `--token`) is set, `connect.params.auth.token`
must match or the socket closes.
- Idempotency keys are required for side‑effecting methods (`send`, `agent`) to
safely retry; the server keeps a short‑lived dedupe cache.
## Type system and codegen
- Source of truth: TypeBox (or ArkType) definitions in `protocol/` on the server.
- Build step emits JSON Schema.
- Clients:
- TypeScript: uses the same TypeBox types directly.
- Swift: generated `Codable` models via quicktype from the JSON Schema.
- Validation: AJV on the server for every inbound frame; optional client-side validation for defensive programming.
## Protocol typing and codegen
## Invariants
- Exactly one Gateway controls a single Baileys session per host. No fallbacks to ad-hoc direct Baileys sends.
- Handshake is mandatory; any non-JSON or non-connect first frame is a hard close.
- All methods and events are versioned; new fields are additive; breaking changes increment `protocol`.
- No event replay: on seq gaps, clients must refresh (`health` + `system-presence`) and continue; presence is bounded via TTL/max entries.
- TypeBox schemas define the protocol.
- JSON Schema is generated from those schemas.
- Swift models are generated from the JSON Schema.
## Remote access
- Preferred: Tailscale or VPN; alternate: SSH tunnel `ssh -N -L 18789:127.0.0.1:18789 user@host`.
- Same protocol over the tunnel; same handshake. If a shared token is configured, clients must send it in `connect.params.auth.token` even over the tunnel.
- Same protocol over the tunnel; same handshake. If a shared token is configured, clients must send it in `connect.params.auth.token` even over the tunnel.
- Preferred: Tailscale or VPN.
- Alternative: SSH tunnel
```bash
ssh -N -L 18789:127.0.0.1:18789 user@host
```
- The same handshake + auth token apply over the tunnel.
## Operations snapshot
- Start: `clawdbot gateway` (foreground, logs to stdout).
Supervise with launchd/systemd for restarts.
- Health: request `health` over WS; also surfaced in `hello-ok.health`.
- Metrics/logging: keep outside this spec; gateway should expose Prometheus text or structured logs separately.
## Migration notes
- This architecture supersedes the legacy stdin RPC and the ad-hoc TCP control port. New clients should speak only the WS protocol. Legacy compatibility is intentionally dropped.
- Start: `clawdbot gateway` (foreground, logs to stdout).
- Health: `health` over WS (also included in `hello-ok`).
- Supervision: launchd/systemd for auto‑restart.
## Invariants
- Exactly one Gateway controls a single Baileys session per host.
- Handshake is mandatory; any non‑JSON or non‑connect first frame is a hard close.
- Events are not replayed; clients must refresh on gaps.
+17 -13
View File
@@ -7,7 +7,7 @@ read_when:
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
Note: `routing.groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior.
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
## What’s implemented (2025-12-03)
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`whatsapp.groups`) and overridden per group via `/activation`. When `whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
@@ -28,16 +28,21 @@ Add a `groupChat` block to `~/.clawdbot/clawdbot.json` so display-name pings wor
"*": { "requireMention": true }
}
},
"routing": {
"groupChat": {
"historyLimit": 50,
"mentionPatterns": [
"@?clawd",
"@?clawd\\s*uk",
"@?clawdbot",
"\\+?447700900123"
]
}
"agents": {
"list": [
{
"id": "main",
"groupChat": {
"historyLimit": 50,
"mentionPatterns": [
"@?clawd",
"@?clawd\\s*uk",
"@?clawdbot",
"\\+?447700900123"
]
}
}
]
}
}
```
@@ -61,7 +66,6 @@ Only the owner number (from `whatsapp.allowFrom`, or the bot’s own E.164 when
4) Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
## Testing / verification
- Automated: `pnpm test -- src/web/auto-reply.test.ts --runInBand` (covers mention gating, history injection, sender suffix).
- Manual smoke:
- Send an `@clawd` ping in the group and confirm a reply that references the sender name.
- Send a second ping and verify the history block is included then cleared on the next turn.
@@ -71,4 +75,4 @@ Only the owner number (from `whatsapp.allowFrom`, or the bot’s own E.164 when
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.clawdbot/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
- Typing indicators in groups follow `agent.typingMode` (default: `message` when unmentioned).
- Typing indicators in groups follow `agents.defaults.typingMode` (default: `message` when unmentioned).
+11 -5
View File
@@ -88,11 +88,16 @@ Group messages require a mention unless overridden per group. Defaults live per
"123": { requireMention: false }
}
},
routing: {
groupChat: {
mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"],
historyLimit: 50
}
agents: {
list: [
{
id: "main",
groupChat: {
mentionPatterns: ["@clawd", "clawdbot", "\\+15555550123"],
historyLimit: 50
}
}
]
}
}
```
@@ -100,6 +105,7 @@ Group messages require a mention unless overridden per group. Defaults live per
Notes:
- `mentionPatterns` are case-insensitive regexes.
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
- Discord defaults live in `discord.guilds."*"` (overridable per guild/channel).
+83
View File
@@ -0,0 +1,83 @@
---
summary: "Message flow, sessions, queueing, and reasoning visibility"
read_when:
- Explaining how inbound messages become replies
- Clarifying sessions, queueing modes, or streaming behavior
- Documenting reasoning visibility and usage implications
---
# Messages
This page ties together how Clawdbot handles inbound messages, sessions, queueing,
streaming, and reasoning visibility.
## Message flow (high level)
```
Inbound message
-> routing/bindings -> session key
-> queue (if a run is active)
-> agent run (streaming + tools)
-> outbound replies (provider limits + chunking)
```
Key knobs live in configuration:
- `messages.*` for prefixes, queueing, and group behavior.
- `agents.defaults.*` for block streaming and chunking defaults.
- Provider overrides (`whatsapp.*`, `telegram.*`, etc.) for caps and streaming toggles.
See [Configuration](/gateway/configuration) for full schema.
## Sessions and devices
Sessions are owned by the gateway, not by clients.
- Direct chats collapse into the agent main session key.
- Groups/channels get their own session keys.
- The session store and transcripts live on the gateway host.
Multiple devices/providers can map to the same session, but history is not fully
synced back to every client. Recommendation: use one primary device for long
conversations to avoid divergent context. The Control UI and TUI always show the
gateway-backed session transcript, so they are the source of truth.
Details: [Session management](/concepts/session).
## Queueing and followups
If a run is already active, inbound messages can be queued, steered into the
current run, or collected for a followup turn.
- Configure via `messages.queue` (and `messages.queue.byProvider`).
- Modes: `interrupt`, `steer`, `followup`, `collect`, plus backlog variants.
Details: [Queueing](/concepts/queue).
## Streaming, chunking, and batching
Block streaming sends partial replies as the model produces text blocks.
Chunking respects provider text limits and avoids splitting fenced code.
Key settings:
- `agents.defaults.blockStreamingDefault` (`on|off`, default off)
- `agents.defaults.blockStreamingBreak` (`text_end|message_end`)
- `agents.defaults.blockStreamingChunk` (`minChars|maxChars|breakPreference`)
- `agents.defaults.blockStreamingCoalesce` (idle-based batching)
- Provider overrides: `*.blockStreaming` and `*.blockStreamingCoalesce` (non-Telegram providers require explicit `*.blockStreaming: true`)
Details: [Streaming + chunking](/concepts/streaming).
## Reasoning visibility and tokens
Clawdbot can expose or hide model reasoning:
- `/reasoning on|off|stream` controls visibility.
- Reasoning content still counts toward token usage when produced by the model.
- Telegram supports reasoning stream into the draft bubble.
Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/token-use).
## Prefixes, threading, and replies
Outbound message formatting is centralized in `messages`:
- `messages.responsePrefix` (outbound prefix) and `whatsapp.messagePrefix` (WhatsApp inbound prefix)
- Reply threading via `replyToMode` and per-provider defaults
Details: [Configuration](/gateway/configuration#messages) and provider docs.
+34 -6
View File
@@ -9,7 +9,7 @@ read_when:
Clawdbot handles failures in two stages:
1) **Auth profile rotation** within the current provider.
2) **Model fallback** to the next model in `agent.model.fallbacks`.
2) **Model fallback** to the next model in `agents.defaults.model.fallbacks`.
This doc explains the runtime rules and the data that backs them.
@@ -21,6 +21,8 @@ Clawdbot uses **auth profiles** for both API keys and OAuth tokens.
- Config `auth.profiles` / `auth.order` are **metadata + routing only** (no secrets).
- Legacy import-only OAuth file: `~/.clawdbot/credentials/oauth.json` (imported into `auth-profiles.json` on first use).
More detail: [/concepts/oauth](/concepts/oauth)
Credential types:
- `type: "api_key"` → `{ provider, key }`
- `type: "oauth"` → `{ provider, access, refresh, expires, email? }` (+ `projectId`/`enterpriseUrl` for some providers)
@@ -44,7 +46,7 @@ When a provider has multiple profiles, Clawdbot chooses an order like this:
If no explicit order is configured, Clawdbot uses a round‑robin order:
- **Primary key:** profile type (**OAuth before API keys**).
- **Secondary key:** `usageStats.lastUsed` (oldest first, within each type).
- **Cooldown profiles** are moved to the end, ordered by soonest cooldown expiry.
- **Cooldown/disabled profiles** are moved to the end, ordered by soonest expiry.
### Why OAuth can “look lost”
@@ -77,17 +79,43 @@ State is stored in `auth-profiles.json` under `usageStats`:
}
```
## Billing disables
Billing/credit failures (for example “insufficient credits” / “credit balance too low”) are treated as failover‑worthy, but they’re usually not transient. Instead of a short cooldown, Clawdbot marks the profile as **disabled** (with a longer backoff) and rotates to the next profile/provider.
State is stored in `auth-profiles.json`:
```json
{
"usageStats": {
"provider:profile": {
"disabledUntil": 1736178000000,
"disabledReason": "billing"
}
}
}
```
Defaults:
- Billing backoff starts at **5 hours**, doubles per billing failure, and caps at **24 hours**.
- Backoff counters reset if the profile hasn’t failed for **24 hours** (configurable).
## Model fallback
If all profiles for a provider fail, Clawdbot moves to the next model in
`agent.model.fallbacks`. This applies to auth failures, rate limits, and
timeouts that exhausted profile rotation.
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
timeouts that exhausted profile rotation (other errors do not advance fallback).
When a run starts with a model override (hooks or CLI), fallbacks still end at
`agents.defaults.model.primary` after trying any configured fallbacks.
## Related config
See [`docs/configuration.md`](/gateway/configuration) for:
- `auth.profiles` / `auth.order`
- `agent.model.primary` / `agent.model.fallbacks`
- `agent.imageModel` routing
- `auth.cooldowns.billingBackoffHours` / `auth.cooldowns.billingBackoffHoursByProvider`
- `auth.cooldowns.billingMaxHours` / `auth.cooldowns.failureWindowHours`
- `agents.defaults.model.primary` / `agents.defaults.model.fallbacks`
- `agents.defaults.imageModel` routing
See [`docs/models.md`](/concepts/models) for the broader model selection and fallback overview.
+117 -116
View File
@@ -1,157 +1,158 @@
---
summary: "Plan for models CLI: scan, list, aliases, fallbacks, status"
summary: "Models CLI: list, set, aliases, fallbacks, scan, status"
read_when:
- Adding or modifying models CLI (models list/set/scan/aliases/fallbacks)
- Changing model fallback behavior or selection UX
- Updating model scan probes (tools/images)
---
# Models CLI plan
# Models CLI
See [`docs/model-failover.md`](/concepts/model-failover) for how auth profiles rotate (OAuth vs API keys), cooldowns, and how that interacts with model fallbacks.
See [/concepts/model-failover](/concepts/model-failover) for auth profile
rotation, cooldowns, and how that interacts with fallbacks.
Goal: give clear model visibility + control (configured vs available), plus scan tooling
that prefers tool-call + image-capable models and maintains ordered fallbacks.
## How Clawdbot models work (quick explainer)
## How model selection works
Clawdbot selects models in this order:
1) The configured **primary** model (`agent.model.primary`).
2) If it fails, fallbacks in `agent.model.fallbacks` (in order).
3) Auth failover happens **inside** the provider first (see [/concepts/model-failover](/concepts/model-failover)).
Key pieces:
- `provider/model` is the canonical model id (e.g. `anthropic/claude-opus-4-5`).
- `agent.models` is the **allowlist/catalog** of models Clawdbot can use, with optional aliases and provider params.
- `agent.imageModel` is only used when the primary model **can’t** accept images.
- `models.providers` lets you add custom providers + models (written to `models.json`).
- `/model <id>` switches the active model for the current session; `/model list` shows what’s allowed.
1) **Primary** model (`agents.defaults.model.primary` or `agents.defaults.model`).
2) **Fallbacks** in `agents.defaults.model.fallbacks` (in order).
3) **Provider auth failover** happens inside a provider before moving to the
next model.
Related:
- Context limits are model-specific; long sessions may trigger compaction. See [/concepts/compaction](/concepts/compaction).
- `agents.defaults.models` is the allowlist/catalog of models Clawdbot can use (plus aliases).
- `agents.defaults.imageModel` is used **only when** the primary model can’t accept images.
## Model recommendations
## Config keys (overview)
- [Claude Opus 4.5](https://www.anthropic.com/claude/opus): default primary for assistant + general work. It’s pricey and cap-prone, so consider the [Claude Max $200 subscription](https://www.anthropic.com/pricing/) if you live here.
- [Claude Sonnet 4.5](https://www.anthropic.com/claude/sonnet): default fallback when Opus caps out. Similar behavior with fewer limit headaches.
- [GPT-5.2-Codex](https://developers.openai.com/codex/models): recommended for coding and sub-agents. Prefer the [Codex CLI](https://developers.openai.com/codex/cli) if you want the strongest feel.
- `agents.defaults.model.primary` and `agents.defaults.model.fallbacks`
- `agents.defaults.imageModel.primary` and `agents.defaults.imageModel.fallbacks`
- `agents.defaults.models` (allowlist + aliases + provider params)
- `models.providers` (custom providers written into `models.json`)
Suggested stacks:
- Assistant-first: Opus 4.5 primary → Sonnet 4.5 fallback.
- Agentic coding: Opus 4.5 primary → GPT-5.2-Codex for sub-agents → Sonnet 4.5 fallback.
Model refs are normalized to lowercase. Provider aliases like `z.ai/*` normalize
to `zai/*`.
## Model discussions (community notes)
## “Model is not allowed” (and why replies stop)
Anecdotal notes from the Discord thread on January 4–5, 2026. Treat as “reported by users,” not a benchmark.
If `agents.defaults.models` is set, it becomes the **allowlist** for `/model` and for
session overrides. When a user selects a model that isn’t in that allowlist,
Clawdbot returns:
**Reported working well**
- [Claude Opus 4.5](https://www.anthropic.com/claude/opus): best overall quality in Clawdbot, especially for “assistant” work. Tradeoff is cost and hitting usage limits quickly.
- [Claude Sonnet 4.5](https://www.anthropic.com/claude/sonnet): common fallback when Opus caps out. Similar behavior with fewer limit headaches.
- [Gemini 3 Pro](https://deepmind.google/en/models/gemini/pro/): some users felt it maps well to Clawdbot’s structure. Vibe was “fits the framework” more than “best at everything.”
- [GLM](https://www.zhipuai.cn/en/): used successfully as a worker model under orchestration. Seen as strong for delegated/secondary tasks, not the primary brain.
- [MiniMax M2.1](https://platform.minimax.io/docs/guides/models-intro): “good enough” for grunt work or a cheap fallback. Community nickname was “Temu-Sonnet,” i.e. usable but not Sonnet-level polish.
```
Model "provider/model" is not allowed. Use /model to list available models.
```
**Mixed / unclear**
- [Antigravity](https://blog.google/technology/ai/google-ai-updates-november-2025/) (Claude Opus access): some reported extra Opus quota. Pricing/limits were unclear, so the value is hard to predict.
This happens **before** a normal reply is generated, so the message can feel
like it “didn’t respond.” The fix is to either:
**Reported weak in Clawdbot**
- [GPT-5.2-Codex](https://developers.openai.com/codex/models) inside Clawdbot: reported as rough for conversation/assistant tasks when embedded. Same notes said Codex felt stronger via the [Codex CLI](https://developers.openai.com/codex/cli) than embedded use.
- [Grok](https://docs.x.ai/docs/models/grok-4): people tried it and then abandoned it. No strong upside showed up in the notes.
- Add the model to `agents.defaults.models`, or
- Clear the allowlist (remove `agents.defaults.models`), or
- Pick a model from `/model list`.
**Theme**
- Token burn feels higher than expected in long sessions; people suspect context buildup + tool outputs. Pruning/compaction helps. Check session logs before blaming providers. See [/concepts/session](/concepts/session) and [/concepts/model-failover](/concepts/model-failover).
Example allowlist config:
Want a tailored stack? Share whether you’re using Clawdbot or Clawdis and your main workload (agentic coding vs “assistant” work), and we can suggest a primary + fallback set based on these reports.
```json5
{
agent: {
model: { primary: "anthropic/claude-sonnet-4-5" },
models: {
"anthropic/claude-sonnet-4-5": { alias: "Sonnet" },
"anthropic/claude-opus-4-5": { alias: "Opus" }
}
}
}
```
## Models CLI
## CLI commands
See [/cli](/cli) for the full command tree and CLI flags.
```bash
clawdbot models list
clawdbot models status
clawdbot models set <provider/model>
clawdbot models set-image <provider/model>
### CLI output (list + status)
clawdbot models aliases list
clawdbot models aliases add <alias> <provider/model>
clawdbot models aliases remove <alias>
`clawdbot models list` (default) prints a table with these columns:
- `Model`: `provider/model` key (truncated in TTY).
- `Input`: `text` or `text+image`.
- `Ctx`: context window in K tokens (from the model registry).
- `Local`: `yes/no` when the provider base URL is local.
- `Auth`: `yes/no` when the provider has usable auth.
- `Tags`: origin + role hints.
clawdbot models fallbacks list
clawdbot models fallbacks add <provider/model>
clawdbot models fallbacks remove <provider/model>
clawdbot models fallbacks clear
Common tags:
- `default` — resolved default model.
- `fallback#N` — `agent.model.fallbacks` order.
- `image` — `agent.imageModel.primary`.
- `img-fallback#N` — `agent.imageModel.fallbacks` order.
- `configured` — present in `agent.models`.
- `alias:<name>` — alias from `agent.models.*.alias`.
- `missing` — referenced in config but not found in the registry.
clawdbot models image-fallbacks list
clawdbot models image-fallbacks add <provider/model>
clawdbot models image-fallbacks remove <provider/model>
clawdbot models image-fallbacks clear
```
Output formats:
- `--plain`: prints only `provider/model` keys (one per line).
- `--json`: `{ count, models: [{ key, name, input, contextWindow, local, available, tags, missing }] }`.
`clawdbot models` (no subcommand) is a shortcut for `models status`.
`clawdbot models status` prints the resolved defaults, fallbacks, image model, aliases,
and an **Auth overview** section showing which providers have profiles/env/models.json keys.
`--plain` prints the resolved default model only; `--json` returns a structured object for tooling.
### `models list`
## Config changes
Shows configured models by default. Useful flags:
- `agent.models` (configured model catalog + aliases).
- `agent.models.*.params` (provider-specific API params passed through to requests).
- `agent.model.primary` + `agent.model.fallbacks`.
- `agent.imageModel.primary` + `agent.imageModel.fallbacks` (optional).
- `auth.profiles` + `auth.order` for per-provider auth failover.
- `--all`: full catalog
- `--local`: local providers only
- `--provider <name>`: filter by provider
- `--plain`: one model per line
- `--json`: machine‑readable output
## Scan behavior (models scan)
### `models status`
Shows the resolved primary model, fallbacks, image model, and an auth overview
of configured providers. It also surfaces OAuth expiry status for profiles found
in the auth store (warns within 24h by default). `--plain` prints only the
resolved primary model.
OAuth status is always shown (and included in `--json` output). If a configured
provider has no credentials, `models status` prints a **Missing auth** section.
JSON includes `auth.oauth` (warn window + profiles) and `auth.providers`
(effective auth per provider).
Use `--check` for automation (exit `1` when missing/expired, `2` when expiring).
Preferred Anthropic auth is the Claude CLI setup-token (run on the gateway host):
```bash
claude setup-token
clawdbot models status
```
## Scanning (OpenRouter free models)
`clawdbot models scan` inspects OpenRouter’s **free model catalog** and can
optionally probe models for tool and image support.
Key flags:
- `--no-probe`: skip live probes (metadata only)
- `--min-params <b>`: minimum parameter size (billions)
- `--max-age-days <days>`: skip older models
- `--provider <name>`: provider prefix filter
- `--max-candidates <n>`: fallback list size
- `--set-default`: set `agents.defaults.model.primary` to the first selection
- `--set-image`: set `agents.defaults.imageModel.primary` to the first image selection
Probing requires an OpenRouter API key (from auth profiles or
`OPENROUTER_API_KEY`). Without a key, use `--no-probe` to list candidates only.
Scan results are ranked by:
1) Image support
2) Tool latency
3) Context size
4) Parameter count
Input
- OpenRouter `/models` list (filter `:free`)
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY`
- Requires OpenRouter API key from auth profiles or `OPENROUTER_API_KEY` (see [/environment](/environment))
- Optional filters: `--max-age-days`, `--min-params`, `--provider`, `--max-candidates`
- Probe controls: `--timeout`, `--concurrency`
Probes (direct pi-ai complete)
- Tool-call probe (required):
- Provide a dummy tool, verify tool call emitted.
- Image probe (preferred):
- Prompt includes 1x1 PNG; success if no "unsupported image" error.
When run in a TTY, you can select fallbacks interactively. In non‑interactive
mode, pass `--yes` to accept defaults.
Scoring/selection
- Prefer models passing tool + image for text/tool fallbacks.
- Prefer image-only models for image tool fallback (even if tool probe fails).
- Rank by: image ok, then lower tool latency, then larger context, then params.
## Models registry (`models.json`)
Interactive selection (TTY)
- Multiselect list with per-model stats:
- model id, tool ok, image ok, median latency, context, inferred params.
- Pre-select top N (default 6).
- Non-TTY: auto-select; require `--yes`/`--no-input` to apply.
Output
- Writes `agent.model.fallbacks` ordered.
- Writes `agent.imageModel.fallbacks` ordered (image-capable models).
- Ensures `agent.models` entries exist for selected models.
- Optional `--set-default` to set `agent.model.primary`.
- Optional `--set-image` to set `agent.imageModel.primary`.
## Runtime fallback
- On model failure: try `agent.model.fallbacks` in order.
- Per-provider auth failover uses `auth.order` (or stored profile order) **before**
moving to the next model.
- Image routing uses `agent.imageModel` **only when configured** and the primary
model lacks image input.
- Persist last successful provider/model to session entry; auth profile success is global.
- See [`docs/model-failover.md`](/concepts/model-failover) for auth profile rotation, cooldowns, and timeout handling.
## Tests
- Unit: scan selection ordering + probe classification.
- CLI: list/aliases/fallbacks add/remove + scan writes config.
- Status: shows last used model + fallbacks.
## Docs
- Update [`docs/configuration.md`](/gateway/configuration) with `agent.models` + `agent.model` + `agent.imageModel`.
- Keep this doc current when CLI surface or scan logic changes.
- Note provider aliases like `z.ai/*` -> `zai/*` when relevant.
- Provider ids in model refs are normalized to lowercase.
Custom providers in `models.providers` are written into `models.json` under the
agent directory (default `~/.clawdbot/agents/<agentId>/models.json`). This file
is merged by default unless `models.mode` is set to `replace`.
+48 -31
View File
@@ -17,14 +17,22 @@ An **agent** is a fully scoped brain with its own:
- **State directory** (`agentDir`) for auth profiles, model registry, and per-agent config.
- **Session store** (chat history + routing state) under `~/.clawdbot/agents/<agentId>/sessions`.
Skills are per-agent via each workspace’s `skills/` folder, with shared skills
available from `~/.clawdbot/skills`. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills).
The Gateway can host **one agent** (default) or **many agents** side-by-side.
**Workspace note:** each agent’s workspace is the **default cwd**, not a hard
sandbox. Relative paths resolve inside the workspace, but absolute paths can
reach other host locations unless sandboxing is enabled. See
[Sandboxing](/gateway/sandboxing).
## Paths (quick map)
- Config: `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH`)
- State dir: `~/.clawdbot` (or `CLAWDBOT_STATE_DIR`)
- Workspace: `~/clawd` (or `~/clawd-<agentId>`)
- Agent dir: `~/.clawdbot/agents/<agentId>/agent` (or `routing.agents.<agentId>.agentDir`)
- Agent dir: `~/.clawdbot/agents/<agentId>/agent` (or `agents.list[].agentDir`)
- Sessions: `~/.clawdbot/agents/<agentId>/sessions`
### Single-agent mode (default)
@@ -44,7 +52,7 @@ Use the agent wizard to add a new isolated agent:
clawdbot agents add work
```
Then add `routing.bindings` (or let the wizard do it) to route inbound messages.
Then add `bindings` (or let the wizard do it) to route inbound messages.
Verify with:
@@ -71,7 +79,7 @@ Bindings are **deterministic** and **most-specific wins**:
3. `teamId` (Slack)
4. `accountId` match for a provider
5. provider-level match (`accountId: "*"`)
6. fallback to `routing.defaultAgentId` (default: `main`)
6. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)
## Multiple accounts / phone numbers
@@ -92,39 +100,42 @@ multiple phone numbers without mixing sessions.
```js
{
routing: {
defaultAgentId: "home",
agents: {
home: {
agents: {
list: [
{
id: "home",
default: true,
name: "Home",
workspace: "~/clawd-home",
agentDir: "~/.clawdbot/agents/home/agent",
},
work: {
{
id: "work",
name: "Work",
workspace: "~/clawd-work",
agentDir: "~/.clawdbot/agents/work/agent",
},
},
// Deterministic routing: first match wins (most-specific first).
bindings: [
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
// Optional per-peer override (example: send a specific group to work agent).
{
agentId: "work",
match: {
provider: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "1203630...@g.us" },
},
},
],
},
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
// Deterministic routing: first match wins (most-specific first).
bindings: [
{ agentId: "home", match: { provider: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { provider: "whatsapp", accountId: "biz" } },
// Optional per-peer override (example: send a specific group to work agent).
{
agentId: "work",
match: {
provider: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "1203630...@g.us" },
},
},
],
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
tools: {
agentToAgent: {
enabled: false,
allow: ["home", "work"],
@@ -152,16 +163,18 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
```js
{
routing: {
agents: {
personal: {
agents: {
list: [
{
id: "personal",
workspace: "~/clawd-personal",
sandbox: {
mode: "off", // No sandbox for personal agent
},
// No tool restrictions - all tools available
},
family: {
{
id: "family",
workspace: "~/clawd-family",
sandbox: {
mode: "all", // Always sandboxed
@@ -176,7 +189,7 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
deny: ["bash", "write", "edit"], // Deny others
},
},
},
],
},
}
```
@@ -186,4 +199,8 @@ Starting with v2026.1.6, each agent can have its own sandbox and tool restrictio
- **Resource control**: Sandbox specific agents while keeping others on host
- **Flexible policies**: Different permissions per agent
Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent.
If you need per-agent boundaries, use `agents.list[].tools` to deny `bash`.
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.
+138
View File
@@ -0,0 +1,138 @@
---
summary: "OAuth in Clawdbot: token exchange, storage, CLI sync, and multi-account patterns"
read_when:
- You want to understand Clawdbot OAuth end-to-end
- You hit token invalidation / logout issues
- You want to reuse Claude Code / Codex CLI OAuth tokens
- You want multiple accounts or profile routing
---
# OAuth
Clawdbot supports “subscription auth” via OAuth for providers that offer it (notably **Anthropic (Claude Pro/Max)** and **OpenAI Codex (ChatGPT OAuth)**). This page explains:
- how the OAuth **token exchange** works (PKCE)
- where tokens are **stored** (and why)
- how we **reuse external CLI tokens** (Claude Code / Codex CLI)
- how to handle **multiple accounts** (profiles + per-session overrides)
## The token sink (why it exists)
OAuth providers commonly mint a **new refresh token** during login/refresh flows. Some providers (or OAuth clients) can invalidate older refresh tokens when a new one is issued for the same user/app.
Practical symptom:
- you log in via Clawdbot *and* via Claude Code / Codex CLI → one of them randomly gets “logged out” later
To reduce that, Clawdbot treats `auth-profiles.json` as a **token sink**:
- the runtime reads credentials from **one place**
- we can **sync in** credentials from external CLIs instead of doing a second login
- we can keep multiple profiles and route them deterministically
## Storage (where tokens live)
Secrets are stored **per-agent**:
- Auth profiles (OAuth + API keys): `~/.clawdbot/agents/<agentId>/agent/auth-profiles.json`
- Runtime cache (managed automatically; don’t edit): `~/.clawdbot/agents/<agentId>/agent/auth.json`
Legacy import-only file (still supported, but not the main store):
- `~/.clawdbot/credentials/oauth.json` (imported into `auth-profiles.json` on first use)
All of the above also respect `$CLAWDBOT_STATE_DIR` (state dir override). Full reference: [/gateway/configuration](/gateway/configuration#auth-storage-oauth--api-keys)
## Reusing Claude Code / Codex CLI OAuth tokens (recommended)
If you already signed in with the external CLIs *on the gateway host*, Clawdbot can reuse those tokens without starting a separate OAuth flow:
- Claude Code: `anthropic:claude-cli`
- macOS: Keychain item "Claude Code-credentials" (choose "Always Allow" to avoid launchd prompts)
- Linux/Windows: `~/.claude/.credentials.json`
- Codex CLI: reads `~/.codex/auth.json` → profile `openai-codex:codex-cli`
Sync happens when Clawdbot loads the auth store (so it stays up-to-date when the CLIs refresh tokens).
On macOS, the first read may trigger a Keychain prompt; run `clawdbot models status`
in a terminal once if the Gateway runs headless and can’t access the entry.
How to verify:
```bash
clawdbot models status
clawdbot providers list
```
Or JSON:
```bash
clawdbot providers list --json
```
## OAuth exchange (how login works)
Clawdbot’s interactive login flows are implemented in `@mariozechner/pi-ai` and wired into the wizards/commands.
### Anthropic (Claude Pro/Max)
Flow shape (PKCE):
1) generate PKCE verifier/challenge
2) open `https://claude.ai/oauth/authorize?...`
3) user pastes `code#state`
4) exchange at `https://console.anthropic.com/v1/oauth/token`
5) store `{ access, refresh, expires }` under an auth profile
The wizard path is `clawdbot onboard` → auth choice `oauth` (Anthropic).
### OpenAI Codex (ChatGPT OAuth)
Flow shape (PKCE):
1) generate PKCE verifier/challenge + random `state`
2) open `https://auth.openai.com/oauth/authorize?...`
3) try to capture callback on `http://127.0.0.1:1455/auth/callback`
4) if callback can’t bind (or you’re remote/headless), paste the redirect URL/code
5) exchange at `https://auth.openai.com/oauth/token`
6) extract `accountId` from the access token and store `{ access, refresh, expires, accountId }`
Wizard path is `clawdbot onboard` → auth choice `openai-codex` (or `codex-cli` to reuse an existing Codex CLI login).
## Refresh + expiry
Profiles store an `expires` timestamp.
At runtime:
- if `expires` is in the future → use the stored access token
- if expired → refresh (under a file lock) and overwrite the stored credentials
The refresh flow is automatic; you generally don’t need to manage tokens manually.
## Multiple accounts (profiles) + routing
Two patterns:
### 1) Preferred: separate agents
If you want “personal” and “work” to never interact, use isolated agents (separate sessions + credentials + workspace):
```bash
clawdbot agents add work
clawdbot agents add personal
```
Then configure auth per-agent (wizard) and route chats to the right agent.
### 2) Advanced: multiple profiles in one agent
`auth-profiles.json` supports multiple profile IDs for the same provider.
Pick which profile is used:
- globally via config ordering (`auth.order`)
- per-session via `/model ...@<profileId>`
Example (session override):
- `/model Opus@anthropic:work`
How to see what profile IDs exist:
- `clawdbot providers list --json` (shows `auth[]`)
Related docs:
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
- [/tools/slash-commands](/tools/slash-commands) (command surface)
+50 -84
View File
@@ -7,127 +7,93 @@ read_when:
---
# Presence
Clawdbot “presence” is a lightweight, best-effort view of:
- The **Gateway** itself (one per host), and
- The **clients connected to the Gateway** (mac app, WebChat, CLI, etc.).
Clawdbot “presence” is a lightweight, best‑effort view of:
- the **Gateway** itself, and
- **clients connected to the Gateway** (mac app, WebChat, CLI, etc.)
Presence is used primarily to render the mac app’s **Instances** tab and to provide quick operator visibility.
Presence is used primarily to render the macOS app’s **Instances** tab and to
provide quick operator visibility.
## The data model
## Presence fields (what shows up)
Presence entries are structured objects with (some) fields:
- `instanceId` (optional but strongly recommended): stable client identity used for dedupe
- `host`: a human-readable name (often the machine name)
- `ip`: best-effort IP address (may be missing or stale)
Presence entries are structured objects with fields like:
- `instanceId` (optional but strongly recommended): stable client identity
- `host`: human‑friendly host name
- `ip`: best‑effort IP address
- `version`: client version string
- `deviceFamily` (optional): hardware family like `iPad`, `iPhone`, `Mac`
- `modelIdentifier` (optional): hardware model identifier like `iPad16,6` or `Mac16,6`
- `mode`: e.g. `gateway`, `app`, `webchat`, `cli`
- `lastInputSeconds` (optional): “seconds since last user input” for that client machine
- `reason`: a short marker like `self`, `connect`, `node-connected`, `node-disconnected`, `periodic`, `instances-refresh`
- `text`: legacy/debug summary string (kept for backwards compatibility and UI display)
- `deviceFamily` / `modelIdentifier`: hardware hints
- `mode`: `gateway`, `app`, `webchat`, `cli`, `node`, ...
- `lastInputSeconds`: “seconds since last user input” (if known)
- `reason`: `self`, `connect`, `node-connected`, `periodic`, ...
- `ts`: last update timestamp (ms since epoch)
## Producers (where presence comes from)
Presence entries are produced by multiple sources and then **merged**.
Presence entries are produced by multiple sources and **merged**.
### 1) Gateway self entry
The Gateway seeds a “self” entry at startup so UIs always show at least the current gateway host.
The Gateway always seeds a “self” entry at startup so UIs show the gateway host
even before any clients connect.
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`initSelfPresence()`).
### 2) WebSocket connect
### 2) WebSocket connect (connection-derived presence)
Every WS client begins with a `connect` request. On successful handshake the
Gateway upserts a presence entry for that connection.
Every WS client must begin with a `connect` request. On successful handshake, the Gateway upserts a presence entry for that connection.
#### Why one‑off CLI commands don’t show up
This is meant to answer: “Which clients are currently connected?”
The CLI often connects for short, one‑off commands. To avoid spamming the
Instances list, `client.mode === "cli"` is **not** turned into a presence entry.
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (connect handling uses `connect.params.client.instanceId` when provided; otherwise falls back to `connId`).
### 3) `system-event` beacons
#### Why one-off CLI commands do not show up
Clients can send richer periodic beacons via the `system-event` method. The mac
app uses this to report host name, IP, and `lastInputSeconds`.
The CLI connects to the Gateway to execute one-off commands (health/status/send/agent/etc.). These are not “nodes” and would spam the Instances list, so the Gateway does not create presence entries for clients with `client.mode === "cli"`.
### 3) `system-event` beacons (client-reported presence)
Clients can publish richer periodic beacons via the `system-event` method. The mac app uses this to report:
- a human-friendly host name
- its best-known IP address
- `lastInputSeconds`
Implementation:
- Gateway: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) handles method `system-event` by calling `updateSystemPresence(...)`.
- mac app beaconing: [`apps/macos/Sources/Clawdbot/PresenceReporter.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/PresenceReporter.swift).
### 4) Node bridge beacons (gateway-owned presence)
### 4) Node bridge beacons
When a node bridge connection authenticates, the Gateway emits a presence entry
for that node and starts periodic refresh beacons so it does not expire.
- Connect/disconnect markers: `node-connected`, `node-disconnected`
- Periodic heartbeat: every 3 minutes (`reason: periodic`)
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (node bridge handlers + timer beacons).
for that node and refreshes it periodically so it doesn’t expire.
## Merge + dedupe rules (why `instanceId` matters)
All producers write into a single in-memory presence map.
Presence entries are stored in a single in‑memory map:
Key points:
- Entries are **keyed** by a “presence key”. If two producers use the same key, they update the same entry.
- The best key is a stable, opaque `instanceId` that does not change across restarts.
- Keys are treated case-insensitively.
- Entries are keyed by a **presence key**.
- The best key is a stable `instanceId` that survives restarts.
- Keys are case‑insensitive.
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`normalizePresenceKey()`).
If a client reconnects without a stable `instanceId`, it may show up as a
**duplicate** row.
### mac app identity (stable UUID)
## TTL and bounded size
The mac app uses a persisted UUID as `instanceId` so:
- restarts/reconnects do not create duplicates
- renaming the Mac does not create a new “instance”
- debug/release builds can share the same identity
Presence is intentionally ephemeral:
Implementation: [`apps/macos/Sources/Clawdbot/InstanceIdentity.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstanceIdentity.swift).
- **TTL:** entries older than 5 minutes are pruned
- **Max entries:** 200 (oldest dropped first)
`displayName` (machine name) is used for UI, while `instanceId` is used for dedupe.
## TTL and bounded size (why stale rows disappear)
Presence entries are not permanent:
- TTL: entries older than 5 minutes are pruned
- Max: map is capped at 200 entries (LRU by `ts`)
Implementation: [`src/infra/system-presence.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/system-presence.ts) (`TTL_MS`, `MAX_ENTRIES`, pruning in `listSystemPresence()`).
This keeps the list fresh and avoids unbounded memory growth.
## Remote/tunnel caveat (loopback IPs)
When a client connects over an SSH tunnel / local port forward, the Gateway may see the remote address as loopback (`127.0.0.1`).
When a client connects over an SSH tunnel / local port forward, the Gateway may
see the remote address as `127.0.0.1`. To avoid overwriting a good client‑reported
IP, loopback remote addresses are ignored.
To avoid degrading an otherwise-correct client beacon IP, the Gateway avoids writing loopback remote addresses into presence entries.
Implementation: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) (`isLoopbackAddress()`).
## Consumers (who reads presence)
## Consumers
### macOS Instances tab
The mac app’s Instances tab renders the result of `system-presence`.
Implementation:
- View: [`apps/macos/Sources/Clawdbot/InstancesSettings.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstancesSettings.swift)
- Store: [`apps/macos/Sources/Clawdbot/InstancesStore.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/InstancesStore.swift)
The Instances rows show a small presence indicator (Active/Idle/Stale) based on
the last beacon age. The label is derived from the entry timestamp (`ts`).
The store refreshes periodically and also applies `presence` WS events.
The macOS app renders the output of `system-presence` and applies a small status
indicator (Active/Idle/Stale) based on the age of the last update.
## Debugging tips
- To see the raw list, call `system-presence` against the gateway.
- To see the raw list, call `system-presence` against the Gateway.
- If you see duplicates:
- confirm clients send a stable `instanceId` in the handshake (`connect.params.client.instanceId`)
- confirm beaconing uses the same `instanceId`
- check whether the connection-derived entry is missing `instanceId` (then it will be keyed by `connId` and duplicates are expected on reconnect)
- confirm clients send a stable `instanceId` in the handshake
- confirm periodic beacons use the same `instanceId`
- check whether the connection‑derived entry is missing `instanceId` (duplicates are expected)
+106 -18
View File
@@ -3,24 +3,112 @@ summary: "Routing rules per provider (WhatsApp, Telegram, Discord, web) and shar
read_when:
- Changing provider routing or inbox behavior
---
# Providers & Routing
# Providers & routing
Updated: 2026-01-06
Goal: deterministic replies per provider, while supporting multi-agent + multi-account routing.
Clawdbot routes replies **back to the provider where a message came from**. The
model does not choose a provider; routing is deterministic and controlled by the
host configuration.
- **Provider**: provider label (`whatsapp`, `webchat`, `telegram`, `discord`, `signal`, `imessage`, …). Routing is fixed: replies go back to the origin provider; the model doesn’t choose.
- **AccountId**: provider account instance (e.g. WhatsApp account `"default"` vs `"work"`). Not every provider supports multi-account yet.
- **AgentId**: one isolated “brain” (workspace + per-agent agentDir + per-agent session store).
- **Reply context:** inbound replies include `ReplyToId`, `ReplyToBody`, and `ReplyToSender`, and the quoted context is appended to `Body` as a `[Replying to ...]` block.
- **Canonical direct session (per agent):** direct chats collapse to `agent:<agentId>:<mainKey>` (default `main`). Groups/channels stay isolated per agent:
- group: `agent:<agentId>:<provider>:group:<id>`
- channel/room: `agent:<agentId>:<provider>:channel:<id>`
- Telegram forum topics: `agent:<agentId>:telegram:group:<chatId>:topic:<threadId>`
- **Session store:** per-agent store lives under `~/.clawdbot/agents/<agentId>/sessions/sessions.json` (override via `session.store` with `{agentId}` templating). JSONL transcripts live next to it.
- **WebChat:** attaches to the selected agent’s main session (so desktop reflects cross-provider history for that agent).
- **Implementation hints:**
- Set `Provider` + `AccountId` in each ingress.
- Route inbound to an agent via `routing.bindings` (match on `provider`, `accountId`, plus optional peer/guild/team).
- Keep routing deterministic: originate → same provider. Use the gateway WebSocket for sends; avoid side channels.
- Do not let the agent emit “send to X” decisions; keep that policy in the host code.
## Key terms
- **Provider**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
- **AccountId**: per‑provider account instance (when supported).
- **AgentId**: an isolated workspace + session store (“brain”).
- **SessionKey**: the bucket key used to store context and control concurrency.
## Session key shapes (examples)
Direct messages collapse to the agent’s **main** session:
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
Groups and channels remain isolated per provider:
- Groups: `agent:<agentId>:<provider>:group:<id>`
- Channels/rooms: `agent:<agentId>:<provider>:channel:<id>`
Threads:
- Slack/Discord threads append `:thread:<threadId>` to the base key.
- Telegram forum topics embed `:topic:<topicId>` in the group key.
Examples:
- `agent:main:telegram:group:-1001234567890:topic:42`
- `agent:main:discord:channel:123456:thread:987654`
## Routing rules (how an agent is chosen)
Routing picks **one agent** for each inbound message:
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
2. **Guild match** (Discord) via `guildId`.
3. **Team match** (Slack) via `teamId`.
4. **Account match** (`accountId` on the provider).
5. **Provider match** (any account on that provider).
6. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
The matched agent determines which workspace and session store are used.
## Broadcast groups (run multiple agents)
Broadcast groups let you run **multiple agents** for the same peer **when Clawdbot would normally reply** (for example: in WhatsApp groups, after mention/activation gating).
Config:
```json5
{
broadcast: {
strategy: "parallel",
"120363403215116621@g.us": ["alfred", "baerbel"],
"+15555550123": ["support", "logger"]
}
}
```
See: [Broadcast Groups](/broadcast-groups).
## Config overview
- `agents.list`: named agent definitions (workspace, model, etc.).
- `bindings`: map inbound providers/accounts/peers to agents.
Example:
```json5
{
agents: {
list: [
{ id: "support", name: "Support", workspace: "~/clawd-support" }
]
},
bindings: [
{ match: { provider: "slack", teamId: "T123" }, agentId: "support" },
{ match: { provider: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
]
}
```
## Session storage
Session stores live under the state directory (default `~/.clawdbot`):
- `~/.clawdbot/agents/<agentId>/sessions/sessions.json`
- JSONL transcripts live alongside the store
You can override the store path via `session.store` and `{agentId}` templating.
## WebChat behavior
WebChat attaches to the **selected agent** and defaults to the agent’s main
session. Because of this, WebChat lets you see cross‑provider context for that
agent in one place.
## Reply context
Inbound replies include:
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
This is consistent across providers.
+7 -7
View File
@@ -12,9 +12,9 @@ We now serialize command-based auto-replies (WhatsApp Web listener) through a ti
- Serializing avoids competing for terminal/stdin, keeps logs readable, and reduces the chance of rate limits from upstream tools.
## How it works
- [`src/process/command-queue.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/process/command-queue.ts) holds a lane-aware FIFO queue and drains each lane synchronously.
- A lane-aware FIFO queue drains each lane synchronously.
- `runEmbeddedPiAgent` enqueues by **session key** (lane `session:<key>`) to guarantee only one active run per session.
- Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agent.maxConcurrent`.
- Each session run is then queued into a **global lane** (`main` by default) so overall parallelism is capped by `agents.defaults.maxConcurrent`.
- When verbose logging is enabled, queued commands emit a short notice if they waited more than ~2s before starting.
- Typing indicators (`onReplyStart`) still fire immediately on enqueue so user experience is unchanged while we wait our turn.
@@ -30,16 +30,16 @@ Inbound messages can steer the current run, wait for a followup turn, or do both
Steer-backlog means you can get a followup response after the steered run, so
streaming surfaces can look like duplicates. Prefer `collect`/`steer` if you want
one response per inbound message.
Send `/queue collect` as a standalone command (per-session) or set `routing.queue.byProvider.discord: "collect"`.
Send `/queue collect` as a standalone command (per-session) or set `messages.queue.byProvider.discord: "collect"`.
Defaults (when unset in config):
- All surfaces → `collect`
Configure globally or per provider via `routing.queue`:
Configure globally or per provider via `messages.queue`:
```json5
{
routing: {
messages: {
queue: {
mode: "collect",
debounceMs: 1000,
@@ -67,11 +67,11 @@ Defaults: `debounceMs: 1000`, `cap: 20`, `drop: summarize`.
## Scope and guarantees
- Applies only to config-driven command replies; plain text replies are unaffected.
- Default lane (`main`) is process-wide for inbound + main heartbeats; set `agent.maxConcurrent` to allow multiple sessions in parallel.
- Default lane (`main`) is process-wide for inbound + main heartbeats; set `agents.defaults.maxConcurrent` to allow multiple sessions in parallel.
- Additional lanes may exist (e.g. `cron`) so background jobs can run in parallel without blocking inbound replies.
- Per-session lanes guarantee that only one agent run touches a given session at a time.
- No external dependencies or background worker threads; pure TypeScript + promises.
## Troubleshooting
- If commands seem stuck, enable verbose logs and look for “queued for …ms” lines to confirm the queue is draining.
- `enqueueCommand` exposes a lightweight `getQueueSize()` helper if you need to surface queue depth in future diagnostics.
- If you need queue depth, enable verbose logs and watch for queue timing lines.
+15 -5
View File
@@ -1,12 +1,12 @@
---
summary: "Session pruning: opt-in tool-result trimming to reduce context bloat"
summary: "Session pruning: tool-result trimming to reduce context bloat"
read_when:
- You want to reduce LLM context growth from tool outputs
- You are tuning agent.contextPruning
- You are tuning agents.defaults.contextPruning
---
# Session Pruning
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It is **opt-in** and does **not** rewrite the on-disk session history (`*.jsonl`).
Session pruning trims **old tool results** from the in-memory context right before each LLM call. It does **not** rewrite the on-disk session history (`*.jsonl`).
## When it runs
- Before each LLM request (context hook).
@@ -23,7 +23,7 @@ Session pruning trims **old tool results** from the in-memory context right befo
Pruning uses an estimated context window (chars ≈ tokens × 4). The window size is resolved in this order:
1) Model definition `contextWindow` (from the model registry).
2) `models.providers.*.models[].contextWindow` override.
3) `agent.contextTokens`.
3) `agents.defaults.contextTokens`.
4) Default `200000` tokens.
## Modes
@@ -44,6 +44,7 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
## Tool selection
- `tools.allow` / `tools.deny` support `*` wildcards.
- Deny wins.
- Matching is case-insensitive.
- Empty allow list => all tools allowed.
## Interaction with other limits
@@ -59,7 +60,7 @@ Pruning uses an estimated context window (chars ≈ tokens × 4). The window siz
- `hardClear`: `{ enabled: true, placeholder: "[Old tool result content cleared]" }`
## Examples
Minimal (adaptive):
Default (adaptive):
```json5
{
agent: {
@@ -68,6 +69,15 @@ Minimal (adaptive):
}
```
To disable:
```json5
{
agent: {
contextPruning: { mode: "off" }
}
}
```
Aggressive:
```json5
{
+11 -8
View File
@@ -21,7 +21,7 @@ Goal: small, hard-to-misuse tool set so agents can list sessions, fetch history,
- Hooks use `hook:<uuid>` unless explicitly set.
- Node bridge uses `node-<nodeId>` unless explicitly set.
`global` and `unknown` are internal-only and never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.
`global` and `unknown` are reserved values and are never listed. If `session.scope = "global"`, we alias it to `main` for all tools so callers never see `global`.
## sessions_list
List sessions as an array of rows.
@@ -76,6 +76,7 @@ Behavior:
- `timeoutSeconds > 0`: wait up to N seconds for completion, then return `{ runId, status: "ok", reply }`.
- If wait times out: `{ runId, status: "timeout", error }`. Run continues; call `sessions_history` later.
- If the run fails: `{ runId, status: "error", error }`.
- Announce delivery runs after the primary run completes and is best-effort; `status: "ok"` does not guarantee the announce was delivered.
- Waits via gateway `agent.wait` (server-side) so reconnects don't drop the wait.
- Agent-to-agent message context is injected for the primary run.
- After the primary run completes, Clawdbot runs a **reply-back loop**:
@@ -132,19 +133,19 @@ Parameters:
- `cleanup?` (`delete|keep`, default `keep`)
Allowlist:
- `routing.agents.<agentId>.subagents.allowAgents`: list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
- `agents.list[].subagents.allowAgents`: list of agent ids allowed via `agentId` (`["*"]` to allow any). Default: only the requester agent.
Discovery:
- Use `agents_list` to discover which agent ids are allowed for `sessions_spawn`.
Behavior:
- Starts a new `agent:<agentId>:subagent:<uuid>` session with `deliver: false`.
- Sub-agents default to the full tool set **minus session tools** (configurable via `agent.subagents.tools`).
- Sub-agents default to the full tool set **minus session tools** (configurable via `tools.subagents.tools`).
- Sub-agents are not allowed to call `sessions_spawn` (no sub-agent → sub-agent spawning).
- Always non-blocking: returns `{ status: "accepted", runId, childSessionKey }` immediately.
- After completion, Clawdbot runs a sub-agent **announce step** and posts the result to the requester chat provider.
- Reply exactly `ANNOUNCE_SKIP` during the announce step to stay silent.
- Sub-agent sessions are auto-archived after `agent.subagents.archiveAfterMinutes` (default: 60).
- Sub-agent sessions are auto-archived after `agents.defaults.subagents.archiveAfterMinutes` (default: 60).
- Announce replies include a stats line (runtime, tokens, sessionKey/sessionId, transcript path, and optional cost).
## Sandbox Session Visibility
@@ -155,10 +156,12 @@ Config:
```json5
{
agent: {
sandbox: {
// default: "spawned"
sessionToolsVisibility: "spawned" // or "all"
agents: {
defaults: {
sandbox: {
// default: "spawned"
sessionToolsVisibility: "spawned" // or "all"
}
}
}
}
+2 -2
View File
@@ -21,8 +21,8 @@ All session state is **owned by the gateway** (the “master” Clawdbot). UI cl
- Group entries may include `displayName`, `provider`, `subject`, `room`, and `space` to label sessions in UIs.
- Clawdbot does **not** read legacy Pi/Tau session folders.
## Session pruning (optional)
Clawdbot can trim **old tool results** from the in-memory context right before LLM calls (opt-in).
## Session pruning
Clawdbot trims **old tool results** from the in-memory context right before LLM calls by default.
This does **not** rewrite JSONL history. See [/concepts/session-pruning](/concepts/session-pruning).
## Mapping transports → session keys
+26 -4
View File
@@ -32,9 +32,11 @@ Legend:
- `provider send`: actual outbound messages (block replies).
**Controls:**
- `agent.blockStreamingDefault`: `"on"`/`"off"` (default on).
- `agent.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agent.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- `agents.defaults.blockStreamingDefault`: `"on"`/`"off"` (default off).
- Provider overrides: `*.blockStreaming` (and per-account variants) to force `"on"`/`"off"` per provider.
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- `agents.defaults.blockStreamingCoalesce`: `{ minChars?, maxChars?, idleMs? }` (merge streamed blocks before send).
- Provider hard cap: `*.textChunkLimit` (e.g., `whatsapp.textChunkLimit`).
- Discord soft cap: `discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
@@ -54,13 +56,32 @@ Block chunking is implemented by `EmbeddedBlockChunker`:
`maxChars` is clamped to the provider `textChunkLimit`, so you can’t exceed per-provider caps.
## Coalescing (merge streamed blocks)
When block streaming is enabled, Clawdbot can **merge consecutive block chunks**
before sending them out. This reduces “single-line spam” while still providing
progressive output.
- Coalescing waits for **idle gaps** (`idleMs`) before flushing.
- Buffers are capped by `maxChars` and will flush if they exceed it.
- `minChars` prevents tiny fragments from sending until enough text accumulates
(final flush always sends remaining text).
- Joiner is derived from `blockStreamingChunk.breakPreference`
(`paragraph` → `\n\n`, `newline` → `\n`, `sentence` → space).
- Provider overrides are available via `*.blockStreamingCoalesce` (including per-account configs).
- Default coalesce `minChars` is bumped to 1500 for Signal/Slack/Discord unless overridden.
## “Stream chunks or everything”
This maps to:
- **Stream chunks:** `blockStreamingDefault: "on"` + `blockStreamingBreak: "text_end"` (emit as you go).
- **Stream chunks:** `blockStreamingDefault: "on"` + `blockStreamingBreak: "text_end"` (emit as you go). Non-Telegram providers also need `*.blockStreaming: true`.
- **Stream everything at end:** `blockStreamingBreak: "message_end"` (flush once, possibly multiple chunks if very long).
- **No block streaming:** `blockStreamingDefault: "off"` (only final reply).
**Provider note:** For non-Telegram providers, block streaming is **off unless**
`*.blockStreaming` is explicitly set to `true`. Telegram can stream drafts
(`telegram.streamMode`) without block replies.
## Telegram draft streaming (token-ish)
Telegram is the only provider with draft streaming:
@@ -69,6 +90,7 @@ Telegram is the only provider with draft streaming:
- `partial`: draft updates with the latest stream text.
- `block`: draft updates in chunked blocks (same chunker rules).
- `off`: no draft streaming.
- Draft streaming is separate from block streaming; block replies are off by default and only enabled by `*.blockStreaming: true` on non-Telegram providers.
- Final reply is still a normal message.
- `/reasoning stream` writes reasoning into the draft bubble (Telegram only).
+20 -16
View File
@@ -1,23 +1,23 @@
---
summary: "What the ClaudeBot system prompt contains and how it is assembled"
summary: "What the Clawdbot system prompt contains and how it is assembled"
read_when:
- Editing system prompt text, tools list, or time/heartbeat sections
- Changing workspace bootstrap or skills injection behavior
---
# System Prompt
ClaudeBot builds a custom system prompt for every agent run. The prompt is **Clawdbot-owned** and does not use the p-coding-agent default prompt.
Clawdbot builds a custom system prompt for every agent run. The prompt is **Clawdbot-owned** and does not use the p-coding-agent default prompt.
The prompt is assembled in `src/agents/system-prompt.ts` and injected by `src/agents/pi-embedded-runner.ts`.
The prompt is assembled by Clawdbot and injected into each agent run.
## Structure
The prompt is intentionally compact and uses fixed sections:
- **Tooling**: current tool list + short descriptions.
- **Skills**: tells the model how to load skill instructions on demand.
- **ClaudeBot Self-Update**: how to run `config.apply` and `update.run`.
- **Workspace**: working directory (`agent.workspace`).
- **Skills** (when available): tells the model how to load skill instructions on demand.
- **Clawdbot Self-Update**: how to run `config.apply` and `update.run`.
- **Workspace**: working directory (`agents.defaults.workspace`).
- **Workspace Files (injected)**: indicates bootstrap files are included below.
- **Time**: UTC default + the user’s local time (already converted).
- **Reply Tags**: optional reply tag syntax for supported providers.
@@ -43,22 +43,26 @@ Large files are truncated with a marker. Missing files inject a short missing-fi
The Time line is compact and explicit:
- Assume timestamps are **UTC** unless stated.
- The listed **user time** is already converted to `agent.userTimezone` (if set).
- The listed **user time** is already converted to `agents.defaults.userTimezone` (if set).
Use `agent.userTimezone` in `~/.clawdbot/clawdbot.json` to change the user time zone.
Use `agents.defaults.userTimezone` in `~/.clawdbot/clawdbot.json` to change the user time zone.
## Skills
Skills are **not** auto-injected. Instead, the prompt instructs the model to use `read` to load skill instructions on demand:
When eligible skills exist, Clawdbot injects a compact **available skills list**
(`formatSkillsForPrompt`) that includes the **file path** for each skill. The
prompt instructs the model to use `read` to load the SKILL.md at the listed
location (workspace, managed, or bundled). If no skills are eligible, the
Skills section is omitted.
```
<workspace>/skills/<name>/SKILL.md
<available_skills>
<skill>
<name>...</name>
<description>...</description>
<location>...</location>
</skill>
</available_skills>
```
This keeps the base prompt small while still enabling targeted skill usage.
## Code references
- Prompt text: `src/agents/system-prompt.ts`
- Prompt assembly + injection: `src/agents/pi-embedded-runner.ts`
- Bootstrap trimming: `src/agents/pi-embedded-helpers.ts`
+1 -1
View File
@@ -26,7 +26,7 @@ These are typically UTC ISO strings (Discord) or UTC epoch strings (Slack). We d
## User timezone for the system prompt
Set `agent.userTimezone` to tell the model the user's local time zone. If it is
Set `agents.defaults.userTimezone` to tell the model the user's local time zone. If it is
unset, Clawdbot resolves the **host timezone at runtime** (no config write).
```json5
+20 -26
View File
@@ -3,40 +3,34 @@ summary: "TypeBox schemas as the single source of truth for the gateway protocol
read_when:
- Updating protocol schemas or codegen
---
# TypeBox as Protocol Source of Truth
# TypeBox as protocol source of truth
Last updated: 2025-12-09
Last updated: 2026-01-08
We use TypeBox schemas in [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/schema.ts) as the single source of truth for the Gateway control plane (connect/req/res/event frames and payloads). All derived artifacts should be generated from these schemas, not edited by hand.
TypeBox schemas define the Gateway control plane (connect/req/res/event frames and
payloads). All generated artifacts must come from these schemas.
## Current pipeline
- **TypeBox → JSON Schema**: `pnpm protocol:gen` writes [`dist/protocol.schema.json`](https://github.com/clawdbot/clawdbot/blob/main/dist/protocol.schema.json) (draft-07) and runs AJV in the server tests.
- **TypeBox → Swift**: `pnpm protocol:gen:swift` generates [`apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift).
- `pnpm protocol:gen`
- writes the JSON Schema output (draft‑07)
- `pnpm protocol:gen:swift`
- generates Swift gateway models
- `pnpm protocol:check`
- runs both generators and verifies the output is committed
## Problem
## Swift codegen behavior
- We want strong typing in Swift, including a sealed `GatewayFrame` enum with a discriminator and a forward-compatible `unknown` case.
The Swift generator emits:
## Preferred plan (next step)
- `GatewayFrame` enum with `req`, `res`, `event`, and `unknown` cases
- Strongly typed payload structs/enums
- `ErrorCode` values and `GATEWAY_PROTOCOL_VERSION`
- Add a small, custom Swift generator driven directly by the TypeBox schemas:
- Emit a sealed `enum GatewayFrame: Codable { case req(RequestFrame), res(ResponseFrame), event(EventFrame) }`.
- Emit strongly typed payload structs/enums (`ConnectParams`, `HelloOk`, `RequestFrame`, `ResponseFrame`, `EventFrame`, `PresenceEntry`, `Snapshot`, `StateVersion`, `ErrorShape`, `AgentEvent`, `TickEvent`, `ShutdownEvent`, `SendParams`, `AgentParams`, `ErrorCode`, `PROTOCOL_VERSION`).
- Custom `init(from:)` / `encode(to:)` enforces the `type` discriminator and can include an `unknown` case for forward compatibility.
- Wire a new script (e.g., `pnpm protocol:gen:swift`) into `protocol:check` so CI fails if the generated Swift is stale.
Unknown frame types are preserved as raw payloads for forward compatibility.
Why this path:
- Single source of truth stays TypeBox; no new IDL to maintain.
- Predictable, strongly typed Swift (no optional soup).
- Small deterministic codegen (~150–200 LOC script) we control.
## When you change schemas
## Alternative (if we want off-the-shelf codegen)
- Wrap the existing JSON Schema into an OpenAPI 3.1 doc (auto-generated) and use **swift-openapi-generator** or **openapi-generator swift5**. More moving parts, but also yields enums with discriminator support. Keep this as a fallback if we don’t want a custom emitter.
## Action items
- Implement `protocol:gen:swift` that reads the TypeBox schemas and emits the sealed Swift enum + payload structs.
- Update `protocol:check` to include the Swift generator output in the diff check.
- Remove quicktype output once the custom generator is in place (or keep it for docs only).
1) Update the TypeBox schemas.
2) Run `pnpm protocol:check`.
3) Commit the regenerated schema + Swift models.
+3 -3
View File
@@ -6,18 +6,18 @@ read_when:
# Typing indicators
Typing indicators are sent to the chat provider while a run is active. Use
`agent.typingMode` to control **when** typing starts and `typingIntervalSeconds`
`agents.defaults.typingMode` to control **when** typing starts and `typingIntervalSeconds`
to control **how often** it refreshes.
## Defaults
When `agent.typingMode` is **unset**, Clawdbot keeps the legacy behavior:
When `agents.defaults.typingMode` is **unset**, Clawdbot keeps the legacy behavior:
- **Direct chats**: typing starts immediately once the model loop begins.
- **Group chats with a mention**: typing starts immediately.
- **Group chats without a mention**: typing starts only when message text begins streaming.
- **Heartbeat runs**: typing is disabled.
## Modes
Set `agent.typingMode` to one of:
Set `agents.defaults.typingMode` to one of:
- `never` — no typing indicator, ever.
- `instant` — start typing **as soon as the model loop begins**, even if the run
later returns only the silent reply token.
+2 -1
View File
@@ -11,7 +11,8 @@ read_when:
- No estimated costs; only the provider-reported windows.
## Where it shows up
- `/status` in chats: adds a short “Usage” line (only if available).
- `/status` in chats: emoji‑rich status card with session tokens + estimated cost (API key only) and provider quota windows when available.
- `/cost on|off` in chats: toggles per‑response usage lines (OAuth shows tokens only).
- CLI: `clawdbot status --usage` prints a full per-provider breakdown.
- CLI: `clawdbot providers list` prints the same usage snapshot alongside provider config (use `--no-usage` to skip).
- macOS menu bar: “Usage” section under Context (only if available).
+150
View File
@@ -0,0 +1,150 @@
---
summary: "Debugging tools: watch mode, raw model streams, and tracing reasoning leakage"
read_when:
- You need to inspect raw model output for reasoning leakage
- You want to run the Gateway in watch mode while iterating
- You need a repeatable debugging workflow
---
# Debugging
This page covers debugging helpers for streaming output, especially when a
provider mixes reasoning into normal text.
## Runtime debug overrides
Use `/debug` in chat to set **runtime-only** config overrides (memory, not disk).
This is handy when you need to toggle obscure settings without editing `clawdbot.json`.
Examples:
```
/debug show
/debug set messages.responsePrefix="[clawdbot]"
/debug unset messages.responsePrefix
/debug reset
```
`/debug reset` clears all overrides and returns to the on-disk config.
## Gateway watch mode
For fast iteration, run the gateway under the file watcher:
```bash
pnpm gateway:watch --force
```
This maps to:
```bash
tsx watch src/entry.ts gateway --force
```
Add any gateway CLI flags after `gateway:watch` and they will be passed through
on each restart.
## Dev profile + dev gateway (--dev)
Use the dev profile to isolate state and spin up a safe, disposable setup for
debugging. There are **two** `--dev` flags:
- **Global `--dev` (profile):** isolates state under `~/.clawdbot-dev` and
defaults the gateway port to `19001` (derived ports shift with it).
- **`gateway --dev`: tells the Gateway to auto-create a default config +
workspace** when missing (and skip BOOTSTRAP.md).
Recommended flow:
```bash
pnpm clawdbot --dev gateway --dev
pnpm clawdbot --dev tui
```
What this does:
1) **Profile isolation** (global `--dev`)
- `CLAWDBOT_PROFILE=dev`
- `CLAWDBOT_STATE_DIR=~/.clawdbot-dev`
- `CLAWDBOT_CONFIG_PATH=~/.clawdbot-dev/clawdbot.json`
- `CLAWDBOT_GATEWAY_PORT=19001` (bridge/canvas/browser shift accordingly)
2) **Dev bootstrap** (`gateway --dev`)
- Writes a minimal config if missing (`gateway.mode=local`, bind loopback).
- Sets `agent.workspace` to the dev workspace.
- Sets `agent.skipBootstrap=true` (no BOOTSTRAP.md).
- Seeds the workspace files if missing:
`AGENTS.md`, `SOUL.md`, `TOOLS.md`, `IDENTITY.md`, `USER.md`, `HEARTBEAT.md`.
- Default identity: **C3‑PO** (protocol droid).
Reset flow (fresh start):
```bash
pnpm clawdbot --dev gateway --dev --reset
```
`--reset` wipes config, credentials, sessions, and the dev workspace (using
`trash`, not `rm`), then recreates the default dev setup.
Tip: if a non‑dev gateway is already running (launchd/systemd), stop it first:
```bash
clawdbot daemon stop
```
## Raw stream logging (Clawdbot)
Clawdbot can log the **raw assistant stream** before any filtering/formatting.
This is the best way to see whether reasoning is arriving as plain text deltas
(or as separate thinking blocks).
Enable it via CLI:
```bash
pnpm gateway:watch --force --raw-stream
```
Optional path override:
```bash
pnpm gateway:watch --force --raw-stream --raw-stream-path ~/.clawdbot/logs/raw-stream.jsonl
```
Equivalent env vars:
```bash
CLAWDBOT_RAW_STREAM=1
CLAWDBOT_RAW_STREAM_PATH=~/.clawdbot/logs/raw-stream.jsonl
```
Default file:
`~/.clawdbot/logs/raw-stream.jsonl`
## Raw chunk logging (pi-mono)
To capture **raw OpenAI-compat chunks** before they are parsed into blocks,
pi-mono exposes a separate logger:
```bash
PI_RAW_STREAM=1
```
Optional path:
```bash
PI_RAW_STREAM_PATH=~/.pi-mono/logs/raw-openai-completions.jsonl
```
Default file:
`~/.pi-mono/logs/raw-openai-completions.jsonl`
> Note: this is only emitted by processes using pi-mono’s
> `openai-completions` provider.
## Safety notes
- Raw stream logs can include full prompts, tool output, and user data.
- Keep logs local and delete them after debugging.
- If you share logs, scrub secrets and PII first.
Loaded 100 of 743 files, more files were not shown because too many files have changed in this diff. Show more