Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ FROM node:22-slim
RUN apt-get update && apt-get install -y \
chromium \
fonts-liberation \
fonts-noto-cjk \
fonts-noto-color-emoji \
libgbm1 \
libnss3 \
Expand Down
148 changes: 84 additions & 64 deletions container/agent-runner/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion container/agent-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"test": "vitest run"
},
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.34",
"@anthropic-ai/claude-agent-sdk": "^0.2.68",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"zod": "^4.0.0"
Expand Down
11 changes: 11 additions & 0 deletions container/agent-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,17 @@ async function runQuery(
const errors = 'errors' in message ? (message as { errors?: string[] }).errors : undefined;
const errorText = errors?.length ? errors.join('; ') : null;
log(`Result #${resultCount}: subtype=${message.subtype} is_error=${isError}${textResult ? ` text=${textResult.slice(0, 200)}` : ''}${errorText ? ` errors=${errorText.slice(0, 200)}` : ''}`);

// Detect stale session errors — throw to trigger fresh session retry in main()
// instead of writing the error to stdout (which the host would treat as final)
if (isError) {
const combinedError = [textResult, errorText].filter(Boolean).join(' ');
if (/No message found with message\.uuid/i.test(combinedError)) {
ipcPolling = false;
throw new Error(combinedError);
}
}

writeOutput({
status: isError ? 'error' : 'success',
result: textResult || null,
Expand Down
25 changes: 25 additions & 0 deletions docs/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Upstream targets macOS with Apple Container. This fork adds full Docker support
- **Container shutdown**: `group-queue.ts` explicitly calls `docker stop` with 10s grace period during graceful shutdown
- **OAuth credential bind mount**: Host `~/.claude/.credentials.json` is bind-mounted directly into containers (file-level mount overlaying the session directory mount) instead of copied at spawn time. If any host process refreshes the token, containers see it immediately — eliminates stale token failures during long conversations
- **Auth error detection**: `orchestrator.ts` detects auth-specific error patterns (expired OAuth, invalid API key) and sends a targeted `[Auth Error]` notification to the user immediately, even when the agent had already sent output earlier in the conversation. Prevents silent failures where auth expires mid-conversation
- **Host networking via `--add-host`**: Containers resolve `host.docker.internal` to the host's bridge IP using Docker's `--add-host=host.docker.internal:host-gateway` flag (added to `extraRunArgs()` in `container-runtime.ts`). This replaces the previous socat bridge approach for host-to-container networking (e.g., claude-mem worker access), eliminating a separate systemd service

---

Expand Down Expand Up @@ -249,6 +250,24 @@ Upstream targets macOS with Apple Container. This fork adds full Docker support
| `CLAUDE.md` | Added security section referencing SECURITY.md |
| `groups/main/CLAUDE.md` | Anti-prompt-injection rules |

### Sender allowlist

Per-chat access control for who can trigger the bot or have their messages stored. Config at `~/.config/nanoclaw/sender-allowlist.json`. Two modes:
- **trigger** (default): messages are stored but only allowed senders can trigger the bot
- **drop**: messages from disallowed senders are not stored at all

Fail-open: if config file is missing, all senders are allowed (backwards compatible).

| File | Purpose |
|------|---------|
| `src/sender-allowlist.ts` | Loads config, exports `isSenderAllowed()`, `shouldDropMessage()`, `isTriggerAllowed()` |
| `src/__tests__/sender-allowlist.test.ts` | Test suite (14 tests) |
| `src/config.ts` | `SENDER_ALLOWLIST_PATH` constant |
| `src/orchestrator.ts` | `isTriggerAllowed()` check alongside trigger pattern in both `processGroupMessages()` and `startMessageLoop()` |
| `src/index.ts` | Drop-mode check in `onMessage` callback — skips `storeMessage()` for disallowed senders |

Port of upstream `4de981b`.

### How secrets work in containers

Upstream passes secrets via environment variables, which are visible to `env` and `/proc/*/environ`. This fork takes a different approach:
Expand Down Expand Up @@ -406,6 +425,12 @@ These are clean fixes submitted to upstream. If they merge, the divergences coll
- **Hook error handling** — `startup()`, `runInboundHooks()`, and `runOutboundHooks()` now catch and log errors per-plugin instead of letting one failing plugin take down the chain (`src/plugin-loader.ts`)
- **WhatsApp exponential backoff** — Reconnect logic now uses exponential backoff (2s → 4s → 8s → ... capped at 5min) instead of a single 5s retry. Prevents log floods and resource exhaustion during persistent failures (port of upstream #466) (`plugins/channels/whatsapp/index.js`)
- **WhatsApp protocol message filter** — Skip `protocolMessage`, `reactionMessage`, and `editedMessage` early in the handler before JID translation and metadata updates. Saves cycles on system messages that carry no user text (port of upstream #491) (`plugins/channels/whatsapp/index.js`)
- **Atomic task claims** — `runningTaskId` tracking in `GroupState` prevents the scheduler from re-enqueuing a task that's already executing. Previously only `pendingTasks` was checked. (port of upstream `f794185`) (`src/group-queue.ts`)
- **Interval task drift fix** — `computeNextRun()` anchors interval tasks to their scheduled time and skips missed intervals (`while (next <= now) next += ms`) instead of using `Date.now() + ms` which accumulates drift. (port of upstream `f794185`) (`src/task-scheduler.ts`)
- **WhatsApp message handler resilience** — Inner `messages.upsert` loop wrapped in try-catch so one malformed message doesn't crash processing for the entire batch. Error logged with `remoteJid` for debugging. (port of upstream `5e3d8b6`) (`plugins/channels/whatsapp/index.js`)
- **Third-party model env vars** — `readSecrets()` now includes `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` for third-party API providers. (port of upstream `51bb329`) (`src/container-mounts.ts`)
- **CJK fonts** — Added `fonts-noto-cjk` to container Dockerfile for Chinese/Japanese/Korean text rendering. (port of upstream `d48ef91`) (`container/Dockerfile`)
- **SDK update** — Bumped `@anthropic-ai/claude-agent-sdk` from `^0.2.34` to `^0.2.68` in agent-runner. (port of upstream `5955cd6`) (`container/agent-runner/package.json`)

---

Expand Down
Loading
Loading