Commit Graph
74 Commits
Author SHA1 Message Date
Gavriel CohenandClaude Opus 4.6 b5a6757211 fix: pass requiresTrigger through IPC and auto-discover additional directories
- 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>
2026-02-13 12:18:16 +02:00
Gavriel CohenandClaude Opus 4.6 acdc6454db fix: WhatsApp auth improvements and LID translation for DMs
- 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>
2026-02-12 22:49:04 +02:00
gavrielcandClaude Opus 4.6 464735347c chore: add /groups/ and /launchd/ to CODEOWNERS
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 10:18:15 +02:00
gavrielcandClaude Opus 4.6 8eb80d4ed0 fix: prevent infinite message replay on container timeout (#164)
Container timeout and idle timeout both fire at 30min, racing the
graceful shutdown. The hard kill returns error status, rolling back
the message cursor even though output was already sent — causing
duplicate messages indefinitely.

- Grace period: hard timeout is now IDLE_TIMEOUT + 30s minimum
- Timeout after output resolves as success (idle cleanup, not failure)
- Don't roll back cursor if output was already sent to user
- Remove src/telegram.ts and config vars (added to PR #156 by mistake)
- Add typecheck step to CI workflow
- Add container-runner timeout behavior tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 17:25:42 +02:00
gavrielcandClaude Opus 4.6 2b56fecfdc Refactor index (#156)
* 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>
2026-02-11 00:36:37 +02:00
Gavriel CohenandClaude Opus 4.6 196abf67cf docs: clarify agent swarms vs teams in Telegram skill
Agent Teams work without swarm support — they just operate behind
the scenes. Swarm support makes subagents visible as separate bots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 10:15:22 +02:00
Gavriel CohenandClaude Opus 4.6 b3f5814f48 feat: move to Claude's native memory management
Enable CLAUDE_CODE_DISABLE_AUTO_MEMORY=0 and
CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD=1 in container env so
agents use Claude Code's built-in persistent memory instead of
manually editing CLAUDE.md. Remove instructions that told agents to
write context into CLAUDE.md files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:28:25 +02:00
gavrielcandClaude Opus 4.6 116fba1349 fix: bust shields.io cache for Discord badge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 06:25:15 +02:00
Gavriel CohenandClaude Opus 4.6 126b3f4a45 feat: add Telegram agent swarm skill
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 04:58:33 +02:00
gavrielcandClaude Opus 4.6 6f02ee530b Adds Agent Swarms
* 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>
2026-02-09 02:50:43 +02:00
gavrielcandClaude Opus 4.6 f26468c9b0 fix: setup skill reliability, requiresTrigger option, agent-browser visibility
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>
2026-02-07 01:39:31 +02:00
Gavriel CohenandClaude Opus 4.6 675ed30ba0 fix: improve container error logging to include full stdout/stderr
Always log detailed input/output/stderr on error (not just in verbose
mode), and stop truncating stderr/stdout in structured log fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 00:17:46 +02:00
gavrielcandClaude Opus 4.6 8dd27bc58d fix: defend against missing structured output and message without content
- 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>
2026-02-06 20:29:04 +02:00
gavrielc 2ecda36df2 small tweak to acknowledgement prompt 2026-02-06 20:25:21 +02:00
gavrielcandClaude Opus 4.6 44f0b3d99c fix: improve agent output schema, tool descriptions, and shutdown robustness
- 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>
2026-02-06 20:22:45 +02:00
gavrielcandClaude ae177156ec feat: per-group queue, SQLite state, graceful shutdown (#111)
* 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>
2026-02-06 18:54:26 +02:00
gavrielcandClaude Opus 4.6 03df69e9b5 fix: address review feedback for per-group queue reliability
- Fix startup recovery running before WhatsApp connects, which could
  permanently lose agent responses by advancing lastAgentTimestamp
  before sock is initialized
- Add 5s retry on container failure so messages aren't silently dropped
  until a new message arrives for the group
- Use `container stop` in shutdown instead of raw SIGTERM to CLI wrapper,
  ensuring proper container cleanup
- Replace unnecessary dynamic imports with static imports in processTaskIpc
- Guard JSON.parse of DB-stored last_agent_timestamp against corruption
- Validate MAX_CONCURRENT_CONTAINERS (default 5, min 1, NaN-safe)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 16:45:00 +02:00
gavrielcandClaude Opus 4.6 eac9a6acfd feat: per-group queue, SQLite state, graceful shutdown
Add per-group container locking with global concurrency limit to prevent
concurrent containers for the same group (#89) and cap total containers.
Fix message batching bug where lastAgentTimestamp advanced to trigger
message instead of latest in batch, causing redundant re-processing.
Move router state, sessions, and registered groups from JSON files to
SQLite with automatic one-time migration. Add SIGTERM/SIGINT handlers
with graceful shutdown (SIGTERM -> grace period -> SIGKILL). Add startup
recovery for messages missed during crash. Remove dead code: utils.ts,
Session type, isScheduledTask flag, ContainerConfig.env, getTaskRunLogs,
GroupQueue.isActive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 07:38:07 +02:00
gavrielcandClaude Opus 4.6 db216a459e fix: proper container lifecycle management to prevent stopped container accumulation
- Name containers (nanoclaw-{group}-{timestamp}) for trackability
- Replace SIGKILL timeout with graceful `container stop` so --rm fires
- Add startup sweep to clean up stopped nanoclaw containers from previous runs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 07:10:26 +02:00
Gavriel CohenandClaude Opus 4.5 3a4d340f80 Fix duplicate responses caused by reconnect-stacking loops
WhatsApp reconnections called startMessageLoop/startSchedulerLoop/
startIpcWatcher and setInterval again without stopping the previous
instances, creating parallel loops that processed the same messages.

Add guard flags so each loop starts only once per process lifetime.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 00:18:55 +02:00
Gavriel CohenandClaude Opus 4.5 7ff9a65792 Update setup skill to use claude setup-token for auth
Replace manual credential extraction with the proper CLI command.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 21:03:35 +02:00
gavrielcandClaude Opus 4.5 21c66df2b1 Add prettier
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 17:14:17 +02:00
gavrielcandClaude Opus 4.5 80e68dc00d Add social preview image
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:52:21 +02:00
gavrielcandClaude Opus 4.5 722351159e Add contribution guidelines and PR checks for skills-only model
- CONTRIBUTING.md: Explain accepted source changes vs skills
- PR template: Checkboxes for contribution type
- CI workflow: Block PRs that add skills while modifying source
- CODEOWNERS: Require maintainer review for source changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:50:25 +02:00
gavrielcandClaude Opus 4.5 ff23125800 Add Docker support and integrate /convert-to-docker into setup flow
- Update setup skill to detect platform and offer Docker/Apple Container choice
- On Linux, automatically use Docker via /convert-to-docker skill
- On macOS, ask user preference if Apple Container not installed
- Update README to reflect Docker support and Linux compatibility
- Fix exact line number reference in convert-to-docker skill
- Add thank you to @dotsetgreg for the Docker skill contribution

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:35:27 +02:00
gavrielcandClaude Opus 4.5 849b22400b Fix minor issues in add-parallel skill
- Remove unrelated ASSISTANT_NAME from allowedVars example
- Fix log message reference (remove incorrect [agent-runner] prefix)
- Revert unrelated .gitignore addition (.backups/)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 13:23:28 +02:00
gavrielc fb5dbcbc12 Update README.md 2026-02-02 02:42:42 +02:00
gavrielc 40d41542d2 Update README.md 2026-02-02 01:39:37 +02:00
gavrielc 98f82b7645 Update README.md 2026-02-02 01:08:59 +02:00
gavrielc d20df2e785 Update README.md 2026-02-02 00:56:01 +02:00
gavrielcandClaude Opus 4.5 05a29d562f Security improvements: per-group session isolation, remove built-in Gmail
- 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>
2026-02-02 00:07:59 +02:00
gavrielcandClaude Opus 4.5 17f7b84420 Add /add-gmail skill for Gmail integration
Skill guides users through adding Gmail to NanoClaw with two modes:
- Tool Mode: Agent can read/send emails when triggered from WhatsApp
- Channel Mode: Emails can trigger the agent and receive replies

Includes step-by-step GCP OAuth setup, code integration instructions,
and troubleshooting guidance.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 23:52:05 +02:00
gavrielcandClaude d000f33928 Add container output size limiting to prevent memory issues (#18)
* Fix potential memory DoS via unbounded container output

Add CONTAINER_MAX_OUTPUT_SIZE (default 10MB) to limit accumulated
stdout/stderr from container processes. Without this limit, a malicious
or buggy container could emit huge output leading to host memory
exhaustion.

Changes:
- Add configurable CONTAINER_MAX_OUTPUT_SIZE in config.ts
- Implement size-limited output buffering in runContainerAgent
- Log warnings when truncation occurs
- Include truncation status in container logs

https://claude.ai/code/session_01TjVDwwaGwbcFDdmrFF2y8B

* Update package-lock.json

https://claude.ai/code/session_01TjVDwwaGwbcFDdmrFF2y8B

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:09:50 +02:00
gavrielcandClaude 33ef0c68d3 Fix message cursor to only advance on successful processing (#17)
Previously, lastTimestamp was unconditionally advanced after each message,
even if processMessage failed. This caused transient errors to permanently
drop messages since they would never be retried.

Now the cursor only advances after successful processing, implementing
at-least-once delivery semantics. On failure, the loop breaks and the
failed message will be retried on the next poll iteration.

https://claude.ai/code/session_01SEQDWxXeZHe7t1bb5cw2CA

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:05:37 +02:00
gavrielcandClaude c45f0efcdb Escape regex metacharacters in ASSISTANT_NAME for trigger pattern (#16)
ASSISTANT_NAME was interpolated directly into a regex without escaping.
If the name contained regex metacharacters (e.g., @A.*), the trigger
would match unintended patterns. This adds escapeRegex() to properly
escape special characters before building the TRIGGER_PATTERN.

https://claude.ai/code/session_01Lvuxq73qa9S4rtmGpX1WsP

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:05:13 +02:00
gavrielcandClaude e5b436ab48 Fix group metadata sync setting epoch timestamp for new groups (#15)
updateChatName() was inserting new groups with last_message_time set to
Unix epoch (1970-01-01), causing newly discovered groups to appear at
the bottom of activity-ordered listings. Changed to use current time
for new groups while preserving existing timestamps for known groups.

https://claude.ai/code/session_018rcZvALfF3ND2jfcvBaFo2

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 23:04:49 +02:00
gavrielcandClaude Opus 4.5 df52232763 Pre-launch fixes: error handling, cleanup, consistency
- Remove unused claude-agent-sdk from host deps (only used in container)
- Remove dead scheduler MCP config (built into IPC)
- Remove unused eslint script
- Add clear error message when Apple Container fails to start
- Auto-generate launchd plist with real paths in setup skill
- Standardize Node.js version to 20+ everywhere

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 23:01:04 +02:00
gavrielcandClaude 48822ff67d Add mount security allowlist for external directory access (#14)
* Add secure mount allowlist validation

Addresses arbitrary host mount vulnerability by validating additional
mounts against an external allowlist stored at ~/.config/nanoclaw/.
This location is never mounted into containers, making it tamper-proof.

Security measures:
- Allowlist cached in memory (edits require process restart)
- Real path resolution (blocks symlink and .. traversal attacks)
- Blocked patterns for sensitive paths (.ssh, .gnupg, .aws, etc.)
- Non-main groups forced to read-only when nonMainReadOnly is true
- Container path validation prevents /workspace/extra escape

https://claude.ai/code/session_01BPqdNy4EAHHJcdtZ27TXkh

* Add mount allowlist setup to /setup skill

Interactive walkthrough that asks users:
- Whether they want agents to access external directories
- Which directories to allow (with paths)
- Read-write vs read-only for each
- Whether non-main groups should be restricted to read-only

Creates ~/.config/nanoclaw/mount-allowlist.json based on answers.

https://claude.ai/code/session_01BPqdNy4EAHHJcdtZ27TXkh

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 22:55:08 +02:00
gavrielcandClaude Opus 4.5 066eeb9646 Make OpenClaw critique specific with actual numbers
52+ modules, 8 config files, 45+ deps, 15 channel providers.
Security is application-level (allowlists) vs actual container isolation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 22:36:45 +02:00
gavrielcandClaude Opus 4.5 016a1a0e31 Add group metadata sync for easier group activation
- Sync group names from WhatsApp via groupFetchAllParticipating()
- Store group names in chats table (jid -> name mapping)
- Daily sync with 24h cache, on-demand refresh via IPC
- Write available_groups.json snapshot for agent (main group only)
- Agent can request refresh_groups via IPC if group not found
- Update documentation in main CLAUDE.md and debug skill

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 22:25:29 +02:00
gavrielcandClaude Opus 4.5 f6e7f7aca9 Make main group respond to all messages without trigger prefix
Other groups still require @Andy prefix to trigger the agent.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:49:39 +02:00
gavrielcandClaude Opus 4.5 6745a1c54b Apply fixes from closed PRs: sentinel markers, JID lookup, schedule validation
- 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>
2026-02-01 20:49:57 +02:00
gavrielc ade9f2d323 Merge pull request #3 from gavrielc/claude/secure-ipc-access-Ni9l4
Secure IPC with per-group namespaces to prevent privilege escalation
2026-02-01 20:40:27 +02:00
gavrielc febf90f3c8 Merge pull request #12 from gavrielc/claude/fix-agent-failure-timestamp-yiOZt
Fix: only update lastAgentTimestamp on agent success
2026-02-01 20:40:15 +02:00
gavrielc 8f89f67c4b Merge pull request #11 from gavrielc/claude/fix-message-loss-error-DJwye
Fix message loss when processMessage throws
2026-02-01 20:40:13 +02:00
gavrielc 7310c5ba04 Merge pull request #9 from gavrielc/claude/fix-sensitive-log-data-xb0E8
Remove message content from info-level logs
2026-02-01 20:40:05 +02:00
gavrielc 069bc76016 Merge pull request #7 from gavrielc/claude/fix-home-directory-fallback-FF5Tr
Fix hardcoded home directory fallback in container runner
2026-02-01 20:40:02 +02:00
gavrielc 30bc2262e9 Merge pull request #2 from gavrielc/claude/fix-dotenv-exposure-LEzJ8
Fix security: only expose auth vars to containers, not full .env
2026-02-01 20:39:59 +02:00
gavrielcandClaude Opus 4.5 c255451ac3 Move Quick Start section above Philosophy
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 17:53:54 +02:00
gavrielc 1699dfc3d1 Update README.md 2026-02-01 17:51:59 +02:00
gavrielcandClaude Opus 4.5 479ca166ca Add NanoClaw logo and branding assets
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 17:45:05 +02:00
gavrielcandClaude Opus 4.5 17e7b469f4 Refactor: delete dead code, extract utils, rename files for clarity
- Delete scheduler-mcp.ts (285 lines of dead code, unused)
- Extract loadJson/saveJson to utils.ts (generic utilities)
- Rename auth.ts → whatsapp-auth.ts (more specific)
- Rename scheduler.ts → task-scheduler.ts (more specific)
- Update all references in docs and imports

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 17:35:03 +02:00
gavrielcandClaude Opus 4.5 f25e0f9a10 Remove redundant comments throughout codebase
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>
2026-02-01 16:00:44 +02:00
gavrielcandClaude Opus 4.5 732c624e6b Fix security issues: IPC auth, message logging, container logs
- Add authorization checks to IPC task operations (pause/resume/cancel)
  to prevent cross-group task manipulation
- Only store message content for registered groups; unregistered chats
  only get metadata stored for group discovery
- Container logs now only include full input/output in debug mode;
  default logging omits sensitive message content

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 15:51:53 +02:00
gavrielcandClaude Opus 4.5 552b26cc95 Add PreCompact hook for conversation archiving, remove /clear command
- 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>
2026-02-01 15:37:13 +02:00
gavrielcandClaude Opus 4.5 2026eaf53d Clean up README prose and add contribution FAQ
- Remove dash-as-em-dash patterns throughout
- Add FAQ about what changes are accepted (security, bugs, clear fixes)
- Clarify that enhancements should be skills, not PRs
- Fix "Leverage" → "Use" in REQUIREMENTS.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 13:43:11 +02:00
gavrielcandClaude Opus 4.5 0ccdaaac48 Mount project root for main channel
- Main gets /workspace/project with full project access
- Main can query SQLite database and edit configs
- Updated main CLAUDE.md with container paths
- Added docs for configuring additional mounts per group

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 23:01:45 +02:00
gavrielcandClaude Opus 4.5 ef24c45413 Update setup skill for container architecture
- Add Apple Container installation step
- Add container image build step
- Renumber subsequent steps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 22:57:12 +02:00
gavrielcandClaude Opus 4.5 09c0e8142e Add containerized agent execution with Apple Container
- 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>
2026-01-31 22:55:57 +02:00
gavrielcandClaude Opus 4.5 fa13b14dae Add built-in scheduler with group-scoped tasks
- Custom nanoclaw MCP server with scheduling tools (schedule_task,
  list_tasks, get_task, update_task, pause/resume/cancel_task, send_message)
- Tasks run as full agents in their group's context
- Support for cron, interval, and one-time schedules
- Task run logging with duration and results
- Main channel has Bash access for admin tasks (query DB, manage groups)
- Other groups restricted to file operations only
- Updated docs and requirements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 21:00:37 +02:00
gavrielcandClaude Opus 4.5 423d45c52e Update docs to reflect current architecture
- SPEC.md: Add new source files, update config location, document
  conversation catch-up feature, fix message flow description
- customize/SKILL.md: Fix file references (was Python, now TypeScript),
  update launchd service name

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:36:22 +02:00
gavrielcandClaude Opus 4.5 0c08e8a034 Use date + time format in message timestamps
Format: [Jan 31 2:35 PM] instead of [14:35:00]
Date provides important context for ongoing conversations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:33:11 +02:00
gavrielcandClaude Opus 4.5 22bd3d7c58 Store and display sender's WhatsApp name
Use pushName from baileys to get the sender's display name instead
of just the phone number. Falls back to phone number if no name.

Includes migration to add sender_name column to existing databases.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:32:18 +02:00
gavrielcandClaude Opus 4.5 cbe33f4ba6 Keep trigger in prompt, simplify message formatting
Include the full message with @trigger so agent sees exactly
what was written. Current message is already in missedMessages
query so no need to add separately.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:30:32 +02:00
gavrielcandClaude Opus 4.5 f2afb11e71 Include missed messages when catching up the agent
When a triggered message comes in, fetch all messages in that chat
since the last agent interaction and include them in the prompt.
Each message is formatted with timestamp and sender.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:29:09 +02:00
gavrielcandClaude Opus 4.5 4ec74e9120 Simplify runAgent: just pass the prompt
- Remove context building (group name, chat JID, permissions)
- Agent runs in group folder which determines its permissions
- Caller handles response prefixing and where to send reply
- Session provides continuity, no need for metadata in each message

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:24:55 +02:00
gavrielcandClaude Opus 4.5 545cbc7b9c Remove unnecessary shutdown handlers
Daemon runs forever; launchd manages lifecycle. SQLite handles
ungraceful shutdowns fine.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:21:59 +02:00
gavrielcandClaude Opus 4.5 0691601469 Extract database operations into separate db.ts module
- src/db.ts: initDatabase, closeDatabase, storeMessage, getNewMessages
- Removes SQL from index.ts
- Database initialization happens once at startup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:20:41 +02:00
gavrielcandClaude Opus 4.5 78426c764d Extract config and types into separate files, clean up index.ts
- src/config.ts: configuration constants
- src/types.ts: TypeScript interfaces
- src/index.ts: remove section comments, streamline code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:17:40 +02:00
gavrielcandClaude Opus 4.5 fe5ae974a3 Improve setup skill: better Gmail explanation, use placeholders
- Explain Gmail/GCP prerequisites upfront before asking
- Replace hardcoded "Andy" with ASSISTANT_NAME placeholder
- Replace hardcoded timestamp with CURRENT_ISO_TIMESTAMP
- Add step 3 to ask user for their preferred trigger word
- Renumber sections accordingly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:12:19 +02:00
gavrielcandClaude Opus 4.5 8a385c7988 Separate WhatsApp auth from daemon into standalone script
- Add src/auth.ts for interactive QR code authentication
- Add `npm run auth` script
- Update setup skill for current Node.js architecture
- Daemon (src/index.ts) now only uses stored credentials

Auth is run during setup; daemon assumes credentials exist and
shows macOS notification if re-auth is needed.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:06:25 +02:00
gavrielcandClaude Opus 4.5 e1867f8d27 Replace QR code display with macOS notification
The daemon should never show interactive QR codes. If auth is needed,
send a macOS notification alerting the user to run /setup, then exit.

Also removes unused qrcode-terminal dependency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 19:01:41 +02:00
gavrielcandClaude Opus 4.5 295c3e620d Fix: exit gracefully when auth needed in daemon mode
If WhatsApp authentication is required but the process is running
non-interactively (as a launchd daemon), exit with an error instead
of showing a QR code that nobody will see.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:57:15 +02:00
gavrielcandClaude Opus 4.5 c17823a732 Initial commit: NanoClaw - Personal Claude assistant via WhatsApp
A minimal Node.js application that connects Claude Agent SDK to WhatsApp
using baileys. Features per-group memory via CLAUDE.md files, session
continuity, scheduled tasks, and Gmail integration via MCP.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 18:54:24 +02:00