* fix(db): remove unique constraint on folder to support multi-channel agents
* ci: implement automated skill drift detection and self-healing PRs
* fix: align registration logic with Gavriel's feedback and fix build/test issues from Daniel Mi
* style: conform to prettier standards for CI validation
* test: fix branch naming inconsistency in CI (master vs main)
* fix(ci): robust module resolution by removing file extensions in scripts
* refactor(ci): simplify skill validation by removing redundant combination tests
* style: conform skills-engine to prettier, unify logging in index.ts and cleanup unused imports
* refactor: extract multi-channel DB changes to separate branch
Move channel column, folder suffix logic, and related migrations
to feat/multi-channel-db-v2 for independent review. This PR now
contains only CI/CD optimizations, Prettier formatting, and
logging improvements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The fetchLatestWaWebVersion call added in #443 could crash the
connection flow if the HTTP fetch fails. Wrap with .catch() to log
and fall back to the default Baileys version.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Baileys' default WhatsApp version can fall behind when WhatsApp updates
their protocol, causing 405 "Method Not Allowed" errors on the websocket
handshake. This prevents both new authentication and reconnection.
Now fetches the latest version on each connection so it stays current.
Applied to both the runtime connection (whatsapp.ts) and the setup auth
flow (whatsapp-auth.ts).
- Replace 'as any' with proper type definition for error status code access
- Add error logging to sendPresenceUpdate() catch block
- Add debug logging when .env file is not found
These changes improve type safety and visibility into potential failures
without changing any core functionality.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: gavrielc <gabicohen22@yahoo.com>
The test expected voice notes (audioMessage with no caption) to be
delivered with empty content, but 6f177ad added a guard that skips
messages with no text content. Update assertion accordingly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WhatsApp protocol messages (encryption key distribution, read receipts,
ephemeral settings, senderKeyDistributionMessage) have a message envelope
but no text content. These were being stored in messages.db and processed
by the agent, causing:
1. Agent responds to empty messages, wasting API tokens
2. Container stays alive indefinitely (idle timer resets)
3. Scheduled tasks blocked (queue slot occupied)
This fix skips messages with empty content before calling onMessage,
preventing protocol messages from being stored and processed.
Fixes#250
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor: multi-channel infrastructure with explicit channel/is_group tracking
- Add channels[] array and findChannel() routing in index.ts, replacing
hardcoded whatsapp.* calls with channel-agnostic callbacks
- Add channel TEXT and is_group INTEGER columns to chats table with
COALESCE upsert to protect existing values from null overwrites
- is_group defaults to 0 (safe: unknown chats excluded from groups)
- WhatsApp passes explicit channel='whatsapp' and isGroup to onChatMetadata
- getAvailableGroups filters on is_group instead of JID pattern matching
- findChannel logs warnings instead of silently dropping unroutable JIDs
- Migration backfills channel/is_group from JID patterns for existing DBs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: skills engine v0.1 — deterministic skill packages with rerere resolution
Three-way merge engine for applying skill packages on top of a core
codebase. Skills declare which files they add/modify, and the engine
uses git merge-file for conflict detection with git rerere for
automatic resolution of previously-seen conflicts.
Key components:
- apply: three-way merge with backup/rollback safety net
- replay: clean-slate replay for uninstall and rebase
- update: core version updates with deletion detection
- rebase: bake applied skills into base (one-way)
- manifest: validation with path traversal protection
- resolution-cache: pre-computed rerere resolutions
- structured: npm deps, env vars, docker-compose merging
- CI: per-skill test matrix with conflict detection
151 unit tests covering merge, rerere, backup, replay, uninstall,
update, rebase, structured ops, and edge cases.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Discord and Telegram skill packages
Skill packages for adding Discord and Telegram channels to NanoClaw.
Each package includes:
- Channel implementation (add/src/channels/)
- Three-way merge targets for index.ts, config.ts, routing.test.ts
- Intent docs explaining merge invariants
- Standalone integration tests
- manifest.yaml with dependency/conflict declarations
Applied via: npx tsx scripts/apply-skill.ts .claude/skills/add-discord
These are inert until applied — no runtime impact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* remove unused docs (skills-system-status, implementation-guide)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add is_bot_message column and support dedicated phone numbers
Replace fragile content-prefix bot detection with an explicit
is_bot_message database column. The old prefix check (content NOT LIKE
'Andy:%') is kept as a backstop for pre-migration messages.
- Add is_bot_message column with automatic backfill migration
- Add ASSISTANT_HAS_OWN_NUMBER env var to skip name prefix when the
assistant has its own WhatsApp number
- Move prefix logic into WhatsApp channel (no longer a router concern)
- Remove prefixAssistantName from Channel interface
- Load .env via dotenv so launchd-managed processes pick up config
- WhatsApp bot detection: fromMe for own number, prefix match for shared
Based on #160 and #173.
Co-Authored-By: Stefan Gasser <stefan@stefangasser.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract shared .env parser and remove dotenv dependency
Extract .env parsing into src/env.ts, used by both config.ts and
container-runner.ts. Reads only requested keys without loading secrets
into process.env, avoiding leaking API keys to child processes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Stefan Gasser <stefan@stefangasser.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two issues fixed:
- Use 'paused' instead of 'available' to stop typing. Baileys'
sendPresenceUpdate('available') sends a global <presence> stanza and
ignores the JID, so chatstate never left 'composing' and WhatsApp
suppressed duplicate composing notifications per XEP-0085.
- Add setTyping call when piping messages to an already-running
container. Previously only the first message (which spawns a new
container) triggered the typing indicator.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added missing Browsers mock to the Baileys vi.mock and made
triggerMessages async to flush microtasks before assertions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sending 'paused' after the first response caused WhatsApp to stop
relaying subsequent 'composing' presence updates. Using 'available'
keeps the bot in a state where typing indicators work consistently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without announcing 'available' after connecting, WhatsApp stops relaying
composing/paused presence updates after the first message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add pairing code auth with 515 reconnect handling (Baileys stream
error after pairing is now retried instead of failing)
- Use Browsers.macOS('Chrome') identifier for WhatsApp compatibility
- Fix LID-to-phone translation for DMs using signalRepository.getPNForLID
- Strip device suffix (:0) from resolved phone JIDs
- Update setup skill with three auth options (browser QR, pairing code,
terminal QR), DM channel type, and LID troubleshooting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add Telegram channel with agent swarm support
Add Telegram as a messaging channel that can run alongside WhatsApp
or standalone (TELEGRAM_ONLY mode). Includes bot pool support for
agent swarms where each subagent appears as a different bot identity
in the group.
- Add grammy dependency for Telegram Bot API
- Route messages through tg: JID prefix convention
- Add storeMessageDirect for non-Baileys channels
- Add sender field to IPC send_message for swarm identity
- Support TELEGRAM_BOT_TOKEN, TELEGRAM_ONLY, TELEGRAM_BOT_POOL config
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add index.ts refactor plan
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: extract channel abstraction, IPC, and router from index.ts
Break the 1088-line monolith into focused modules:
- src/channels/whatsapp.ts: WhatsAppChannel class implementing Channel interface
- src/ipc.ts: IPC watcher and task processing with dependency injection
- src/router.ts: message formatting, outbound routing, channel lookup
- src/types.ts: Channel interface, OnInboundMessage, OnChatMetadata types
Also adds regression test suite (98 tests), updates all documentation
and skill files to reflect the new architecture.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: add test workflow for PRs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove accidentally committed pool-bot assets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ci): remove grammy from base dependencies
Grammy is installed by the /add-telegram skill, not a base dependency.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>