* feat: implement credential proxy for enhanced container environment isolation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR review — bind proxy to loopback, scope OAuth injection, add tests
- Bind credential proxy to 127.0.0.1 instead of 0.0.0.0 (security)
- OAuth mode: only inject Authorization on token exchange endpoint
- Add 5 integration tests for credential-proxy.ts
- Remove dangling comment
- Extract host gateway into container-runtime.ts abstraction
- Update Apple Container skill for credential proxy compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: scope OAuth token injection by header presence instead of path
Path-based matching missed auth probe requests the CLI sends before
the token exchange. Now the proxy replaces Authorization only when
the container actually sends one, leaving x-api-key-only requests
(post-exchange) untouched.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind credential proxy to docker0 bridge IP on Linux
On bare-metal Linux Docker, containers reach the host via the bridge IP
(e.g. 172.17.0.1), not loopback. Detect the docker0 interface address
via os.networkInterfaces() and bind there instead of 0.0.0.0, so the
proxy is reachable by containers but not exposed to the LAN.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: bind credential proxy to loopback on WSL
WSL uses Docker Desktop with the same VM routing as macOS, so
127.0.0.1 is correct and secure. Without this, the fallback to
0.0.0.0 was triggered because WSL has no docker0 interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: detect WSL via /proc instead of env var
WSL_DISTRO_NAME isn't set under systemd. Use
/proc/sys/fs/binfmt_misc/WSLInterop which is always present on WSL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The send_message tool description incorrectly stated that a scheduled
task's final output is not delivered to the user, instructing agents to
use the MCP tool for any communication. In reality, task-scheduler.ts
unconditionally forwards the agent's result to the user via a streaming
output callback (deps.sendMessage), which is a direct call to the
channel layer — entirely separate from the MCP tool path.
This caused agents following the description to call send_message
explicitly, resulting in duplicate messages: once via MCP and once via
the native streaming callback.
- Remove the incorrect note from the send_message tool description
- Fix the misleading comment at task-scheduler.ts which attributed
result delivery to the MCP tool rather than the streaming callback
schedule_task was creating duplicate tasks when users asked to modify
a schedule, because the agent had no way to update an existing task
and didn't know the ID of the task it created. Now schedule_task
generates and returns the task ID, and a new update_task tool allows
modifying prompt, schedule_type, and schedule_value in place.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: implement channel architecture and dynamic setup
- Introduced ChannelRegistry for dynamic channel loading
- Decoupled WhatsApp from core index.ts and config.ts
- Updated setup wizard to support ENABLED_CHANNELS selection
- Refactored IPC and group registration to be channel-aware
- Verified with 359 passing tests and clean typecheck
* style: fix formatting in config.ts to pass CI
* refactor(setup): full platform-agnostic transformation
- Harmonized all instructional text and help prompts
- Implemented conditional guards for WhatsApp-specific steps
- Normalized CLI terminology across all 4 initial channels
- Unified troubleshooting and verification logic
- Verified 369 tests pass with clean typecheck
* feat(skills): transform WhatsApp into a pluggable skill
- Created .claude/skills/add-whatsapp with full 5-phase interactive setup
- Fixed TS7006 'implicit any' error in IpcDeps
- Added auto-creation of STORE_DIR to prevent crashes on fresh installs
- Verified with 369 passing tests and clean typecheck
* refactor(skills): move WhatsApp from core to pluggable skill
- Move src/channels/whatsapp.ts to add-whatsapp skill add/ folder
- Move src/channels/whatsapp.test.ts to skill add/ folder
- Move src/whatsapp-auth.ts to skill add/ folder
- Create modify/ for barrel file (src/channels/index.ts)
- Create tests/ with skill package validation test
- Update manifest with adds/modifies lists
- Remove WhatsApp deps from core package.json (now skill-managed)
- Remove WhatsApp-specific ghost language from types.ts
- Update SKILL.md to reflect skill-apply workflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(skills): move setup/whatsapp-auth.ts into WhatsApp skill
The WhatsApp auth setup step is channel-specific — move it from core
to the add-whatsapp skill so core stays minimal.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(skills): convert Telegram skill to pluggable channel pattern
Replace the old direct-integration approach (modifying src/index.ts,
src/config.ts, src/routing.test.ts) with self-registration via the
channel registry, matching the WhatsApp skill pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(skills): fix add-whatsapp build failure and improve auth flow
- Add missing @types/qrcode-terminal to manifest npm_dependencies
(build failed after skill apply without it)
- Make QR-browser the recommended auth method (terminal QR too small,
pairing codes expire too fast)
- Remove "replace vs alongside" question — channels are additive
- Add pairing code retry guidance and QR-browser fallback
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove hardcoded WhatsApp default and stale Baileys comment
- ENABLED_CHANNELS now defaults to empty (fresh installs must configure
channels explicitly via /setup; existing installs already have .env)
- Remove Baileys-specific comment from storeMessageDirect() in db.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(skills): convert Discord, Slack, Gmail skills to pluggable channel pattern
All channel skills now use the same self-registration pattern:
- registerChannel() factory at module load time
- Barrel file append (src/channels/index.ts) instead of orchestrator modifications
- No more *_ONLY flags (DISCORD_ONLY, SLACK_ONLY) — use ENABLED_CHANNELS instead
- Removed ~2500 lines of old modify/ files (src/index.ts, src/config.ts, src/routing.test.ts)
Gmail retains its container-runner.ts and agent-runner modifications (MCP
mount + server config) since those are independent of channel wiring.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: use getRegisteredChannels instead of ENABLED_CHANNELS
Remove the ENABLED_CHANNELS env var entirely. The orchestrator now
iterates getRegisteredChannelNames() from the channel registry —
channels self-register via barrel imports and their factories return
null when credentials are missing, so unconfigured channels are
skipped automatically.
Deleted setup/channels.ts (and its tests) since its sole purpose was
writing ENABLED_CHANNELS to .env. Refactored verify, groups, and
environment setup steps to detect channels by credential presence
instead of reading ENABLED_CHANNELS.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add breaking change notice and whatsapp migration instructions
CHANGELOG.md documents the pluggable channel architecture shift and
provides migration steps for existing WhatsApp users.
CLAUDE.md updated: Quick Context reflects multi-channel architecture,
Key Files lists registry.ts instead of whatsapp.ts, and a new
Troubleshooting section directs users to /add-whatsapp if WhatsApp
stops connecting after upgrade.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: rewrite READMEs for pluggable multi-channel architecture
Reflects the architectural shift from a hardcoded WhatsApp bot to a
pluggable channel platform. Adds upgrading notice, Mermaid architecture
diagram, CI/License/TypeScript/PRs badges, and clarifies that slash
commands run inside the Claude Code CLI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: move pluggable channel architecture details to SPEC.md
Revert READMEs to original tone with only two targeted changes:
- Add upgrading notice for WhatsApp breaking change
- Mention pluggable channels in "What It Supports"
Move Mermaid diagram, channel registry internals, factory pattern
explanation, and self-registration walkthrough into docs/SPEC.md.
Update stale WhatsApp-specific references in SPEC.md to be
channel-agnostic.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: move upgrading notice to CHANGELOG, add changelog link
Remove the "Upgrading from Pre-Pluggable Versions" section from
README.md — breaking change details belong in the CHANGELOG. Add a
Changelog section linking to CHANGELOG.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: expand CHANGELOG with full PR #500 changes
Cover all changes: channel registry, WhatsApp moved to skill, removed
core dependencies, all 5 skills simplified, orchestrator refactored,
setup decoupled. Use Claude Code CLI instructions for migration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: bump version to 1.2.0 for pluggable channel architecture
Minor version bump — new functionality (pluggable channels) with a
managed migration path for existing WhatsApp users. Update version
references in CHANGELOG and update skill.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix skill application
* fix: use slotted barrel file to prevent channel merge conflicts
Pre-allocate a named comment slot for each channel in
src/channels/index.ts, separated by blank lines. Each skill's
modify file only touches its own slot, so three-way merges
never conflict when applying multiple channels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve real chat ID during setup for token-based channels
Instead of registering with `pending@telegram` (which never matches
incoming messages), the setup skill now runs an inline bot that waits
for the user to send /chatid, capturing the real chat ID before
registration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: setup delegates to channel skills, fix group sync and Discord metadata
- Restructure setup SKILL.md to delegate channel setup to individual
channel skills (/add-whatsapp, /add-telegram, etc.) instead of
reimplementing auth/registration inline with broken placeholder JIDs
- Move channel selection to step 5 where it's immediately acted on
- Fix setup/groups.ts: write sync script to temp file instead of passing
via node -e which broke on shell escaping of newlines
- Fix Discord onChatMetadata missing channel and isGroup parameters
- Add .tmp-* to .gitignore for temp sync script cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: align add-whatsapp skill with main setup patterns
Add headless detection for auth method selection, structured inline
error handling, dedicated number DM flow, and reorder questions to
match main's trigger-first flow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add missing auth script to package.json
The add-whatsapp skill adds src/whatsapp-auth.ts but doesn't add
the corresponding npm script. Setup and SKILL.md reference `npm run auth`
for WhatsApp QR terminal authentication.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update Discord skill tests to match onChatMetadata signature
The onChatMetadata callback now takes 5 arguments (jid, timestamp,
name, channel, isGroup) but the Discord skill tests only expected 3.
This caused skill application to roll back on test failure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: replace 'pluggable' jargon with clearer language
User-facing text now says "multi-channel" or describes what it does.
Developer-facing text uses "self-registering" or "channel registry".
Also removes extra badge row from README.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: align Chinese README with English version
Remove extra badges, replace pluggable jargon, remove upgrade section
(now in CHANGELOG), add missing intro line and changelog section,
fix setup FAQ answer.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: warn on installed-but-unconfigured channels instead of silent skip
Channels with missing credentials now emit WARN logs naming the exact
missing variable, so misconfigurations surface instead of being hidden.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: simplify changelog to one-liner with compare link
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add isMain flag and channel-prefixed group folders
Replace MAIN_GROUP_FOLDER constant with explicit isMain boolean on
RegisteredGroup. Group folders now use channel prefix convention
(e.g., whatsapp_main, telegram_family-chat) to prevent cross-channel
collisions.
- Add isMain to RegisteredGroup type and SQLite schema (with migration)
- Replace all folder-based main group checks with group.isMain
- Add --is-main flag to setup/register.ts
- Strip isMain from IPC payload (defense in depth)
- Update MCP tool description for channel-prefixed naming
- Update all channel SKILL.md files and documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: gavrielc <gabicohen22@yahoo.com>
Co-authored-by: Koshkoshinski <daniel.milliner@gmail.com>
Containers had no TZ set, so any time-aware code inside ran in UTC while
the host interpreted bare timestamps as local time. Now TIMEZONE from
config.ts is passed via -e TZ= to the container args.
Also rejects Z-suffixed or offset-suffixed timestamps in the container's
schedule_task validation, since bare timestamps are expected to be local
time and silently accepting UTC suffixes would cause an offset mismatch.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The container agent-runner had 'Andy' hardcoded as the sender name in
archived conversation transcripts. This ignored the configurable
ASSISTANT_NAME setting, so users who changed their assistant's name
(via .env or config) would still see 'Andy' in transcripts.
- Add assistantName field to ContainerInput interface (both host and
container copies)
- Pass ASSISTANT_NAME from config through to container in index.ts
and task-scheduler.ts
- Thread assistantName through createPreCompactHook and
formatTranscriptMarkdown in the agent-runner
- Use 'AssistantNameMissing' as fallback instead of 'Andy' so a
missing name is visible rather than silently wrong
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pass secrets to the SDK via the `env` query option instead of setting
process.env, so Bash subprocesses never inherit API keys. Delete
/tmp/input.json immediately after reading to remove secrets from disk.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Use a PreToolUse SDK hook to prepend `unset ANTHROPIC_API_KEY
CLAUDE_CODE_OAUTH_TOKEN` to every Bash command Kit runs, preventing
secret leakage via env/printenv/echo/$PROC. Secrets are now passed
via stdin JSON instead of mounted env files, closing all known
exfiltration vectors.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- IPC register_group handler now passes requiresTrigger field to registerGroup(),
fixing groups silently defaulting to trigger-required mode
- Agent runner scans /workspace/extra/* and passes them as additionalDirectories
to the SDK query, so CLAUDE.md files in mounted dirs are loaded automatically
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>
* feat: streaming container mode, IPC messaging, agent teams support
Major architectural shift from single-shot container runs to long-lived
streaming containers with IPC-based message injection.
- Agent runner: query loop with AsyncIterable prompt to keep stdin open
for agent teams (fixes isSingleUserTurn premature shutdown)
- New standalone stdio MCP server (ipc-mcp-stdio.ts) inheritable by
subagents, with send_message and schedule_task tools
- Streaming output: parse OUTPUT_START/END markers in real-time, send
results to WhatsApp as they arrive
- IPC file-based messaging: host writes to ipc/{group}/input/, agent
polls for follow-up messages without respawning containers
- Per-group settings.json with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1
- SDK bumped to 0.2.34 for TeamCreate tool support
- Container idle timeout (30min) with _close sentinel for shutdown
- Orphaned container cleanup on startup
- alwaysRespond flag for groups that skip trigger pattern check
- Uncaught exception/rejection handlers with timestamps in logger
- Combined SDK documentation into single deep dive reference
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: remove unused ipc-mcp.ts (replaced by ipc-mcp-stdio.ts)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: clarify agent communication model in docs and tool descriptions
- CLAUDE.md (main + global): split communication instructions into
"responding to messages" vs "scheduled tasks" sections
- send_message tool: note that scheduled task output is not sent to user
- Remove structured output (outputFormat) — not needed with current flow
- Regular output is sent to WhatsApp; scheduled task output is only logged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: ignore dynamic group data while preserving base structure
Only track groups/main/CLAUDE.md and groups/global/CLAUDE.md. All other
group directories and files are ignored to prevent tracking user-specific
session data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve critical bugs in streaming container mode
Bug 1 (scheduled task hang): Task scheduler now passes onOutput callback
with idle timer that writes _close sentinel after IDLE_TIMEOUT, so
containers exit cleanly instead of blocking queue slots for 30 minutes.
Scheduled tasks stay alive for interactive follow-up via IPC.
Bug 2 (timeout disabled): Remove resetTimeout() from stderr handler.
SDK writes debug logs continuously, resetting the timer on every line.
Timeout now only resets on actual output markers in stdout.
Bug 3 (trigger bypass): Piped messages in startMessageLoop now check
trigger pattern for non-main groups. Non-trigger messages accumulate in
DB and are pulled as context via getMessagesSince when a trigger arrives.
Bug 7 (non-atomic IPC writes): GroupQueue.sendMessage uses temp file +
rename for atomic writes, matching ipc-mcp-stdio.ts pattern.
Also: flip isVerbose back to false (debug leftover), add isScheduledTask
to host-side ContainerInput interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: idle timer not starting + scheduled task groupFolder missing
Two bugs that prevented the scheduled task idle timeout fix from working:
1. onOutput was only called when parsed.result !== null, but session
update markers have result: null. The idle timer never started for
"silent" query completions, leaving containers parked at
waitForIpcMessage until hard timeout.
2. Scheduler's onProcess callback didn't pass groupFolder to
queue.registerProcess, so closeStdin no-oped (groupFolder was null).
The _close sentinel was never written even when the idle timer fired.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: duplicate messages and timestamp rollback in piping path
Two bugs introduced by the trigger context accumulation change:
1. processGroupMessages didn't advance lastAgentTimestamp until after
the container finished. The piping path's getMessagesSince(lastAgent
Timestamp) re-fetched messages already sent as the initial prompt,
causing duplicates.
2. processGroupMessages overwrote lastAgentTimestamp with the original
batch timestamp on completion, rolling back any advancement made by
the piping path while the container was running.
Fix: advance lastAgentTimestamp immediately after building the prompt,
before starting the container. This matches the piping path behavior
and eliminates both the overlap and the rollback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: container idles 30 extra minutes after _close during query
When _close was detected during pollIpcDuringQuery, it was consumed
(deleted) and stream.end() was called. But after runQuery returned,
main() still emitted a session-update marker (resetting the host's idle
timer) and called waitForIpcMessage (which polled forever since _close
was already gone). The container had to wait for a second _close.
Fix: runQuery now returns closedDuringQuery. When true, main() skips
the session-update marker and waitForIpcMessage, exiting immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resume branching, internal tags, and output forwarding
- Fix resume branching: pass resumeSessionAt with last assistant UUID
to anchor each query loop resume to the correct conversation tree
position. Prevents agent responses landing on invisible branches
when agent teams subagents create parallel JSONL entries.
- Add <internal> tag stripping: agent can wrap internal reasoning in
<internal> tags which are logged but not sent to WhatsApp. Prevents
duplicate messages and internal monologue reaching users.
- Forward scheduled task output: scheduled tasks now send result text
to WhatsApp (with <internal> stripping), matching regular message
behavior. No more special-case instructions.
- Update Communication guidance in CLAUDE.md: simplified to "your
output is sent to the user or group" with soft guidance on
<internal> tags and send_message usage.
- Add messaging behavior docs to schedule_task tool: prompts the
scheduling agent to include guidance on whether the task should
always/conditionally/never message the user.
- Mount security: containerPath now optional, defaults to basename
of hostPath.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: cursor rollback on error, flush guard, verbose logging
- Roll back lastAgentTimestamp on container error so retries can
re-process the messages instead of silently losing them.
- Add guard flag to flushOutgoingQueue to prevent duplicate sends
from concurrent flushes during rapid WA reconnects.
- Revert isVerbose from hardcoded false back to env-based check
(LOG_LEVEL=debug|trace).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: orphan container cleanup was silently failing
The startup cleanup used `container ls --format {{.Names}}` which is
Docker Go-template syntax. Apple Container only supports `--format json`
or `--format table`. The command errored with exit code 64, but the
catch block silently swallowed it — orphan containers were never cleaned
up on restart.
Fixed to use `--format json` and parse `configuration.id` from the
JSON output. Also filters by `status: running` and logs a warning on
failure instead of silently catching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add Discord badge and community section
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: idle timer reset on null results and flush queue message loss
- Only reset idle timer on actual results (non-null), not session-update
markers. Prevents containers staying alive 30 extra minutes after the
agent finishes work.
- flushOutgoingQueue now uses shift() instead of splice(0) so unattempted
messages stay in the queue if an unexpected error bails the loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: add Agent Swarms to README
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update Telegram skill for current architecture
Rewrite integration instructions to match the per-group queue/SQLite
architecture: remove onMessage callback pattern (store to DB, let
message loop pick up), fix startSchedulerLoop signature, add
TELEGRAM_ONLY service startup, SQLite registration, data/env/env sync,
@mention-to-trigger translation, and BotFather group privacy docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: Telegram skill message chunking, media placeholders, chat discovery
- Split long messages at Telegram's 4096 char limit to prevent silent
send failures
- Store placeholder text for non-text messages (photos, voice, stickers,
etc.) so the agent knows media was sent
- Update getAvailableGroups filter to include tg: chats so the agent can
discover and register Telegram chats via IPC
- Fix removal step numbering
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update REQUIREMENTS.md and SPEC.md for SQLite architecture
- Replace all registered_groups.json / sessions.json / router_state.json
references with SQLite equivalents
- Fix CONTAINER_TIMEOUT default (300000 → 1800000)
- Add missing config exports (IDLE_TIMEOUT, MAX_CONCURRENT_CONTAINERS)
- Update folder structure: add missing src files (logger, group-queue,
mount-security), remove non-existent utils.ts, list all skills
- Fix agent-runner entry (ipc-mcp.ts → ipc-mcp-stdio.ts)
- Update startup sequence to reflect per-group queue architecture
- Fix env mounting description (data/env/env, not extracted vars)
- Update troubleshooting to use sqlite3 commands
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: fix README architecture description, revert SPEC.md env error
- README: update architecture blurb to mention per-group queue, add
group-queue.ts to key files, update file descriptions
- SPEC.md: restore correct credential filtering description (only auth
vars are extracted from .env, not the full file)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Setup skill fixes:
- Run QR auth in foreground with long timeout, not background
- Replace fragile message-based registration with DB group sync lookup
- Personal chats: ask for phone number instead of querying empty DB
- Consolidate trigger word + security model + channel selection into one step
- Remove `timeout` shell command (unavailable on macOS), use Bash tool timeout
- Query 40 groups, display 10 at a time, support name lookup
requiresTrigger support:
- Add requiresTrigger field to RegisteredGroup type and DB schema
- Skip trigger check when requiresTrigger is false (for solo/personal chats)
- Main group still always processes all messages (unchanged)
Agent-browser visibility:
- Append global CLAUDE.md to non-main agent system prompts via SDK
- Add browser tool docs to global and main CLAUDE.md
- Update skill description to be broader (not just "web testing")
- Reference agent-browser.md in root CLAUDE.md key files
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fall back to text result when success subtype has no structured_output
- Treat outputType 'message' without userMessage as 'log' with warning
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Rename status→outputType, responded/silent→message/log for clarity
- Remove scheduled task special-casing: userMessage now sent for all contexts
- Update schema, tool, and CLAUDE.md descriptions to be clear and
non-contradictory about communication mechanisms
- Use full tool name mcp__nanoclaw__send_message in docs
- Change schedule_task target_group to accept JID instead of folder name
- Only show target_group_jid parameter to main group agents
- Add defense-in-depth sanitization and error callback to exec() in shutdown
- Use "user or group" consistently (supports both 1:1 and group chats)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: wire up queue processMessagesFn before recovery to prevent silent message loss
recoverPendingMessages() was called after startMessageLoop(), which meant:
1. Recovery could race with the message loop's first iteration
2. processMessagesFn was set inside startMessageLoop, so recovery
enqueues would fire runForGroup with processMessagesFn still null,
silently skipping message processing
Move setProcessMessagesFn and recoverPendingMessages before startMessageLoop
so the queue is fully wired before any messages are enqueued.
https://claude.ai/code/session_01PCY8zNjDa2N29jvBAV5vfL
* feat: structured agent output to fix infinite retry on silent responses (#113)
Use Agent SDK's outputFormat with json_schema to get typed responses
from the agent. The agent now returns { status: 'responded' | 'silent',
userMessage?, internalLog? } instead of a plain string. This fixes a
critical bug where a null/empty agent response caused infinite 5-second
retry loops by conflating "nothing to say" with "error".
- Agent runner: add AGENT_RESPONSE_SCHEMA and parse structured_output
- Host: advance lastAgentTimestamp on both responded AND silent status
- GroupQueue: add exponential backoff (5s-80s) with max 5 retries for
actual errors, replacing unbounded fixed-interval retries
https://claude.ai/code/session_014SLc8MxP9BYhEhDCLox9U8
Co-authored-by: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Main agent can now register new groups via MCP tool without restart.
Host updates both in-memory state and JSON file, creates group folders.
Authorization enforced at both agent and host level.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Isolate Claude sessions per-group (data/sessions/{group}/.claude/)
to prevent cross-group access to conversation history
- Remove Gmail MCP from built-in (now available via /add-gmail skill)
- Add SECURITY.md documenting the security model
- Move docs to docs/ folder (SPEC.md, REQUIREMENTS.md, SECURITY.md)
- Update documentation to reflect changes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add TIMEZONE config using system timezone for cron expressions
- Filter bot messages by content prefix instead of is_from_me
(user shares WhatsApp account with bot)
- Format messages as XML for cleaner agent parsing
- Update schedule_task tool to clarify local time usage
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Scheduled tasks can now run in either:
- "group" mode: uses the group's conversation session for context
- "isolated" mode: runs with a fresh session (previous behavior)
The tool description guides the agent on when to use each mode and
prompts them to ask the user if unclear. Group mode is now the default.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- PR #10: Add sentinel markers for robust JSON parsing between container
and host. Fallback to last-line parsing for backwards compatibility.
- PR #5: Look up target JID from registeredGroups instead of trusting
IPC payload, fixing cross-group scheduled tasks getting wrong chat_jid.
- PR #8: Add lightweight schedule validation in container MCP that
returns errors to agents (cron syntax, positive interval, valid ISO
timestamp). Also defensive validation on host side.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix Apple Container mount issue: move groups/CLAUDE.md to groups/global/
directory (Apple Container only supports directory mounts, not file mounts)
- Fix scheduled tasks for main group: properly detect isMain based on
group_folder instead of always setting false
- Add isScheduledTask flag so agent knows when running as scheduled task
- Improve schedule_task tool description with clear format examples for
cron, interval, and once schedule types
- Update global CLAUDE.md with instructions for scheduled tasks to use
mcp__nanoclaw__send_message when needed
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Keep only comments that explain non-obvious behavior or add context
not apparent from reading the code.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add PreCompact hook in agent-runner that archives conversations before
compaction, using session summary from sessions-index.json for filename
- Remove /clear command (programmatic compaction not supported by SDK)
- Add /add-clear to RFS for future implementation
- Update CLAUDE.md templates with memory system instructions
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix session mount path: ~/.claude/ now mounts to /home/node/.claude/
(container runs as 'node' user with HOME=/home/node, not root)
- Fix ~/.gmail-mcp/ mount path similarly
- Use absolute paths for GROUPS_DIR and DATA_DIR (required for container mounts)
- Auto-start Apple Container system on NanoClaw startup
- Update debug skill with session troubleshooting guide
- Update spec.md with startup sequence and troubleshooting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Container fixes:
- Run as non-root 'node' user (required for --dangerously-skip-permissions)
- Add allowDangerouslySkipPermissions: true to SDK options
- Mount .env file to work around Apple Container -i env var bug
- Use --mount for readonly, -v for read-write (Apple Container quirk)
- Bump SDK to 0.2.29, zod to v4
- Install Claude Code CLI globally in container
Logging improvements:
- Write per-run logs to groups/{folder}/logs/container-*.log
- Add debug-level logging for mounts and container args
Documentation:
- Add /debug skill with comprehensive troubleshooting guide
- Update /setup skill with API key configuration step
- Update SPEC.md with container details, mount syntax, security notes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Agents run in isolated Linux VMs via Apple Container
- All groups get Bash access (safe - sandboxed in container)
- Browser automation via agent-browser + Chromium
- Per-group configurable additional directory mounts
- File-based IPC for messages and scheduled tasks
- Container image with Node.js 22, Chromium, agent-browser
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>