From 032ba77a7fee9a3407bb03b8d49baa781eb45310 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Fri, 3 Apr 2026 16:17:57 +0300 Subject: [PATCH] feat: mount store rw for main agent and add requiresTrigger to register_group - Mount store/ separately as read-write so the main agent can access the SQLite database directly. - Add requiresTrigger parameter to the register_group MCP tool (host IPC already supported it, but the tool never exposed it). Defaults to false (no trigger). - Update group registration instructions to ask user about trigger. Co-Authored-By: Claude Opus 4.6 (1M context) --- container/agent-runner/src/ipc-mcp-stdio.ts | 7 +++++++ docs/SECURITY.md | 3 ++- groups/main/CLAUDE.md | 14 ++++++++------ src/container-runner.ts | 11 ++++++++++- 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/container/agent-runner/src/ipc-mcp-stdio.ts b/container/agent-runner/src/ipc-mcp-stdio.ts index fb429ed..989891b 100644 --- a/container/agent-runner/src/ipc-mcp-stdio.ts +++ b/container/agent-runner/src/ipc-mcp-stdio.ts @@ -460,6 +460,12 @@ Use available_groups.json to find the JID for a group. The folder name must be c 'Channel-prefixed folder name (e.g., "whatsapp_family-chat", "telegram_dev-team")', ), trigger: z.string().describe('Trigger word (e.g., "@Andy")'), + requiresTrigger: z + .boolean() + .optional() + .describe( + 'Whether messages must start with the trigger word. Default: false (respond to all messages). Set to true for busy groups with many participants where you only want the agent to respond when explicitly mentioned.', + ), }, async (args) => { if (!isMain) { @@ -480,6 +486,7 @@ Use available_groups.json to find the JID for a group. The folder name must be c name: args.name, folder: args.folder, trigger: args.trigger, + requiresTrigger: args.requiresTrigger ?? false, timestamp: new Date().toISOString(), }; diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 7cf29f8..dbfd6bf 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -42,7 +42,7 @@ private_key, .secret **Read-Only Project Root:** -The main group's project root is mounted read-only. Writable paths the agent needs (group folder, IPC, `.claude/`) are mounted separately. This prevents the agent from modifying host application code (`src/`, `dist/`, `package.json`, etc.) which would bypass the sandbox entirely on next restart. +The main group's project root is mounted read-only. Writable paths the agent needs (store, group folder, IPC, `.claude/`) are mounted separately. This prevents the agent from modifying host application code (`src/`, `dist/`, `package.json`, etc.) which would bypass the sandbox entirely on next restart. The `store/` directory is mounted read-write so the main agent can access the SQLite database directly. ### 3. Session Isolation @@ -88,6 +88,7 @@ Each NanoClaw group gets its own OneCLI agent identity. This allows different cr | Capability | Main Group | Non-Main Group | |------------|------------|----------------| | Project root access | `/workspace/project` (ro) | None | +| Store (SQLite DB) | `/workspace/project/store` (rw) | None | | Group folder | `/workspace/group` (rw) | `/workspace/group` (rw) | | Global memory | Implicit via project | `/workspace/global` (ro) | | Additional mounts | Configurable | Read-only unless allowed | diff --git a/groups/main/CLAUDE.md b/groups/main/CLAUDE.md index e99de77..a94c004 100644 --- a/groups/main/CLAUDE.md +++ b/groups/main/CLAUDE.md @@ -83,15 +83,16 @@ Anthropic credentials must be either an API key from console.anthropic.com (`ANT ## Container Mounts -Main has read-only access to the project and read-write access to its group folder: +Main has read-only access to the project, read-write access to the store (SQLite DB), and read-write access to its group folder: | Container Path | Host Path | Access | |----------------|-----------|--------| | `/workspace/project` | Project root | read-only | +| `/workspace/project/store` | `store/` | read-write | | `/workspace/group` | `groups/main/` | read-write | Key paths inside the container: -- `/workspace/project/store/messages.db` - SQLite database +- `/workspace/project/store/messages.db` - SQLite database (read-write) - `/workspace/project/store/messages.db` (registered_groups table) - Group config - `/workspace/project/groups/` - All group folders @@ -172,10 +173,11 @@ Fields: ### Adding a Group 1. Query the database to find the group's JID -2. Use the `register_group` MCP tool with the JID, name, folder, and trigger -3. Optionally include `containerConfig` for additional mounts -4. The group folder is created automatically: `/workspace/project/groups/{folder-name}/` -5. Optionally create an initial `CLAUDE.md` for the group +2. Ask the user whether the group should require a trigger word before registering +3. Use the `register_group` MCP tool with the JID, name, folder, trigger, and the chosen `requiresTrigger` setting +4. Optionally include `containerConfig` for additional mounts +5. The group folder is created automatically: `/workspace/project/groups/{folder-name}/` +6. Optionally create an initial `CLAUDE.md` for the group Folder naming convention — channel prefix with underscore separator: - WhatsApp "Family Chat" → `whatsapp_family-chat` diff --git a/src/container-runner.ts b/src/container-runner.ts index f6f86b1..31efa96 100644 --- a/src/container-runner.ts +++ b/src/container-runner.ts @@ -68,7 +68,7 @@ function buildVolumeMounts( if (isMain) { // Main gets the project root read-only. Writable paths the agent needs - // (group folder, IPC, .claude/) are mounted separately below. + // (store, group folder, IPC, .claude/) are mounted separately below. // Read-only prevents the agent from modifying host application code // (src/, dist/, package.json, etc.) which would bypass the sandbox // entirely on next restart. @@ -89,6 +89,15 @@ function buildVolumeMounts( }); } + // Main gets writable access to the store (SQLite DB) so it can + // query and write to the database directly. + const storeDir = path.join(projectRoot, 'store'); + mounts.push({ + hostPath: storeDir, + containerPath: '/workspace/project/store', + readonly: false, + }); + // Main also gets its group folder as the working directory mounts.push({ hostPath: groupDir,