Compare commits

..
Author SHA1 Message Date
Peter Steinberger d3c53f4596 fix: stabilize CI lint/tests (#592) 2026-01-09 18:35:52 +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 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
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
615 changed files with 43389 additions and 10375 deletions

No files matched your search

+3 -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
@@ -412,7 +412,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/
+11 -4
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,8 @@
- 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.
- 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,6 +83,10 @@
- **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.
- 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/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.
@@ -87,17 +94,17 @@
- 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
)"
+124 -2
View File
@@ -2,7 +2,129 @@
## Unreleased
- (nothing yet)
- 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
- CLI: add `sandbox list` and `sandbox recreate` commands for managing Docker sandbox containers after image/config updates. (#563) — thanks @pasogott
- 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
- 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
- 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
- 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
- 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
- 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.
- Agent: enable adaptive context pruning by default for tool-result trimming.
- 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
- Doctor: run legacy state migrations in non-interactive mode without prompts.
- Cron: parse Telegram topic targets for isolated delivery. (#478) — thanks @nachoiacovino
- 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
- 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: fix /status usage summary filtering for the active provider.
- 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
- 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: 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
- 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
- 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
- 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
## 2026.1.8
@@ -19,7 +141,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.
+47 -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,21 @@ 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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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=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/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></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 {
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>
+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

@@ -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,66 @@ 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)
self.logger.warning(
"Node bridge heartbeat timed out; disconnecting (age: \(String(describing: age), privacy: .public)).")
await self.disconnect()
return
}
}
let id = UUID().uuidString
do {
try await self.send(BridgePing(type: "ping", id: id))
} catch {
self.logger.warning(
"Node bridge ping send failed; disconnecting (error: \(String(describing: error), privacy: .public)).")
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):
self.logger.warning(
"Node bridge connection failed; disconnecting (error: \(String(describing: error), privacy: .public)).")
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,19 @@
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)
}
}
}
@@ -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
@@ -22,7 +22,7 @@ defaults:
nav:
- title: "Home"
url: "/"
- title: "Clawd Setup"
- title: "Clawdbot Assistant"
url: "/start/clawd/"
- title: "Gateway"
url: "/gateway/"
+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.
+6 -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,9 @@ 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 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 +171,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' \
+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'
```
+75 -40
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,15 @@ 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|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>`
- `--gateway-port <port>`
- `--gateway-bind <loopback|lan|tailnet|auto>`
- `--gateway-auth <off|token|password>`
@@ -202,8 +220,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 +246,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 +300,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 +381,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 +422,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 +451,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 +480,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 +502,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 +672,6 @@ Options:
- `--session <key>`
- `--deliver`
- `--thinking <level>`
- `--message <text>`
- `--timeout-ms <ms>`
- `--history-limit <n>`
+176
View File
@@ -0,0 +1,176 @@
---
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 ok)
- 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`
- `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 "✅"
```
+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`.
+11 -23
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:
@@ -96,9 +84,9 @@ 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
via `agents.defaults.blockStreamingDefault: "off"` if you only want the final response.
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).
Verbose tool summaries are emitted at tool start (no debounce); Control UI
streams tool output via agent events when available.
@@ -107,7 +95,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).
+6 -4
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)
@@ -80,14 +82,14 @@ State is stored in `auth-profiles.json` under `usageStats`:
## 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
`agents.defaults.model.fallbacks`. This applies to auth failures, rate limits, and
timeouts that exhausted profile rotation.
## Related config
See [`docs/configuration.md`](/gateway/configuration) for:
- `auth.profiles` / `auth.order`
- `agent.model.primary` / `agent.model.fallbacks`
- `agent.imageModel` routing
- `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)
+88 -18
View File
@@ -3,24 +3,94 @@ 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.
## 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
{
+10 -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.
@@ -132,19 +132,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 +155,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
+3 -3
View File
@@ -32,9 +32,9 @@ 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 on).
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- Provider hard cap: `*.textChunkLimit` (e.g., `whatsapp.textChunkLimit`).
- Discord soft cap: `discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
+7 -13
View File
@@ -1,14 +1,14 @@
---
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
@@ -16,8 +16,8 @@ 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`).
- **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,9 +43,9 @@ 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
@@ -56,9 +56,3 @@ Skills are **not** auto-injected. Instead, the prompt instructs the model to use
```
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.
+29 -3
View File
@@ -97,6 +97,14 @@
"source": "/bun",
"destination": "/install/bun"
},
{
"source": "/auth-monitoring",
"destination": "/automation/auth-monitoring"
},
{
"source": "/scripts",
"destination": "/scripts"
},
{
"source": "/camera",
"destination": "/nodes/camera"
@@ -474,8 +482,8 @@
"destination": "/gateway/troubleshooting"
},
{
"source": "/tui",
"destination": "/web/tui"
"source": "/web/tui",
"destination": "/tui"
},
{
"source": "/typebox",
@@ -508,6 +516,10 @@
{
"source": "/wizard",
"destination": "/start/wizard"
},
{
"source": "/oauth",
"destination": "/concepts/oauth"
}
],
"navigation": {
@@ -537,6 +549,14 @@
"install/bun"
]
},
{
"group": "CLI",
"pages": [
"cli/index",
"cli/gateway",
"cli/sandbox"
]
},
{
"group": "Core Concepts",
"pages": [
@@ -544,6 +564,8 @@
"concepts/agent",
"concepts/agent-loop",
"concepts/system-prompt",
"token-use",
"concepts/oauth",
"concepts/agent-workspace",
"concepts/multi-agent",
"concepts/compaction",
@@ -571,6 +593,7 @@
"gateway/gateway-lock",
"gateway/configuration",
"gateway/configuration-examples",
"gateway/authentication",
"gateway/background-process",
"gateway/health",
"gateway/heartbeat",
@@ -592,7 +615,7 @@
"web/control-ui",
"web/dashboard",
"web/webchat",
"web/tui"
"tui"
]
},
{
@@ -611,6 +634,7 @@
{
"group": "Automation & Hooks",
"pages": [
"automation/auth-monitoring",
"automation/webhook",
"automation/gmail-pubsub",
"automation/cron-jobs",
@@ -655,6 +679,7 @@
"platforms/android",
"platforms/windows",
"platforms/linux",
"platforms/hetzner",
"platforms/exe-dev"
]
},
@@ -684,6 +709,7 @@
{
"group": "Reference & Templates",
"pages": [
"scripts",
"reference/rpc",
"reference/device-models",
"reference/test",
+60
View File
@@ -0,0 +1,60 @@
---
summary: "Where Clawdbot loads environment variables and the precedence order"
read_when:
- You need to know which env vars are loaded, and in what order
- You are debugging missing API keys in the Gateway
- You are documenting provider auth or deployment environments
---
# Environment variables
Clawdbot pulls environment variables from multiple sources. The rule is **never override existing values**.
## Precedence (highest → lowest)
1) **Process environment** (what the Gateway process already has from the parent shell/daemon).
2) **`.env` in the current working directory** (dotenv default; does not override).
3) **Global `.env`** at `~/.clawdbot/.env` (aka `$CLAWDBOT_STATE_DIR/.env`; does not override).
4) **Config `env` block** in `~/.clawdbot/clawdbot.json` (applied only if missing).
5) **Optional login-shell import** (`env.shellEnv.enabled` or `CLAWDBOT_LOAD_SHELL_ENV=1`), applied only for missing expected keys.
If the config file is missing entirely, step 4 is skipped; shell import still runs if enabled.
## Config `env` block
Two equivalent ways to set inline env vars (both are non-overriding):
```json5
{
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-..."
}
}
}
```
## Shell env import
`env.shellEnv` runs your login shell and imports only **missing** expected keys:
```json5
{
env: {
shellEnv: {
enabled: true,
timeoutMs: 15000
}
}
}
```
Env var equivalents:
- `CLAWDBOT_LOAD_SHELL_ENV=1`
- `CLAWDBOT_SHELL_ENV_TIMEOUT_MS=15000`
## Related
- [Gateway configuration](/gateway/configuration)
- [FAQ: env vars and .env loading](/start/faq#env-vars-and-env-loading)
- [Models overview](/concepts/models)
@@ -8,11 +8,11 @@ read_when: "Changing onboarding wizard steps or config schema endpoints"
Purpose: shared onboarding + config surfaces across CLI, macOS app, and Web UI.
## Components
- Wizard engine: `src/wizard` (session + prompts + onboarding state).
- CLI: [`src/commands/onboard-*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/onboard-*.ts) uses the wizard with the CLI prompter.
- Gateway RPC: wizard + config schema endpoints serve UI clients.
- macOS: SwiftUI onboarding uses the wizard step model.
- Web UI: config form renders from JSON Schema + hints.
- Wizard engine (shared session + prompts + onboarding state).
- CLI onboarding uses the same wizard flow as the UI clients.
- Gateway RPC exposes wizard + config schema endpoints.
- macOS onboarding uses the wizard step model.
- Web UI renders config forms from JSON Schema + UI hints.
## Gateway RPC
- `wizard.start` params: `{ mode?: "local"|"remote", workspace?: string }`
+13 -28
View File
@@ -28,44 +28,29 @@ Recent gateway logs show repeated `cron.add` failures with invalid parameters (m
- Agent cron tool schema allows arbitrary `job` objects, enabling malformed inputs.
- Gateway strictly validates `cron.add` with no normalization, so wrapped payloads fail.
## Proposed Approach
1. **Normalize** incoming `cron.add` payloads (unwrap `data`/`job`, infer `schedule.kind` and `payload.kind`, default `wakeMode` + `sessionTarget` when safe).
2. **Harden** the agent cron tool schema using the canonical gateway `CronAddParamsSchema` and normalize before sending to the gateway.
3. **Align** provider enums and cron status fields across gateway schema, TS types, CLI descriptions, and UI form controls.
4. **Test** normalization in gateway tests and tool behavior in agent tests.
## What changed
## Multi-phase Execution Plan
- `cron.add` and `cron.update` now normalize common wrapper shapes and infer missing `kind` fields.
- Agent cron tool schema matches the gateway schema, which reduces invalid payloads.
- Provider enums are aligned across gateway, CLI, UI, and macOS picker.
- Control UI uses the gateway’s `jobs` count field for status.
### Phase 1 — Schema + type alignment
- [x] Expand gateway `CronPayloadSchema` provider enum to include `signal` and `imessage`.
- [x] Update CLI `--provider` descriptions to include `slack` (already supported by gateway).
- [x] Update UI Cron payload/provider union types to include all supported providers.
- [x] Fix UI CronStatus type to match gateway (`jobs` instead of `jobCount`).
- [x] Update cron UI provider select to include Discord/Slack/Signal/iMessage.
- [x] Update macOS CronJobEditor provider picker + enum to include Slack/Signal/iMessage.
- [x] Document cron compatibility normalization policy in [`docs/cron-jobs.md`](/automation/cron-jobs).
## Current behavior
### Phase 2 — Input normalization + tooling hardening
- [x] Add shared cron input normalization helpers (`normalizeCronJobCreate`/`normalizeCronJobPatch`).
- [x] Apply normalization in gateway `cron.add` (and patch normalization in `cron.update`).
- [x] Tighten agent cron tool schema to `CronAddParamsSchema` and normalize job/patch before sending.
- **Normalization:** wrapped `data`/`job` payloads are unwrapped; `schedule.kind` and `payload.kind` are inferred when safe.
- **Defaults:** safe defaults are applied for `wakeMode` and `sessionTarget` when missing.
- **Providers:** Discord/Slack/Signal/iMessage are now consistently surfaced across CLI/UI.
### Phase 3 — Tests
- [x] Add gateway test covering wrapped `cron.add` payload normalization.
- [x] Add cron tool test to assert normalization and defaulting for `cron.add`.
- [x] Add gateway test covering `cron.update` normalization.
- [x] Add UI + Swift conformance test for cron channels + status fields.
See [`docs/cron-jobs.md`](/automation/cron-jobs) for the normalized shape and examples.
### Phase 4 — Verification
- [x] Run tests (full suite executed via `pnpm test -- cron-tool`).
## Verification
## Rollout/Monitoring
- Watch gateway logs for reduced `cron.add` INVALID_REQUEST errors.
- Confirm Control UI cron status shows job count after refresh.
- If errors persist, extend normalization for additional common shapes (e.g., `schedule.at`, `payload.message` without `kind`).
## Optional Follow-ups
- Manual Control UI smoke: add cron job per provider + verify status job count.
- Manual Control UI smoke: add a cron job per provider + verify status job count.
## Open Questions
- Should `cron.add` accept explicit `state` from clients (currently disallowed by schema)?
+21 -109
View File
@@ -1,126 +1,38 @@
---
summary: "Spec: groupPolicy hardening for Telegram allowlist parity"
summary: "Telegram allowlist hardening: prefix + whitespace normalization"
read_when:
- Reviewing historical Telegram allowlist normalization changes
- Reviewing historical Telegram allowlist changes
---
# Engineering Execution Spec: groupPolicy Hardening (Telegram Allowlist Parity)
# Telegram Allowlist Hardening
**Date**: 2026-01-05
**Status**: Complete
**PR**: #216 (feat/whatsapp-group-policy)
**PR**: #216
---
## Summary
## Executive Summary
Telegram allowlists now accept `telegram:` and `tg:` prefixes case-insensitively, and tolerate
accidental whitespace. This aligns inbound allowlist checks with outbound send normalization.
Follow-up hardening work ensures Telegram allowlists behave consistently across inbound group/DM filtering and outbound send normalization. The focus is on prefix parity (`telegram:` / `tg:`), case-insensitive matching for prefixes, and resilience to accidental whitespace in config entries. Documentation and tests were updated to reflect and lock in this behavior.
## What changed
---
- Prefixes `telegram:` and `tg:` are treated the same (case-insensitive).
- Allowlist entries are trimmed; empty entries are ignored.
## Findings Analysis
## Examples
### [MED] F1: Telegram Allowlist Prefix Handling Is Case-Sensitive and Excludes `tg:`
All of these are accepted for the same ID:
**Location**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
- `telegram:123456`
- `TG:123456`
- ` tg:123456 `
**Problem**: Inbound allowlist normalization only stripped a lowercase `telegram:` prefix. This rejected `TG:123` / `Telegram:123` and did not accept the `tg:` shorthand even though outbound send normalization already accepts `tg:` and case-insensitive prefixes.
## Why it matters
**Impact**:
- DMs and group allowlists fail when users copy/paste prefixed IDs from logs or existing send format.
- Behavior is inconsistent between inbound filtering and outbound send normalization.
Copy/paste from logs or chat IDs often includes prefixes and whitespace. Normalizing avoids
false negatives when deciding whether to respond in DMs or groups.
**Fix**: Normalize allowlist entries by trimming whitespace and stripping `telegram:` / `tg:` prefixes case-insensitively at pre-compute time.
## Related docs
---
### [LOW] F2: Allowlist Entries Are Not Trimmed
**Location**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
**Problem**: Allowlist entries are not trimmed; accidental whitespace causes mismatches.
**Fix**: Trim and drop empty entries while normalizing allowlist inputs.
---
## Implementation Phases
### Phase 1: Normalize Telegram Allowlist Inputs
**File**: [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts)
**Changes**:
1. Trim allowlist entries and drop empty values.
2. Strip `telegram:` / `tg:` prefixes case-insensitively.
3. Simplify DM allowlist check to rely on normalized values.
---
### Phase 2: Add Coverage for Prefix + Whitespace
**File**: [`src/telegram/bot.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.test.ts)
**Add Tests**:
- DM allowlist accepts `TG:` prefix with surrounding whitespace.
- Group allowlist accepts `TG:` prefix case-insensitively.
---
### Phase 3: Documentation Updates
**Files**:
- [`docs/groups.md`](/concepts/groups)
- [`docs/telegram.md`](/providers/telegram)
**Changes**:
- Document `tg:` alias and case-insensitive prefixes for Telegram allowlists.
---
### Phase 4: Verification
1. Run targeted Telegram tests (`pnpm test -- src/telegram/bot.test.ts`).
2. If time allows, run full suite (`pnpm test`).
---
## Files Modified
| File | Change Type | Description |
|------|-------------|-------------|
| [`src/telegram/bot.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.ts) | Fix | Trim allowlist values; strip `telegram:` / `tg:` prefixes case-insensitively |
| [`src/telegram/bot.test.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/telegram/bot.test.ts) | Test | Add DM + group allowlist coverage for `TG:` prefix + whitespace |
| [`docs/groups.md`](/concepts/groups) | Docs | Mention `tg:` alias + case-insensitive prefixes |
| [`docs/telegram.md`](/providers/telegram) | Docs | Mention `tg:` alias + case-insensitive prefixes |
---
## Success Criteria
- [x] Telegram allowlist accepts `telegram:` / `tg:` prefixes case-insensitively.
- [x] Telegram allowlist tolerates whitespace in config entries.
- [x] DM and group allowlist tests cover prefixed cases.
- [x] Docs updated to reflect allowlist formats.
- [x] Targeted tests pass.
- [x] Full test suite passes.
---
## Risk Assessment
| Risk | Severity | Mitigation |
|------|----------|------------|
| Behavior change for malformed entries | Low | Normalization is additive and trims only whitespace |
| Test fragility | Low | Isolated unit tests; no external dependencies |
| Doc drift | Low | Updated docs alongside code |
---
## Estimated Complexity
- **Phase 1**: Low (normalization helpers)
- **Phase 2**: Low (2 new tests)
- **Phase 3**: Low (doc edits)
- **Phase 4**: Low (verification)
**Total**: ~20 minutes
- [Group Chats](/concepts/groups)
- [Telegram Provider](/providers/telegram)
+22 -137
View File
@@ -1,147 +1,32 @@
---
summary: "Proposal: model config, auth profiles, and fallback behavior"
summary: "Exploration: model config, auth profiles, and fallback behavior"
read_when:
- Designing model selection, auth profiles, or fallback behavior
- Migrating model config schema
- Exploring future model selection + auth profile ideas
---
# Model Config (Exploration)
# Model config proposal
This document captures **ideas** for future model configuration. It is not a
shipping spec. For current behavior, see:
- [Models](/concepts/models)
- [Model failover](/concepts/model-failover)
- [OAuth + profiles](/concepts/oauth)
Goals
- Multi OAuth + multi API key per provider
- Model selection via `/model` with sensible fallback
- Global (not per-session) fallback logic
- Keep last-known-good auth profile when switching models
- Profile override only when explicitly requested
- Image routing override only when explicitly configured
## Motivation
Non-goals (v1)
- Auto-discovery of provider capabilities beyond catalog input tags
- Per-model auth profile order (see open questions)
Operators want:
- Multiple auth profiles per provider (personal vs work).
- Simple `/model` selection with predictable fallbacks.
- Clear separation between text models and image-capable models.
## Proposed config shape
## Possible direction (high level)
```json
{
"auth": {
"profiles": {
"anthropic:default": {
"provider": "anthropic",
"mode": "oauth"
},
"anthropic:work": {
"provider": "anthropic",
"mode": "api_key"
},
"openai:default": {
"provider": "openai",
"mode": "oauth"
}
},
"order": {
"anthropic": ["anthropic:default", "anthropic:work"],
"openai": ["openai:default"]
}
},
"agent": {
"models": {
"anthropic/claude-opus-4-5": {
"alias": "Opus"
},
"openai/gpt-5.2": {
"alias": "gpt52"
}
},
"model": {
"primary": "anthropic/claude-opus-4-5",
"fallbacks": ["openai/gpt-5.2"]
},
"imageModel": {
"primary": "openai/gpt-5.2",
"fallbacks": ["anthropic/claude-opus-4-5"]
}
}
}
```
- Keep model selection simple: `provider/model` with optional aliases.
- Let providers have multiple auth profiles, with an explicit order.
- Use a global fallback list so all sessions fail over consistently.
- Only override image routing when explicitly configured.
Notes
- Canonical model keys are full `provider/model`.
- `alias` optional; used by `/model` resolution.
- `auth.profiles` is keyed. Default CLI login creates `provider:default`.
- `auth.order[provider]` controls rotation order for that provider.
## Open questions
## CLI / UX
Login
- `clawdbot login anthropic` → create/replace `anthropic:default`.
- `clawdbot login anthropic --profile work` → create/replace `anthropic:work`.
Model selection
- `/model Opus` → resolve alias to full id.
- `/model anthropic/claude-opus-4-5` → explicit.
- Optional: `/model Opus@anthropic:work` (explicit profile override for session only).
Model listing
- `/model` list shows:
- model id
- alias
- provider
- auth order (from `auth.order`)
- auth source for the current provider (auth-profiles.json/env/shell env/models.json)
## Fallback behavior (global)
Fallback list
- Use `agent.model.fallbacks` globally.
- No per-session fallback list; last-known-good is per-session but uses global ordering.
Auth profile rotation
- If provider auth error (401/403/invalid refresh):
- advance to next profile in `auth.order[provider]`.
- if all fail, fall back to next model.
Rate limiting
- If rate limit / quota error:
- rotate auth profile first (same provider)
- if still failing, fall back to next model.
Model not found / capability mismatch
- immediate model fallback.
## Image routing
Rule
- Only use `agent.imageModel` when explicitly configured.
- If `agent.imageModel` is configured and the current text model lacks image input, use it.
Support detection
- From model catalog `input` tags when available (e.g. `image` in models.json).
- If unknown: treat as text-only and use `agent.imageModel`.
## Migration (doctor + gateway auto-run)
Inputs
- Legacy keys (pre-migration):
- `agent.model` (string)
- `agent.modelFallbacks` (string[])
- `agent.imageModel` (string)
- `agent.imageModelFallbacks` (string[])
- `agent.allowedModels` (string[])
- `agent.modelAliases` (record)
Outputs
- `agent.models` map with keys for all referenced models
- `agent.model.primary/fallbacks`
- `agent.imageModel.primary/fallbacks`
- Auth profile store seeded from current auth-profiles.json/auth.json + oauth.json + env (as `provider:default`)
- `auth.order` seeded with `["provider:default"]` when config is updated
Auto-run
- Gateway start detects legacy keys and runs doctor migration.
## Decisions
- Auth order is per-provider (`auth.order`).
- Doctor migration is required; gateway will auto-run on startup when legacy keys detected.
- `/model Opus@profile` is explicit session override only.
- Image routing override only when `agent.imageModel` is explicitly configured.
- Should profile rotation be per-provider or per-model?
- How should the UI surface profile selection for a session?
- What is the safest migration path from legacy config keys?
+10 -27
View File
@@ -1,14 +1,14 @@
---
summary: "Proposal + research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)"
summary: "Research notes: offline memory system for Clawd workspaces (Markdown source-of-truth + derived index)"
read_when:
- Designing workspace memory (~/clawd) beyond daily Markdown logs
- Deciding: standalone CLI vs deep Clawdbot integration
- Adding offline recall + reflection (retain/recall/reflect)
---
# Workspace Memory v2 (offline): proposal + research
# Workspace Memory v2 (offline): research notes
Target: Clawd-style workspace (`agent.workspace`, default `~/clawd`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
Target: Clawd-style workspace (`agents.defaults.workspace`, default `~/clawd`) where “memory” is stored as one Markdown file per day (`memory/YYYY-MM-DD.md`) plus a small set of stable files (e.g. `memory.md`, `SOUL.md`).
This doc proposes an **offline-first** memory architecture that keeps Markdown as the canonical, reviewable source of truth, but adds **structured recall** (search, entity summaries, confidence updates) via a derived index.
@@ -159,7 +159,7 @@ Recommendation: **deep integration in Clawdbot**, but keep a separable core libr
### Why integrate into Clawdbot?
- Clawdbot already knows:
- the workspace path (`agent.workspace`)
- the workspace path (`agents.defaults.workspace`)
- the session model + heartbeats
- logging + troubleshooting patterns
- You want the agent itself to call the tools:
@@ -171,8 +171,7 @@ Recommendation: **deep integration in Clawdbot**, but keep a separable core libr
- reuse from other contexts (local scripts, future desktop app, etc.)
Shape:
- `src/memory/*` (library-ish core; pure functions + sqlite adapter)
- [`src/commands/memory/*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/commands/memory/*.ts) (CLI glue)
The memory tooling is intended to be a small CLI + library layer, but this is exploratory only.
## “S-Collide” / SuCo: when to use it (research)
@@ -196,29 +195,13 @@ Open question:
- what’s the **best** offline embedding model for “personal assistant memory” on your machines (MacBook + Castle)?
- if you already have Ollama: embed with a local model; otherwise ship a small embedding model in the toolchain.
## Implementation plan (phased, shippable)
## Smallest useful pilot
### Phase 0: workspace conventions (no code)
- add `bank/` files + entity pages
- add `## Retain` convention to daily logs
If you want a minimal, still-useful version:
### Phase 1: `clawdbot memory index|recall` (FTS-only)
- parse Markdown (`memory/*.md`, `bank/*.md`) into chunks
- write to SQLite: `facts`, `entities`, `fact_entities`, `opinions`
- FTS5 table over `facts.content`
- `recall` returns citations (path + line) + trimmed content budget
### Phase 2: entity summaries + opinion tracking
- `reflect` updates `bank/entities/*.md`
- opinion confidence updates with evidence pointers (no embeddings required yet)
### Phase 3: semantic recall (offline embeddings)
- compute embeddings during indexing (incremental)
- retrieval = `hybrid(FTS, vector)` with simple fusion
### Phase 4: “graph-ish” traversal (still simple)
- entity links enable multi-hop: “related to Peter via warelay”
- optional: “topic” nodes, lightweight edges (not a full KG)
- Add `bank/` entity pages and a `## Retain` section in daily logs.
- Use SQLite FTS for recall with citations (path + line numbers).
- Add embeddings only if recall quality or scale demands it.
## References
+131
View File
@@ -0,0 +1,131 @@
---
summary: "Model authentication: OAuth, API keys, and Claude Code token reuse"
read_when:
- Debugging model auth or OAuth expiry
- Documenting authentication or credential storage
---
# Authentication
Clawdbot supports OAuth and API keys for model providers. For Anthropic
subscription accounts, the most stable path is to **reuse Claude Code OAuth
credentials**, including the 1‑year token created by `claude setup-token`.
See [/concepts/oauth](/concepts/oauth) for the full OAuth flow and storage
layout.
## Preferred Anthropic setup (Claude CLI setup-token)
For Anthropic, the **preferred** path is the Claude CLI setup-token, not an API key.
Run it on the **gateway host**:
```bash
claude setup-token
```
Then verify and sync into Clawdbot:
```bash
clawdbot models status
clawdbot doctor
```
Alternative: run the wrapper (also updates Clawdbot config):
```bash
clawdbot models auth setup-token --provider anthropic
```
Manual token entry (any provider; writes `auth-profiles.json` + updates config):
```bash
clawdbot models auth paste-token --provider anthropic
clawdbot models auth paste-token --provider openrouter
```
## Recommended: long‑lived Claude Code token
Run this on the **gateway host** (the machine running the Gateway):
```bash
claude setup-token
```
This issues a long‑lived **OAuth token** (not an API key) and stores it for
Claude Code. Then sync and verify:
```bash
clawdbot models status
clawdbot doctor
```
Automation-friendly check (exit `1` when expired/missing, `2` when expiring):
```bash
clawdbot models status --check
```
Optional ops scripts (systemd/Termux) are documented here:
[/automation/auth-monitoring](/automation/auth-monitoring)
`clawdbot models status` loads Claude Code credentials into Clawdbot’s
`auth-profiles.json` and shows expiry (warns within 24h by default).
`clawdbot doctor` also performs the sync when it runs.
> `claude setup-token` requires an interactive TTY.
## Checking model auth status
```bash
clawdbot models status
clawdbot doctor
```
## Controlling which credential is used
### Per-session (chat command)
Use `/model <alias-or-id>@<profileId>` to pin a specific provider credential for the current session (example profile ids: `anthropic:claude-cli`, `anthropic:default`). Use `/model status` to see candidates + which one is next.
### Per-agent (CLI override)
Set an explicit auth profile order override for an agent (stored in that agent’s `auth-profiles.json`):
```bash
clawdbot models auth order get --provider anthropic
clawdbot models auth order set --provider anthropic anthropic:claude-cli
clawdbot models auth order clear --provider anthropic
```
Use `--agent <id>` to target a specific agent; omit it to use the configured default agent.
## How sync works
1. **Claude Code** stores credentials in `~/.claude/.credentials.json` (or
Keychain on macOS).
2. **Clawdbot** syncs those into
`~/.clawdbot/agents/<agentId>/agent/auth-profiles.json` when the auth store is
loaded.
3. Refreshable OAuth profiles can be refreshed automatically on use. Static
token profiles (including Claude CLI setup-token) are not refreshable by
Clawdbot.
## Troubleshooting
### “No credentials found”
If the Anthropic token profile is missing, run `claude setup-token` on the
**gateway host**, then re-check:
```bash
clawdbot models status
```
### Token expiring/expired
Run `clawdbot models status` to confirm which profile is expiring. If the profile
is `anthropic:claude-cli`, rerun `claude setup-token`.
## Requirements
- Claude Max or Pro subscription (for `claude setup-token`)
- Claude Code CLI installed (`claude` command available)
+3 -3
View File
@@ -32,9 +32,9 @@ Environment overrides:
- `PI_BASH_JOB_TTL_MS`: TTL for finished sessions (ms, bounded to 1m–3h)
Config (preferred):
- `agent.bash.backgroundMs` (default 10000)
- `agent.bash.timeoutSec` (default 1800)
- `agent.bash.cleanupMs` (default 1800000)
- `tools.bash.backgroundMs` (default 10000)
- `tools.bash.timeoutSec` (default 1800)
- `tools.bash.cleanupMs` (default 1800000)
## process tool
+70 -66
View File
@@ -6,24 +6,29 @@ read_when:
---
# Bonjour / mDNS discovery
Clawdbot uses Bonjour (mDNS / DNS-SD) as a **LAN-only convenience** to discover a running Gateway bridge transport. It is best-effort and does **not** replace SSH or Tailnet-based connectivity.
Clawdbot uses Bonjour (mDNS / DNS‑SD) as a **LAN‑only convenience** to discover
an active Gateway bridge. It is best‑effort and does **not** replace SSH or
Tailnet-based connectivity.
## Wide-Area Bonjour (Unicast DNS-SD) over Tailscale
## Wide‑area Bonjour (Unicast DNS‑SD) over Tailscale
If you want iOS node auto-discovery while the Gateway is on another network (e.g. Vienna ⇄ London), you can keep the `NWBrowser` UX but switch discovery from multicast mDNS (`local.`) to **unicast DNS-SD** (“Wide-Area Bonjour”) over Tailscale.
If the node and gateway are on different networks, multicast mDNS won’t cross the
boundary. You can keep the same discovery UX by switching to **unicast DNS‑SD**
("Wide‑Area Bonjour") over Tailscale.
High level:
High‑level steps:
1) Run a DNS server on the gateway host (reachable via tailnet IP).
2) Publish DNS-SD records for `_clawdbot-bridge._tcp` in a dedicated zone (example: `clawdbot.internal.`).
3) Configure Tailscale **split DNS** so `clawdbot.internal` resolves via that DNS server for clients (including iOS).
1) Run a DNS server on the gateway host (reachable over Tailnet).
2) Publish DNS‑SD records for `_clawdbot-bridge._tcp` under a dedicated zone
(example: `clawdbot.internal.`).
3) Configure Tailscale **split DNS** so `clawdbot.internal` resolves via that
DNS server for clients (including iOS).
Clawdbot standardizes on the discovery domain `clawdbot.internal.` for this mode. iOS/Android nodes browse both `local.` and `clawdbot.internal.` automatically (no per-device knob).
Clawdbot standardizes on `clawdbot.internal.` for this mode. iOS/Android nodes
browse both `local.` and `clawdbot.internal.` automatically.
### Gateway config (recommended)
On the gateway host (the machine running the Gateway bridge), add to `~/.clawdbot/clawdbot.json` (JSON5):
```json5
{
bridge: { bind: "tailnet" }, // tailnet-only (recommended)
@@ -31,21 +36,17 @@ On the gateway host (the machine running the Gateway bridge), add to `~/.clawdbo
}
```
### One-time DNS server setup (gateway host)
On the gateway host (macOS), run:
### One‑time DNS server setup (gateway host)
```bash
clawdbot dns setup --apply
```
This installs CoreDNS and configures it to:
- listen on port 53 **only** on the gateway’s Tailscale interface IPs
- serve the zone `clawdbot.internal.` from the gateway-owned zone file `~/.clawdbot/dns/clawdbot.internal.db`
- listen on port 53 only on the gateway’s Tailscale interfaces
- serve `clawdbot.internal.` from `~/.clawdbot/dns/clawdbot.internal.db`
The Gateway writes/updates that zone file when `discovery.wideArea.enabled` is true.
Validate from any tailnet-connected machine:
Validate from a tailnet‑connected machine:
```bash
dns-sd -B _clawdbot-bridge._tcp clawdbot.internal.
@@ -59,99 +60,102 @@ In the Tailscale admin console:
- Add a nameserver pointing at the gateway’s tailnet IP (UDP/TCP 53).
- Add split DNS so the domain `clawdbot.internal` uses that nameserver.
Once clients accept tailnet DNS, iOS nodes can browse `_clawdbot-bridge._tcp` in `clawdbot.internal.` without multicast.
Wide-area beacons also include `tailnetDns` (when available) so the macOS app can auto-fill SSH targets off-LAN.
Once clients accept tailnet DNS, iOS nodes can browse
`_clawdbot-bridge._tcp` in `clawdbot.internal.` without multicast.
### Bridge listener security (recommended)
The bridge port (default `18790`) is a plain TCP service. By default it binds to `0.0.0.0`, which makes it reachable from *any* interface on the gateway machine (LAN/Wi‑Fi/Tailscale).
For a tailnet-only setup, bind it to the Tailscale IP instead:
The bridge port (default `18790`) is a plain TCP service. By default it binds to
`0.0.0.0`, which makes it reachable from any interface on the gateway host.
For tailnet‑only setups:
- Set `bridge.bind: "tailnet"` in `~/.clawdbot/clawdbot.json`.
- Restart the Gateway (or restart the macOS menubar app via [`./scripts/restart-mac.sh`](https://github.com/clawdbot/clawdbot/blob/main/scripts/restart-mac.sh) on that machine).
This keeps the bridge reachable only from devices on your tailnet (while still listening on loopback for local/SSH port-forwards).
- Restart the Gateway (or restart the macOS menubar app).
## What advertises
Only the **Node Gateway** (`clawd` / `clawdbot gateway`) advertises Bonjour beacons.
- Implementation: [`src/infra/bonjour.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bonjour.ts)
- Gateway wiring: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts)
Only the Gateway (when the **bridge is enabled**) advertises `_clawdbot-bridge._tcp`.
## Service types
- `_clawdbot-bridge._tcp` — bridge transport beacon (used by macOS/iOS/Android nodes).
## TXT keys (non-secret hints)
## TXT keys (non‑secret hints)
The Gateway advertises small non-secret hints to make UI flows convenient:
The Gateway advertises small non‑secret hints to make UI flows convenient:
- `role=gateway`
- `displayName=<friendly name>`
- `lanHost=<hostname>.local`
- `sshPort=<port>` (defaults to 22 when not overridden)
- `gatewayPort=<port>` (informational; the Gateway WS is typically loopback-only)
- `gatewayPort=<port>` (informational; Gateway WS is usually loopback‑only)
- `bridgePort=<port>` (only when bridge is enabled)
- `canvasPort=<port>` (only when the canvas host is enabled + reachable; default `18793`; serves `/__clawdbot__/canvas/`)
- `cliPath=<path>` (optional; absolute path to a runnable `clawdbot` entrypoint or binary)
- `tailnetDns=<magicdns>` (optional hint; auto-detected from Tailscale when available; may be absent)
- `canvasPort=<port>` (only when the canvas host is enabled; default `18793`)
- `sshPort=<port>` (defaults to 22 when not overridden)
- `transport=bridge`
- `cliPath=<path>` (optional; absolute path to a runnable `clawdbot` entrypoint)
- `tailnetDns=<magicdns>` (optional hint when Tailnet is available)
## Debugging on macOS
Useful built-in tools:
Useful built‑in tools:
- Browse instances:
- `dns-sd -B _clawdbot-bridge._tcp local.`
```bash
dns-sd -B _clawdbot-bridge._tcp local.
```
- Resolve one instance (replace `<instance>`):
- `dns-sd -L "<instance>" _clawdbot-bridge._tcp local.`
```bash
dns-sd -L "<instance>" _clawdbot-bridge._tcp local.
```
If browsing shows instances but resolving fails, you’re usually hitting a LAN policy / multicast issue.
If browsing works but resolving fails, you’re usually hitting a LAN policy or
mDNS resolver issue.
## Debugging in Gateway logs
The Gateway writes a rolling log file (printed on startup as `gateway log file: ...`).
The Gateway writes a rolling log file (printed on startup as
`gateway log file: ...`). Look for `bonjour:` lines, especially:
Look for `bonjour:` lines, especially:
- `bonjour: advertise failed ...` (probing/announce failure)
- `bonjour: advertise failed ...`
- `bonjour: ... name conflict resolved` / `hostname conflict resolved`
- `bonjour: watchdog detected non-announced service; attempting re-advertise ...` (self-heal attempt after sleep/interface churn)
- `bonjour: watchdog detected non-announced service ...`
## Debugging on iOS node
The iOS node app discovers bridges via `NWBrowser` browsing `_clawdbot-bridge._tcp`.
The iOS node uses `NWBrowser` to discover `_clawdbot-bridge._tcp`.
To capture what the browser is doing:
To capture logs:
- Settings → Bridge → Advanced → **Discovery Debug Logs**
- Settings → Bridge → Advanced → **Discovery Logs** → reproduce → **Copy**
- Settings → Bridge → Advanced → enable **Discovery Debug Logs**
- Settings → Bridge → Advanced → open **Discovery Logs** → reproduce the “Searching…” / “No bridges found” case → **Copy**
The log includes browser state transitions (`ready`, `waiting`, `failed`, `cancelled`) and result-set changes (added/removed counts).
The log includes browser state transitions and result‑set changes.
## Common failure modes
- **Bonjour doesn’t cross networks**: London/Vienna style setups require Tailnet (MagicDNS/IP) or SSH.
- **Multicast blocked**: some Wi‑Fi networks (enterprise/hotels) disable mDNS; expect “no results”.
- **Sleep / interface churn**: macOS may temporarily drop mDNS results when switching networks; retry.
- **Browse works but resolve fails (iOS “NoSuchRecord”)**: make sure the advertiser publishes a valid SRV target hostname.
- Implementation detail: `@homebridge/ciao` defaults `hostname` to the *service instance name* when `hostname` is omitted. If your instance name contains spaces/parentheses, some resolvers can fail to resolve the implied A/AAAA record.
- Fix: set an explicit DNS-safe `hostname` (single label; no `.local`) in [`src/infra/bonjour.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/bonjour.ts).
- **Bonjour doesn’t cross networks**: use Tailnet or SSH.
- **Multicast blocked**: some Wi‑Fi networks disable mDNS.
- **Sleep / interface churn**: macOS may temporarily drop mDNS results; retry.
- **Browse works but resolve fails**: keep machine names simple (avoid emojis or
punctuation), then restart the Gateway. The bridge instance name derives from
the host name, so overly complex names can confuse some resolvers.
## Escaped instance names (`\\032`)
Bonjour/DNS-SD often escapes bytes in service instance names as decimal `\\DDD` sequences (e.g. spaces become `\\032`).
## Escaped instance names (`\032`)
Bonjour/DNS‑SD often escapes bytes in service instance names as decimal `\DDD`
sequences (e.g. spaces become `\032`).
- This is normal at the protocol level.
- UIs should decode for display (iOS uses `BonjourEscapes.decode` in `apps/shared/ClawdbotKit`).
- UIs should decode for display (iOS uses `BonjourEscapes.decode`).
## Disabling / configuration
- `CLAWDBOT_DISABLE_BONJOUR=1` disables advertising.
- `CLAWDBOT_BRIDGE_ENABLED=0` disables the bridge listener (and therefore the bridge beacon).
- `bridge.bind` / `bridge.port` in `~/.clawdbot/clawdbot.json` control bridge bind/port (preferred).
- `CLAWDBOT_BRIDGE_HOST` / `CLAWDBOT_BRIDGE_PORT` still work as a back-compat override when `bridge.bind` / `bridge.port` are not set.
- `CLAWDBOT_SSH_PORT` overrides the SSH port advertised in `_clawdbot-bridge._tcp`.
- `CLAWDBOT_TAILNET_DNS` publishes a `tailnetDns` hint (MagicDNS) in `_clawdbot-bridge._tcp`. If unset, the gateway auto-detects Tailscale and publishes the MagicDNS name when possible.
- `CLAWDBOT_BRIDGE_ENABLED=0` disables the bridge listener (and the bridge beacon).
- `bridge.bind` / `bridge.port` in `~/.clawdbot/clawdbot.json` control bridge bind/port.
- `CLAWDBOT_BRIDGE_HOST` / `CLAWDBOT_BRIDGE_PORT` still work as back‑compat overrides.
- `CLAWDBOT_SSH_PORT` overrides the SSH port advertised in TXT.
- `CLAWDBOT_TAILNET_DNS` publishes a MagicDNS hint in TXT.
- `CLAWDBOT_CLI_PATH` overrides the advertised CLI path.
## Related docs
+66 -59
View File
@@ -1,9 +1,9 @@
---
summary: "Schema-accurate configuration examples for common Clawdbot setups"
read_when:
- Learning how to configure clawdbot
- Learning how to configure Clawdbot
- Looking for configuration examples
- Setting up clawdbot for the first time
- Setting up Clawdbot for the first time
---
# Configuration Examples
@@ -48,6 +48,10 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
{
// Environment + shell
env: {
OPENROUTER_API_KEY: "sk-or-...",
vars: {
GROQ_API_KEY: "gsk-..."
},
shellEnv: {
enabled: true,
timeoutMs: 15000
@@ -185,52 +189,71 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
},
// Agent runtime
agent: {
workspace: "~/clawd",
userTimezone: "America/Chicago",
model: {
primary: "anthropic/claude-sonnet-4-5",
fallbacks: ["anthropic/claude-opus-4-5", "openai/gpt-5.2"]
},
imageModel: {
primary: "openrouter/anthropic/claude-sonnet-4-5"
},
models: {
"anthropic/claude-opus-4-5": { alias: "opus" },
"anthropic/claude-sonnet-4-5": { alias: "sonnet" },
"openai/gpt-5.2": { alias: "gpt" }
},
thinkingDefault: "low",
verboseDefault: "off",
elevatedDefault: "on",
blockStreamingDefault: "on",
blockStreamingBreak: "text_end",
blockStreamingChunk: {
minChars: 800,
maxChars: 1200,
breakPreference: "paragraph"
},
timeoutSeconds: 600,
mediaMaxMb: 5,
typingIntervalSeconds: 5,
maxConcurrent: 3,
tools: {
allow: ["bash", "process", "read", "write", "edit"],
deny: ["browser", "canvas"]
},
agents: {
defaults: {
workspace: "~/clawd",
userTimezone: "America/Chicago",
model: {
primary: "anthropic/claude-sonnet-4-5",
fallbacks: ["anthropic/claude-opus-4-5", "openai/gpt-5.2"]
},
imageModel: {
primary: "openrouter/anthropic/claude-sonnet-4-5"
},
models: {
"anthropic/claude-opus-4-5": { alias: "opus" },
"anthropic/claude-sonnet-4-5": { alias: "sonnet" },
"openai/gpt-5.2": { alias: "gpt" }
},
thinkingDefault: "low",
verboseDefault: "off",
elevatedDefault: "on",
blockStreamingDefault: "on",
blockStreamingBreak: "text_end",
blockStreamingChunk: {
minChars: 800,
maxChars: 1200,
breakPreference: "paragraph"
},
timeoutSeconds: 600,
mediaMaxMb: 5,
typingIntervalSeconds: 5,
maxConcurrent: 3,
heartbeat: {
every: "30m",
model: "anthropic/claude-sonnet-4-5",
target: "last",
to: "+15555550123",
prompt: "HEARTBEAT",
ackMaxChars: 30
},
sandbox: {
mode: "non-main",
perSession: true,
workspaceRoot: "~/.clawdbot/sandboxes",
docker: {
image: "clawdbot-sandbox:bookworm-slim",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000"
},
browser: {
enabled: false
}
}
}
},
tools: {
allow: ["bash", "process", "read", "write", "edit"],
deny: ["browser", "canvas"],
bash: {
backgroundMs: 10000,
timeoutSec: 1800,
cleanupMs: 1800000
},
heartbeat: {
every: "30m",
model: "anthropic/claude-sonnet-4-5",
target: "last",
to: "+15555550123",
prompt: "HEARTBEAT",
ackMaxChars: 30
},
elevated: {
enabled: true,
allowFrom: {
@@ -242,22 +265,6 @@ Save to `~/.clawdbot/clawdbot.json` and you can DM the bot from that number.
imessage: ["user@example.com"],
webchat: ["session:demo"]
}
},
sandbox: {
mode: "non-main",
perSession: true,
workspaceRoot: "~/.clawdbot/sandboxes",
docker: {
image: "clawdbot-sandbox:bookworm-slim",
workdir: "/workspace",
readOnlyRoot: true,
tmpfs: ["/tmp", "/var/tmp", "/run"],
network: "none",
user: "1000:1000"
},
browser: {
enabled: false
}
}
},
File diff suppressed because it is too large. Load diff
+5 -12
View File
@@ -44,7 +44,7 @@ Target direction:
Troubleshooting and beacon details: [`docs/bonjour.md`](/gateway/bonjour).
#### Current implementation
#### Service beacon details
- Service types:
- `_clawdbot-bridge._tcp` (bridge transport beacon)
@@ -98,15 +98,8 @@ The gateway is the source of truth for node/client admission.
- scopes/ACLs (bridge is not a raw proxy to every gateway method)
- rate limits
## Where the code lives (target architecture)
## Responsibilities by component
- Node gateway:
- advertises discovery beacons (Bonjour)
- owns pairing storage + decisions
- runs the bridge listener (direct transport)
- macOS app:
- UI for picking a gateway, showing pairing prompts, and troubleshooting
- SSH tunneling only for the fallback path
- iOS node:
- browses Bonjour (LAN) as a convenience only
- uses direct transport + pairing to connect to the gateway
- **Gateway**: advertises discovery beacons, owns pairing decisions, runs the bridge listener.
- **macOS app**: helps you pick a gateway, shows pairing prompts, and uses SSH only as a fallback.
- **iOS/Android nodes**: browse Bonjour as a convenience and connect via the paired bridge.
+66 -10
View File
@@ -23,11 +23,24 @@ clawdbot doctor --yes
Accept defaults without prompting (including restart/service/sandbox repair steps when applicable).
```bash
clawdbot doctor --repair
```
Apply recommended repairs without prompting (repairs + restarts where safe).
```bash
clawdbot doctor --repair --force
```
Apply aggressive repairs too (overwrites custom supervisor configs).
```bash
clawdbot doctor --non-interactive
```
Run without prompts and only apply safe migrations (config normalization + on-disk state moves). Skips restart/service/sandbox actions that require human confirmation.
Legacy state migrations run automatically when detected.
```bash
clawdbot doctor --deep
@@ -47,10 +60,14 @@ cat ~/.clawdbot/clawdbot.json
- Legacy config migration and normalization.
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
- State integrity and permissions checks (sessions, transcripts, state dir).
- Config file permission checks (chmod 600) when running locally.
- Model auth health: checks OAuth expiry and can refresh expiring tokens.
- Legacy workspace dir detection (`~/clawdis`, `~/clawdbot`).
- Sandbox image repair when sandboxing is enabled.
- Legacy service migration and extra gateway detection.
- Gateway runtime checks (service installed but not running; cached launchd label).
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
- Gateway runtime best-practice checks (Node vs Bun, version-manager paths).
- Gateway port collision diagnostics (default `18789`).
- Security warnings for open DM policies.
- systemd linger check on Linux.
@@ -77,8 +94,18 @@ legacy config format, so stale configs are repaired without manual intervention.
Current migrations:
- `routing.allowFrom` → `whatsapp.allowFrom`
- `routing.groupChat.requireMention` → `whatsapp/telegram/imessage.groups."*".requireMention`
- `routing.groupChat.historyLimit` → `messages.groupChat.historyLimit`
- `routing.groupChat.mentionPatterns` → `messages.groupChat.mentionPatterns`
- `routing.queue` → `messages.queue`
- `routing.bindings` → top-level `bindings`
- `routing.agents`/`routing.defaultAgentId` → `agents.list` + `agents.list[].default`
- `routing.agentToAgent` → `tools.agentToAgent`
- `routing.transcribeAudio` → `audio.transcription`
- `identity` → `agents.list[].identity`
- `agent.*` → `agents.defaults` + `tools.*` (tools/elevated/bash/sandbox/subagents)
- `agent.model`/`allowedModels`/`modelAliases`/`modelFallbacks`/`imageModelFallbacks`
→ `agent.models` + `agent.model.primary/fallbacks` + `agent.imageModel.primary/fallbacks`
→ `agents.defaults.models` + `agents.defaults.model.primary/fallbacks` + `agents.defaults.imageModel.primary/fallbacks`
### 3) Legacy state migrations (disk layout)
Doctor can migrate older on-disk layouts into the current structure:
@@ -116,44 +143,73 @@ Doctor checks:
split between installs).
- **Remote mode reminder**: if `gateway.mode=remote`, doctor reminds you to run
it on the remote host (the state lives there).
- **Config file permissions**: warns if `~/.clawdbot/clawdbot.json` is
group/world readable and offers to tighten to `600`.
### 5) Sandbox image repair
### 5) Model auth health (OAuth expiry)
Doctor inspects OAuth profiles in the auth store, warns when tokens are
expiring/expired, and can refresh them when safe. If the Anthropic Claude Code
profile is stale, it suggests `claude setup-token` on the gateway host.
Refresh prompts only appear when running interactively (TTY); `--non-interactive`
skips refresh attempts.
### 6) Sandbox image repair
When sandboxing is enabled, doctor checks Docker images and offers to build or
switch to legacy names if the current image is missing.
### 6) Gateway service migrations and cleanup hints
### 7) Gateway service migrations and cleanup hints
Doctor detects legacy Clawdis gateway services (launchd/systemd/schtasks) and
offers to remove them and install the Clawdbot service using the current gateway
port. It can also scan for extra gateway-like services and print cleanup hints
to ensure only one gateway runs per machine.
### 7) Security warnings
### 8) Security warnings
Doctor emits warnings when a provider is open to DMs without an allowlist, or
when a policy is configured in a dangerous way.
### 8) systemd linger (Linux)
### 9) systemd linger (Linux)
If running as a systemd user service, doctor ensures lingering is enabled so the
gateway stays alive after logout.
### 9) Skills status
### 10) Skills status
Doctor prints a quick summary of eligible/missing/blocked skills for the current
workspace.
### 10) Gateway health check + restart
### 11) Gateway health check + restart
Doctor runs a health check and offers to restart the gateway when it looks
unhealthy.
### 11) Gateway runtime + port diagnostics
### 12) Supervisor config audit + repair
Doctor checks the installed supervisor config (launchd/systemd/schtasks) for
missing or outdated defaults (e.g., systemd network-online dependencies and
restart delay). When it finds a mismatch, it recommends an update and can
rewrite the service file/task to the current defaults.
Notes:
- `clawdbot doctor` prompts before rewriting supervisor config.
- `clawdbot doctor --yes` accepts the default repair prompts.
- `clawdbot doctor --repair` applies recommended fixes without prompts.
- `clawdbot doctor --repair --force` overwrites custom supervisor configs.
- You can always force a full rewrite via `clawdbot daemon install --force`.
### 13) Gateway runtime + port diagnostics
Doctor inspects the daemon runtime (PID, last exit status) and warns when the
service is installed but not actually running. It also checks for port collisions
on the gateway port (default `18789`) and reports likely causes (gateway already
running, SSH tunnel).
### 12) Config write + wizard metadata
### 14) Gateway runtime best practices
Doctor warns when the gateway service runs on Bun or a version-managed Node path
(`nvm`, `fnm`, `volta`, `asdf`, etc.). WhatsApp + Telegram providers require Node,
and version-manager paths can break after upgrades because the daemon does not
load your shell init. Doctor offers to migrate to a system Node install when
available (Homebrew/apt/choco).
### 15) Config write + wizard metadata
Doctor persists any config changes and stamps wizard metadata to record the
doctor run.
### 13) Workspace tips (backup + memory system)
### 16) Workspace tips (backup + memory system)
Doctor suggests a workspace memory system when missing and prints a backup tip
if the workspace is not already under git.
+1 -1
View File
@@ -22,7 +22,7 @@ Short guide to verify the WhatsApp Web / Baileys stack without guessing.
## When something fails
- `logged out` or status 409–515 → relink with `clawdbot providers logout` then `clawdbot providers login`.
- Gateway unreachable → start it: `clawdbot gateway --port 18789` (use `--force` if the port is busy).
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `routing.groupChat.mentionPatterns`).
- No inbound messages → confirm linked phone is online and the sender is allowed (`whatsapp.allowFrom`); for group chats, ensure allowlist + mention rules match (`whatsapp.groups`, `agents.list[].groupChat.mentionPatterns`).
## Dedicated "health" command
`clawdbot health --json` asks the running Gateway for its health snapshot (no direct Baileys socket from the CLI). It reports linked creds, auth age, Baileys connect result/status code, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.
+61 -75
View File
@@ -1,106 +1,92 @@
---
summary: "Plan for heartbeat polling messages and notification rules"
summary: "Heartbeat polling messages and notification rules"
read_when:
- Adjusting heartbeat cadence or messaging
---
# Heartbeat (Gateway)
Heartbeat runs periodic agent turns in the **main session** so the model can
surface anything that needs attention without spamming the user.
Heartbeat runs **periodic agent turns** in the main session so the model can
surface anything that needs attention without spamming you.
## Defaults
- Interval: `30m` (set `agent.heartbeat.every` to change, `0m` disables).
- Prompt body (configurable via `agent.heartbeat.prompt`):
- Interval: `30m` (set `agents.defaults.heartbeat.every`; use `0m` to disable).
- Prompt body (configurable via `agents.defaults.heartbeat.prompt`):
`Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.`
- Heartbeat prompt text is sent **verbatim** as the user message. Clawdbot does
not append extra body text. The system prompt includes a Heartbeats section
and the run is flagged as a heartbeat internally.
- The heartbeat prompt is sent **verbatim** as the user message. The system
prompt includes a “Heartbeat” section and the run is flagged internally.
## Prompt contract
- If nothing needs attention, the model should reply `HEARTBEAT_OK`.
- During heartbeat runs, Clawdbot treats `HEARTBEAT_OK` as an ack when it appears at
the **start or end** of the reply. Clawdbot strips the token and discards the
reply if the remaining content is **≤ `ackMaxChars`** (default: 30).
- If `HEARTBEAT_OK` is in the **middle** of a reply, it is not treated specially.
- For alerts, do **not** include `HEARTBEAT_OK`; return only the alert text.
## Response contract
## Prompt overrides
- Overriding `agent.heartbeat.prompt` **replaces** the default body. Nothing is
merged for you.
- If you still want `HEARTBEAT.md` instructions, keep a line like
`Read HEARTBEAT.md if exists` in your custom prompt.
- `HEARTBEAT_OK` handling stays the same; changing the prompt won’t break acks.
- If nothing needs attention, reply with **`HEARTBEAT_OK`**.
- During heartbeat runs, Clawdbot treats `HEARTBEAT_OK` as an ack when it appears
at the **start or end** of the reply. The token is stripped and the reply is
dropped if the remaining content is **≤ `ackMaxChars`** (default: 30).
- If `HEARTBEAT_OK` appears in the **middle** of a reply, it is not treated
specially.
- For alerts, **do not** include `HEARTBEAT_OK`; return only the alert text.
### Stray `HEARTBEAT_OK` outside heartbeats
If the model accidentally includes `HEARTBEAT_OK` at the start or end of a
normal (non-heartbeat) reply, Clawdbot strips the token and logs a verbose
message. If the reply is only `HEARTBEAT_OK`, it is dropped.
### Outbound normalization (all providers)
For **all providers** (WhatsApp/Web, Telegram, Slack, Discord, Signal, iMessage),
Clawdbot applies the same filtering to tool summaries, streaming block replies,
and final replies:
- drop payloads that are only `HEARTBEAT_OK` with no media
- strip `HEARTBEAT_OK` at the edges when mixed with other text
Outside heartbeats, stray `HEARTBEAT_OK` at the start/end of a message is stripped
and logged; a message that is only `HEARTBEAT_OK` is dropped.
## Config
```json5
{
agent: {
heartbeat: {
every: "30m", // default: 30m (0m disables)
model: "anthropic/claude-opus-4-5",
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
to: "+15551234567", // optional provider-specific override (e.g. E.164 or chat id)
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
agents: {
defaults: {
heartbeat: {
every: "30m", // default: 30m (0m disables)
model: "anthropic/claude-opus-4-5",
target: "last", // last | whatsapp | telegram | discord | slack | signal | imessage | none
to: "+15551234567", // optional provider-specific override
prompt: "Read HEARTBEAT.md if exists. Consider outstanding tasks. Checkup sometimes on your human during (user local) day time.",
ackMaxChars: 30 // max chars allowed after HEARTBEAT_OK
}
}
}
}
```
### Fields
- `every`: heartbeat interval (duration string; default unit minutes). Default:
`30m`. Set to `0m` to disable.
- `model`: optional model override for heartbeat runs (`provider/model`).
- `target`: where heartbeat output is delivered.
- `last` (default): send to the last used external provider.
- `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage`: force the provider (optionally set `to`).
- `none`: do not deliver externally; output stays in the session (WebChat-visible).
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram).
- `prompt`: optional override for the heartbeat body (default shown above). Safe to
change; heartbeat acks are still keyed off `HEARTBEAT_OK`.
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery (default: 30).
### Field notes
## Cost awareness
Heartbeats run full agent turns. Shorter intervals burn more tokens. Be
intentional about `every`, keep `HEARTBEAT.md` tiny, and consider a cheaper
`model` or `target: "none"` if you only want internal state updates.
- `every`: heartbeat interval (duration string; default unit = minutes).
- `model`: optional model override for heartbeat runs (`provider/model`).
- `target`:
- `last` (default): deliver to the last used external provider.
- explicit provider: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage`.
- `none`: run the heartbeat but **do not deliver** externally.
- `to`: optional recipient override (E.164 for WhatsApp, chat id for Telegram, etc.).
- `prompt`: overrides the default prompt body (not merged).
- `ackMaxChars`: max chars allowed after `HEARTBEAT_OK` before delivery.
## Delivery behavior
- Heartbeats run in the **main session** (`main`, or `global` when scope is global).
- If the main queue is busy, the heartbeat is skipped and retried later.
- If `target` resolves to no external destination, the run still happens but no
outbound message is sent.
- Heartbeat-only replies do **not** keep the session alive; the last `updatedAt`
is restored so idle expiry behaves normally.
## HEARTBEAT.md (optional)
If a `HEARTBEAT.md` file exists in the workspace, the default prompt tells the
agent to read it. Keep it tiny (short checklist or reminders) to avoid prompt
bloat.
## Behavior
- Runs in the main session (`main`, or `global` when scope is global).
- Uses the main lane queue; if requests are in flight, the wake is retried.
- Empty output or `HEARTBEAT_OK` is treated as “ok” and does **not** keep the
session alive (`updatedAt` is restored).
- If `target` resolves to no external destination (no last route or `none`), the
heartbeat still runs but no outbound message is sent.
## Manual wake (on-demand)
## Ideas for use
- Check up on the user (light, respectful pings during daytime).
- Handle mundane tasks (triage inboxes, summarize queues, refresh notes).
- Nudge on open loops or reminders.
- Background monitoring (health checks, status polling, low-priority alerts).
- Scheduled routines (use [Cron jobs](/automation/cron-jobs) when you
need exact schedules or isolated runs).
You can enqueue a system event and trigger an immediate heartbeat with:
## Wake hook
- The gateway exposes a heartbeat wake hook so cron/jobs/webhooks can request an
immediate run (`requestHeartbeatNow`).
- `wake` endpoints should enqueue system events and optionally trigger a wake; the
heartbeat runner picks those up on the next tick or immediately.
```bash
clawdbot wake --text "Check for urgent follow-ups" --mode now
```
Use `--mode next-heartbeat` to wait for the next scheduled tick.
## Cost awareness
Heartbeats run full agent turns. Shorter intervals burn more tokens. Keep
`HEARTBEAT.md` small and consider a cheaper `model` or `target: "none"` if you
only want internal state updates.
+19 -4
View File
@@ -68,7 +68,7 @@ Defaults (can be overridden via env/flags/config):
- `bridge.port=19002` (derived: `gateway.port+1`)
- `browser.controlUrl=http://127.0.0.1:19003` (derived: `gateway.port+2`)
- `canvasHost.port=19005` (derived: `gateway.port+4`)
- `agent.workspace` default becomes `~/clawd-dev` when you run `setup`/`onboard` under `--dev`.
- `agents.defaults.workspace` default becomes `~/clawd-dev` when you run `setup`/`onboard` under `--dev`.
Derived ports (rules of thumb):
- Base port = `gateway.port` (or `CLAWDBOT_GATEWAY_PORT` / `--port`)
@@ -81,7 +81,7 @@ Checklist per instance:
- unique `gateway.port`
- unique `CLAWDBOT_CONFIG_PATH`
- unique `CLAWDBOT_STATE_DIR`
- unique `agent.workspace`
- unique `agents.defaults.workspace`
- separate WhatsApp numbers (if using WA)
Example:
@@ -127,7 +127,9 @@ See also: [`docs/presence.md`](/concepts/presence) for how presence is produced/
## Typing and validation
- Server validates every inbound frame with AJV against JSON Schema emitted from the protocol definitions.
- Clients (TS/Swift) consume generated types (TS directly; Swift via the repo’s generator).
- Types live in [`src/gateway/protocol/*.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/*.ts); regenerate schemas/models with `pnpm protocol:gen` (writes [`dist/protocol.schema.json`](https://github.com/clawdbot/clawdbot/blob/main/dist/protocol.schema.json)) and `pnpm protocol:gen:swift` (writes [`apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/ClawdbotProtocol/GatewayModels.swift)).
- Protocol definitions are the source of truth; regenerate schema/models with:
- `pnpm protocol:gen`
- `pnpm protocol:gen:swift`
## Connection snapshot
- `hello-ok` includes a `snapshot` with `presence`, `health`, `stateVersion`, and `uptimeMs` plus `policy {maxPayload,maxBufferedBytes,tickIntervalMs}` so clients can render immediately without extra requests.
@@ -156,6 +158,8 @@ See also: [`docs/presence.md`](/concepts/presence) for how presence is produced/
- StandardOut/Err: file paths or `syslog`
- On failure, launchd restarts; fatal misconfig should keep exiting so the operator notices.
- LaunchAgents are per-user and require a logged-in session; for headless setups use a custom LaunchDaemon (not shipped).
- `clawdbot daemon install` writes `~/Library/LaunchAgents/com.clawdbot.gateway.plist`.
- `clawdbot doctor` audits the LaunchAgent config and can update it to current defaults.
## Daemon management (CLI)
@@ -172,8 +176,11 @@ clawdbot logs --follow
Notes:
- `daemon status` probes the Gateway RPC by default using the daemon’s resolved port/config (override with `--url`).
- `daemon status --deep` adds system-level scans (LaunchDaemons/system units).
- `daemon status --no-probe` skips the RPC probe (useful when networking is down).
- `daemon status --json` is stable for scripts.
- `daemon status` reports **supervisor runtime** (launchd/systemd running) separately from **RPC reachability** (WS connect + status RPC).
- `daemon status` prints config path + probe target to avoid “localhost vs LAN bind” confusion and profile mismatches.
- `daemon status` includes the last gateway error line when the service looks running but the port is closed.
- `logs` tails the Gateway file log via RPC (no manual `tail`/`grep` needed).
- If other gateway-like services are detected, the CLI warns. We recommend **one gateway per machine**; one gateway can host multiple agents.
- Cleanup: `clawdbot daemon uninstall` (current service) and `clawdbot doctor` (legacy migrations).
@@ -186,6 +193,14 @@ Bundled mac app:
- `launchctl` only works if the LaunchAgent is installed; otherwise use `clawdbot daemon install` first.
## Supervision (systemd user unit)
Clawdbot installs a **systemd user service** by default on Linux/WSL2. We
recommend user services for single-user machines (simpler env, per-user config).
Use a **system service** for multi-user or always-on servers (no lingering
required, shared supervision).
`clawdbot daemon install` writes the user unit. `clawdbot doctor` audits the
unit and can update it to match the current recommended defaults.
Create `~/.config/systemd/user/clawdbot-gateway.service`:
```
[Unit]
@@ -239,7 +254,7 @@ Windows installs should use **WSL2** and follow the Linux systemd section above.
## CLI helpers
- `clawdbot gateway health|status` — request health/status over the Gateway WS.
- `clawdbot send --to <num> --message "hi" [--media ...]` — send via Gateway (idempotent for WhatsApp).
- `clawdbot message send --to <num> --message "hi" [--media ...]` — send via Gateway (idempotent for WhatsApp).
- `clawdbot agent --message "hi" --to <num>` — run an agent turn (waits for final by default).
- `clawdbot gateway call <method> --params '{"k":"v"}'` — raw method invoker for debugging.
- `clawdbot daemon stop|restart` — stop/restart the supervised gateway service (launchd/systemd).
+6 -12
View File
@@ -7,15 +7,15 @@ read_when:
# Logging
For a user-facing overview (CLI + Control UI + config), see [/logging](/logging).
Clawdbot has two log “surfaces”:
- **Console output** (what you see in the terminal / Debug UI).
- **File logs** (JSON lines) written by the internal logger.
- **File logs** (JSON lines) written by the gateway logger.
## File-based logger
Clawdbot uses a file logger backed by `tslog` ([`src/logging.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/logging.ts)).
- Default rolling log file is under `/tmp/clawdbot/` (one file per day): `clawdbot-YYYY-MM-DD.log`
- The log file path and level can be configured via `~/.clawdbot/clawdbot.json`:
- `logging.file`
@@ -40,9 +40,8 @@ clawdbot logs --follow
## Console capture
The CLI entrypoint enables console capture ([`src/index.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/index.ts) calls `enableConsoleCapture()`).
That means every `console.log/info/warn/error/debug/trace` is also written into the file logs,
while still behaving normally on stdout/stderr.
The CLI captures `console.log/info/warn/error/debug/trace` and writes them to file logs,
while still printing to stdout/stderr.
You can tune console verbosity independently via:
@@ -94,13 +93,8 @@ clawdbot gateway --verbose --ws-log full
## Console formatting (subsystem logging)
Clawdbot formats console logs via a small wrapper on top of the existing stack:
- **tslog** for structured file logs ([`src/logging.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/logging.ts))
- **chalk** for colors ([`src/globals.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/globals.ts))
The console formatter is **TTY-aware** and prints consistent, prefixed lines.
Subsystem loggers are created via `createSubsystemLogger("gateway")`.
Subsystem loggers keep output grouped and scannable.
Behavior:
+69 -89
View File
@@ -7,103 +7,83 @@ read_when:
---
# Gateway-owned pairing (Option B)
Goal: The Gateway (`clawd`) is the **source of truth** for which nodes are allowed to join the network.
This enables:
- Headless approval via terminal/CLI (no Swift UI required).
- Optional macOS UI approval (Swift app is just a frontend).
- One consistent membership store for iOS, mac nodes, future hardware nodes.
In Gateway-owned pairing, the **Gateway** is the source of truth for which nodes
are allowed to join. UIs (macOS app, future clients) are just frontends that
approve or reject pending requests.
## Concepts
- **Pending request**: a node asked to join; requires explicit approve/reject.
- **Paired node**: node is allowed; gateway returns an auth token for subsequent connects.
- **Bridge**: direct transport endpoint owned by the gateway. The bridge does not decide membership.
- **Pending request**: a node asked to join; requires approval.
- **Paired node**: approved node with an issued auth token.
- **Bridge**: transport endpoint only; it forwards requests but does not decide
membership.
## How pairing works
1. A node connects to the bridge and requests pairing.
2. The Gateway stores a **pending request** and emits `node.pair.requested`.
3. You approve or reject the request (CLI or UI).
4. On approval, the Gateway issues a **new token** (tokens are rotated on re‑pair).
5. The node reconnects using the token and is now “paired”.
Pending requests expire automatically after **5 minutes**.
## CLI workflow (headless friendly)
```bash
clawdbot nodes pending
clawdbot nodes approve <requestId>
clawdbot nodes reject <requestId>
clawdbot nodes status
clawdbot nodes rename --node <id|name|ip> --name "Living Room iPad"
```
`nodes status` shows paired/connected nodes and their capabilities.
## API surface (gateway protocol)
These are conceptual method names; wire them into [`src/gateway/protocol/schema.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/protocol/schema.ts) and regenerate Swift types.
### Events
- `node.pair.requested`
- Emitted whenever a new pending pairing request is created.
- Payload:
- `requestId` (string)
- `nodeId` (string)
- `displayName?` (string)
- `platform?` (string)
- `version?` (string)
- `remoteIp?` (string)
- `silent?` (boolean) — hint that the UI may attempt auto-approval
- `ts` (ms since epoch)
- `node.pair.resolved`
- Emitted when a pending request is approved/rejected.
- Payload:
- `requestId` (string)
- `nodeId` (string)
- `decision` ("approved" | "rejected" | "expired")
- `ts` (ms since epoch)
Events:
- `node.pair.requested` — emitted when a new pending request is created.
- `node.pair.resolved` — emitted when a request is approved/rejected/expired.
### Methods
- `node.pair.request`
- Creates (or returns) a pending request.
- Params: node metadata (same shape as `node.pair.requested` payload, minus `requestId`/`ts`).
- Optional `silent` flag hints that the UI can attempt an SSH auto-approve before showing an alert.
- Result:
- `status` ("pending")
- `created` (boolean) — whether this call created the pending request
- `request` (pending request object), including `isRepair` when the node was already paired
- Security: **never returns an existing token**. If a paired node “lost” its token, it must be approved again (token rotation).
- `node.pair.list`
- Returns:
- `pending[]` (pending requests)
- `paired[]` (paired node records)
- `node.pair.approve`
- Params: `{ requestId }`
- Result: `{ requestId, node: { nodeId, token, ... } }`
- Must be idempotent (first decision wins).
- `node.pair.reject`
- Params: `{ requestId }`
- Result: `{ requestId, nodeId }`
- `node.pair.verify`
- Params: `{ nodeId, token }`
- Result: `{ ok: boolean, node?: { nodeId, ... } }`
## CLI flows
CLI must be able to fully operate without any GUI:
- `clawdbot nodes pending`
- `clawdbot nodes approve <requestId>`
- `clawdbot nodes reject <requestId>`
- `clawdbot nodes status` (paired nodes + connection status/capabilities)
Optional interactive helper:
- `clawdbot nodes watch` (subscribe to `node.pair.requested` and prompt in-place)
Implementation pointers:
- CLI commands: [`src/cli/nodes-cli.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/cli/nodes-cli.ts)
- Gateway handlers + events: [`src/gateway/server.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server.ts) + [`src/gateway/server-methods/nodes.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/gateway/server-methods/nodes.ts)
- Pairing store: [`src/infra/node-pairing.ts`](https://github.com/clawdbot/clawdbot/blob/main/src/infra/node-pairing.ts) (under `~/.clawdbot/nodes/`)
- Optional macOS UI prompt (frontend only): [`apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift`](https://github.com/clawdbot/clawdbot/blob/main/apps/macos/Sources/Clawdbot/NodePairingApprovalPrompter.swift)
- Push-first: listens to `node.pair.requested`/`node.pair.resolved`, does a `node.pair.list` on startup/reconnect,
and only runs a slow safety poll while a request is pending/visible.
## Storage (private, local)
Gateway stores the authoritative state under `~/.clawdbot/`:
- `~/.clawdbot/nodes/paired.json`
- `~/.clawdbot/nodes/pending.json` (or `~/.clawdbot/nodes/pending/*.json`)
Methods:
- `node.pair.request` — create or reuse a pending request.
- `node.pair.list` — list pending + paired nodes.
- `node.pair.approve` — approve a pending request (issues token).
- `node.pair.reject` — reject a pending request.
- `node.pair.verify` — verify `{ nodeId, token }`.
Notes:
- Tokens are secrets. Treat `paired.json` as sensitive.
- Pending entries should have a TTL (e.g. 5 minutes) and expire automatically.
- `node.pair.request` is idempotent per node: repeated calls return the same
pending request.
- Approval **always** generates a fresh token; no token is ever returned from
`node.pair.request`.
- Requests may include `silent: true` as a hint for auto-approval flows.
## Bridge integration
Target direction:
- The gateway runs the bridge listener (LAN/tailnet-facing) and advertises discovery beacons (Bonjour).
- The bridge is transport only; it forwards/scopes requests and enforces ACLs, but pairing decisions are made by the gateway.
## Auto-approval (macOS app)
The macOS UI (Swift) can:
- Subscribe to `node.pair.requested`, show an alert (including `remoteIp`), and call `node.pair.approve` or `node.pair.reject`.
- Or ignore/dismiss (“Later”) and let CLI handle it.
- When `silent` is set, it can try a short SSH probe (same user) and auto-approve if reachable; otherwise fall back to the normal alert.
The macOS app can optionally attempt a **silent approval** when:
- the request is marked `silent`, and
- the app can verify an SSH connection to the gateway host using the same user.
## Implementation note
If the bridge is only provided by the macOS app, then “no Swift app running” cannot work end-to-end.
The long-term goal is to move bridge hosting + Bonjour advertising into the Node gateway so headless pairing works by default.
If silent approval fails, it falls back to the normal “Approve/Reject” prompt.
## Storage (local, private)
Pairing state is stored under the Gateway state directory (default `~/.clawdbot`):
- `~/.clawdbot/nodes/paired.json`
- `~/.clawdbot/nodes/pending.json`
If you override `CLAWDBOT_STATE_DIR`, the `nodes/` folder moves with it.
Security notes:
- Tokens are secrets; treat `paired.json` as sensitive.
- Rotating a token requires re-approval (or deleting the node entry).
## Bridge behavior
- The bridge is **transport only**; it does not store membership.
- If the Gateway is offline or pairing is disabled, nodes cannot pair.
- If the bridge is running but the Gateway is in remote mode, pairing still
happens against the remote Gateway’s store.
+1 -1
View File
@@ -68,7 +68,7 @@ ssh -N remote-gateway &
### Step 5: Restart Clawdbot.app
```bash
killall Clawdbot
# Quit Clawdbot.app (⌘Q), then reopen:
open /path/to/Clawdbot.app
```
+104
View File
@@ -0,0 +1,104 @@
---
summary: "How Clawdbot sandboxing works: modes, scopes, workspace access, and images"
title: Sandboxing
read_when: "You want a dedicated explanation of sandboxing or need to tune agents.defaults.sandbox."
status: active
---
# Sandboxing
Clawdbot can run **tools inside Docker containers** to reduce blast radius.
This is **optional** and controlled by configuration (`agents.defaults.sandbox` or
`agents.list[].sandbox`). If sandboxing is off, tools run on the host.
The Gateway stays on the host; tool execution runs in an isolated sandbox
when enabled.
This is not a perfect security boundary, but it materially limits filesystem
and process access when the model does something dumb.
## What gets sandboxed
- Tool execution (`bash`, `read`, `write`, `edit`, `process`, etc.).
- Optional sandboxed browser (`agents.defaults.sandbox.browser`).
Not sandboxed:
- The Gateway process itself.
- Any tool explicitly allowed to run on the host (e.g. `tools.elevated`).
- **Elevated bash runs on the host and bypasses sandboxing.**
- If sandboxing is off, `tools.elevated` does not change execution (already on host). See [Elevated Mode](/tools/elevated).
## Modes
`agents.defaults.sandbox.mode` controls **when** sandboxing is used:
- `"off"`: no sandboxing.
- `"non-main"`: sandbox only **non-main** sessions (default if you want normal chats on host).
- `"all"`: every session runs in a sandbox.
Note: `"non-main"` is based on `session.mainKey` (default `"main"`), not agent id.
Group/channel sessions use their own keys, so they count as non-main and will be sandboxed.
## Scope
`agents.defaults.sandbox.scope` controls **how many containers** are created:
- `"session"` (default): one container per session.
- `"agent"`: one container per agent.
- `"shared"`: one container shared by all sandboxed sessions.
## Workspace access
`agents.defaults.sandbox.workspaceAccess` controls **what the sandbox can see**:
- `"none"` (default): tools see a sandbox workspace under `~/.clawdbot/sandboxes`.
- `"ro"`: mounts the agent workspace read-only at `/agent` (disables `write`/`edit`).
- `"rw"`: mounts the agent workspace read/write at `/workspace`.
Inbound media is copied into the active sandbox workspace (`media/inbound/*`).
Skills note: the `read` tool is sandbox-rooted. With `workspaceAccess: "none"`,
Clawdbot mirrors eligible skills into the sandbox workspace (`.../skills`) so
they can be read. With `"rw"`, workspace skills are readable from
`/workspace/skills`.
## Images + setup
Default image: `clawdbot-sandbox:bookworm-slim`
Build it once:
```bash
scripts/sandbox-setup.sh
```
Sandboxed browser image:
```bash
scripts/sandbox-browser-setup.sh
```
By default, sandbox containers run with **no network**.
Override with `agents.defaults.sandbox.docker.network`.
Docker installs and the containerized gateway live here:
[Docker](/install/docker)
## Tool policy + escape hatches
Tool allow/deny policies still apply before sandbox rules. If a tool is denied
globally or per-agent, sandboxing doesn’t bring it back.
`tools.elevated` is an explicit escape hatch that runs `bash` on the host.
Keep it locked down.
## Multi-agent overrides
Each agent can override sandbox + tools:
`agents.list[].sandbox` and `agents.list[].tools` (plus `agents.list[].tools.sandbox.tools` for sandbox tool policy).
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for precedence.
## Minimal enable example
```json5
{
agents: {
defaults: {
sandbox: {
mode: "non-main",
scope: "session",
workspaceAccess: "none"
}
}
}
}
```
## Related docs
- [Sandbox Configuration](/gateway/configuration#agentsdefaults-sandbox)
- [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools)
- [Security](/gateway/security)
Loaded 100 of 615 files, more files were not shown because too many files have changed in this diff. Show more