# NanoClaw 规范 一款个人 Claude 助手,支持多渠道、按会话持久化记忆、定时任务以及容器隔离的 agent(智能体)执行。 --- ## 目录 1. [架构](#架构) 2. [架构:渠道系统](#架构渠道系统) 3. [目录结构](#目录结构) 4. [配置](#配置) 5. [记忆系统](#记忆系统) 6. [会话管理](#session-管理) 7. [消息流转](#消息流转) 8. [命令](#命令) 9. [定时任务](#定时任务) 10. [MCP 服务器](#mcp-服务器) 11. [部署](#部署) 12. [安全考量](#安全考量) --- ## 架构 ``` ┌──────────────────────────────────────────────────────────────────────┐ │ HOST(宿主机,macOS / Linux) │ │ (主 Node.js 进程) │ ├──────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────┐ ┌────────────────────┐ │ │ │ Channels │─────────────────▶│ SQLite 数据库 │ │ │ │ (渠道,启动时 │◀────────────────│ (messages.db) │ │ │ │ 自行注册) │ 存储/发送 └─────────┬──────────┘ │ │ └──────────────────┘ │ │ │ │ │ │ ┌─────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │ │ │ Message Loop │ │ Scheduler Loop │ │ IPC Watcher │ │ │ │ (消息轮询循环) │ │ (调度器循环) │ │ (IPC 监听器) │ │ │ └────────┬─────────┘ └────────┬─────────┘ └───────────────┘ │ │ │ │ │ │ └───────────┬───────────┘ │ │ │ 启动容器 │ │ ▼ │ ├──────────────────────────────────────────────────────────────────────┤ │ CONTAINER(容器,Linux VM) │ ├──────────────────────────────────────────────────────────────────────┤ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ AGENT RUNNER(智能体运行器) │ │ │ │ │ │ │ │ 工作目录: /workspace/group(从宿主机挂载) │ │ │ │ 卷挂载: │ │ │ │ • groups/{name}/ → /workspace/group │ │ │ │ • groups/global/ → /workspace/global/(仅非主群组) │ │ │ │ • data/sessions/{group}/.claude/ → /home/node/.claude/ │ │ │ │ • 额外目录 → /workspace/extra/* │ │ │ │ │ │ │ │ 工具(所有群组): │ │ │ │ • Bash(安全——在容器内沙箱化!) │ │ │ │ • Read, Write, Edit, Glob, Grep(文件操作) │ │ │ │ • WebSearch, WebFetch(互联网访问) │ │ │ │ • agent-browser(浏览器自动化) │ │ │ │ • mcp__nanoclaw__*(通过 IPC 的调度器工具) │ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────┘ ``` ### 技术栈 | 组件 | 技术 | 用途 | |-----------|------------|---------| | Channel System | Channel registry (`src/channels/registry.ts`) | 渠道在启动时自行注册 | | Message Storage | SQLite (better-sqlite3) | 存储消息供轮询 | | Container Runtime | Containers(Linux VM) | 用于 agent 执行的隔离环境 | | Agent | @anthropic-ai/claude-agent-sdk (0.2.29) | 带工具和 MCP 服务器的 Claude 运行环境 | | Browser Automation | agent-browser + Chromium | Web 交互与截图 | | Runtime | Node.js 20+ | 用于路由和调度的宿主机进程 | --- ## 架构:渠道系统 核心不含任何内置渠道——每个渠道(WhatsApp、Telegram、Slack、Discord、Gmail)作为 [Claude Code skill](https://code.claude.com/docs/en/skills) 安装,将渠道代码添加到您的 fork 中。渠道在启动时自行注册;已安装但缺少凭证的渠道会发出 WARN 日志并被跳过。 ### 系统图 ```mermaid graph LR subgraph Channels["渠道"] WA[WhatsApp] TG[Telegram] SL[Slack] DC[Discord] New["其他渠道 (Signal, Gmail...)"] end subgraph Orchestrator["编排器 — index.ts"] ML[Message Loop] GQ[Group Queue] RT[Router] TS[Task Scheduler] DB[(SQLite)] end subgraph Execution["容器执行"] CR[Container Runner] LC["Linux Container"] IPC[IPC Watcher] end %% Flow WA & TG & SL & DC & New -->|onMessage| ML ML --> GQ GQ -->|并发控制| CR CR --> LC LC -->|文件系统 IPC| IPC IPC -->|任务与消息| RT RT -->|Channel.sendMessage| Channels TS -->|到期任务| CR %% DB Connections DB <--> ML DB <--> TS %% Styling for the dynamic channel style New stroke-dasharray: 5 5,stroke-width:2px ``` ### 渠道注册表 渠道系统建立在 `src/channels/registry.ts` 中的工厂注册表之上: ```typescript export type ChannelFactory = (opts: ChannelOpts) => Channel | null; const registry = new Map(); export function registerChannel(name: string, factory: ChannelFactory): void { registry.set(name, factory); } export function getChannelFactory(name: string): ChannelFactory | undefined { return registry.get(name); } export function getRegisteredChannelNames(): string[] { return [...registry.keys()]; } ``` 每个工厂接收 `ChannelOpts`(包含回调 `onMessage`、`onChatMetadata` 和 `registeredGroups`),并返回一个 `Channel` 实例,如果该渠道的凭证未配置则返回 `null`。 ### 渠道接口 每个渠道实现此接口(定义在 `src/types.ts` 中): ```typescript interface Channel { name: string; connect(): Promise; sendMessage(jid: string, text: string): Promise; isConnected(): boolean; ownsJid(jid: string): boolean; disconnect(): Promise; setTyping?(jid: string, isTyping: boolean): Promise; syncGroups?(force: boolean): Promise; } ``` ### 自行注册模式 渠道使用 barrel 导入模式自行注册: 1. 每个渠道 skill 向 `src/channels/` 添加一个文件(例如 `whatsapp.ts`、`telegram.ts`),在模块加载时调用 `registerChannel()`: ```typescript // src/channels/whatsapp.ts import { registerChannel, ChannelOpts } from './registry.js'; export class WhatsAppChannel implements Channel { /* ... */ } registerChannel('whatsapp', (opts: ChannelOpts) => { // 如果凭证缺失则返回 null if (!existsSync(authPath)) return null; return new WhatsAppChannel(opts); }); ``` 2. barrel 文件 `src/channels/index.ts` 导入所有渠道模块,触发注册: ```typescript import './whatsapp.js'; import './telegram.js'; // ... 每个 skill 在此处添加其导入 ``` 3. 启动时,编排器(`src/index.ts`)循环遍历已注册的渠道,连接返回有效实例的渠道: ```typescript for (const name of getRegisteredChannelNames()) { const factory = getChannelFactory(name); const channel = factory?.(channelOpts); if (channel) { await channel.connect(); channels.push(channel); } } ``` ### 关键文件 | 文件 | 用途 | |------|---------| | `src/channels/registry.ts` | 渠道工厂注册表 | | `src/channels/index.ts` | 触发渠道自行注册的 barrel 导入 | | `src/types.ts` | `Channel` 接口、`ChannelOpts`、消息类型 | | `src/index.ts` | 编排器——实例化渠道、运行消息循环 | | `src/router.ts` | 查找 JID 所属的渠道,格式化消息 | ### 添加新渠道 要添加新渠道,请向 `.claude/skills/add-/` 贡献一个 skill,该 skill 需要: 1. 添加一个 `src/channels/.ts` 文件,实现 `Channel` 接口 2. 在模块加载时调用 `registerChannel(name, factory)` 3. 如果凭证缺失,工厂返回 `null` 4. 向 `src/channels/index.ts` 添加一条导入行 请参考已有的 skills(`/add-whatsapp`、`/add-telegram`、`/add-slack`、`/add-discord`、`/add-gmail`)了解模式。 --- ## 目录结构 ``` nanoclaw/ ├── CLAUDE.md # Claude Code 的项目上下文 ├── docs/ │ ├── SPEC.md # 本规范文档 │ ├── REQUIREMENTS.md # 架构决策 │ └── SECURITY.md # 安全模型 ├── README.md # 用户文档 ├── package.json # Node.js 依赖 ├── tsconfig.json # TypeScript 配置 ├── .mcp.json # MCP 服务器配置(参考) ├── .gitignore │ ├── src/ │ ├── index.ts # 编排器:状态、消息循环、agent 调用 │ ├── channels/ │ │ ├── registry.ts # 渠道工厂注册表 │ │ └── index.ts # 渠道自行注册的 barrel 导入 │ ├── ipc.ts # IPC 监听器与任务处理 │ ├── router.ts # 消息格式化和出站路由 │ ├── config.ts # 配置常量 │ ├── types.ts # TypeScript 接口(包含 Channel) │ ├── logger.ts # Pino 日志器配置 │ ├── db.ts # SQLite 数据库初始化与查询 │ ├── group-queue.ts # 带全局并发限制的按群组队列 │ ├── mount-security.ts # 容器挂载白名单验证 │ ├── whatsapp-auth.ts # 独立的 WhatsApp 认证 │ ├── task-scheduler.ts # 到期时运行定时任务 │ └── container-runner.ts # 在容器中启动 agent │ ├── container/ │ ├── Dockerfile # 容器镜像(以 'node' 用户运行,包含 Claude Code CLI) │ ├── build.sh # 容器镜像的构建脚本 │ ├── agent-runner/ # 在容器内运行的代码 │ │ ├── package.json │ │ ├── tsconfig.json │ │ └── src/ │ │ ├── index.ts # 入口点(查询循环、IPC 轮询、会话恢复) │ │ └── ipc-mcp-stdio.ts # 基于 stdio 的 MCP 服务器,用于宿主机通信 │ └── skills/ │ └── agent-browser.md # 浏览器自动化 skill │ ├── dist/ # 编译后的 JavaScript(gitignored) │ ├── .claude/ │ └── skills/ │ ├── setup/SKILL.md # /setup - 首次安装 │ ├── customize/SKILL.md # /customize - 添加能力 │ ├── debug/SKILL.md # /debug - 容器调试 │ ├── add-telegram/SKILL.md # /add-telegram - Telegram 渠道 │ ├── add-gmail/SKILL.md # /add-gmail - Gmail 集成 │ ├── add-voice-transcription/ # /add-voice-transcription - Whisper │ ├── x-integration/SKILL.md # /x-integration - X/Twitter │ ├── convert-to-apple-container/ # /convert-to-apple-container - Apple Container 运行时 │ └── add-parallel/SKILL.md # /add-parallel - 并行 agent │ ├── groups/ │ ├── CLAUDE.md # 全局记忆(所有群组都读取此文件) │ ├── {channel}_main/ # 主控制渠道(例如 whatsapp_main/) │ │ ├── CLAUDE.md # 主渠道记忆 │ │ └── logs/ # 任务执行日志 │ └── {channel}_{group-name}/ # 按群组目录(注册时创建) │ ├── CLAUDE.md # 群组专属记忆 │ ├── logs/ # 此群组的任务日志 │ └── *.md # agent 创建的文件 │ ├── store/ # 本地数据(gitignored) │ ├── auth/ # WhatsApp 认证状态 │ └── messages.db # SQLite 数据库(messages、chats、scheduled_tasks、task_run_logs、registered_groups、sessions、router_state) │ ├── data/ # 应用状态(gitignored) │ ├── sessions/ # 按群组会话数据(.claude/ 目录,含 JSONL 对话记录) │ ├── env/env # .env 的副本,用于挂载到容器 │ └── ipc/ # 容器 IPC(messages/、tasks/) │ ├── logs/ # 运行时日志(gitignored) │ ├── nanoclaw.log # 宿主机 stdout │ └── nanoclaw.error.log # 宿主机 stderr │ # 注意:每个容器的日志在 groups/{folder}/logs/container-*.log │ └── launchd/ └── com.nanoclaw.plist # macOS 服务配置 ``` --- ## 配置 配置常量在 `src/config.ts` 中定义: ```typescript import path from 'path'; export const ASSISTANT_NAME = process.env.ASSISTANT_NAME || 'Andy'; // 路径必须是绝对路径(容器挂载需要) const PROJECT_ROOT = process.cwd(); export const STORE_DIR = path.resolve(PROJECT_ROOT, 'store'); export const GROUPS_DIR = path.resolve(PROJECT_ROOT, 'groups'); export const DATA_DIR = path.resolve(PROJECT_ROOT, 'data'); // 容器配置 export const CONTAINER_IMAGE = process.env.CONTAINER_IMAGE || 'nanoclaw-agent:latest'; export const CONTAINER_TIMEOUT = parseInt(process.env.CONTAINER_TIMEOUT || '1800000', 10); // 默认30分钟 export const IDLE_TIMEOUT = parseInt(process.env.IDLE_TIMEOUT || '1800000', 10); // 30分钟——最后一次结果后保持容器存活 export const MAX_CONCURRENT_CONTAINERS = Math.max(1, parseInt(process.env.MAX_CONCURRENT_CONTAINERS || '5', 10) || 5); export const TRIGGER_PATTERN = new RegExp(`^@${ASSISTANT_NAME}\\b`, 'i'); ``` **注意:** 路径必须是绝对路径,容器卷挂载才能正常工作。 ### 容器配置 群组可以通过 SQLite `registered_groups` 表中的 `containerConfig`(以 JSON 形式存储在 `container_config` 列中)挂载额外目录。注册示例: ```typescript setRegisteredGroup("1234567890@g.us", { name: "Dev Team", folder: "whatsapp_dev-team", trigger: "@Andy", added_at: new Date().toISOString(), containerConfig: { additionalMounts: [ { hostPath: "~/projects/webapp", containerPath: "webapp", readonly: false, }, ], timeout: 600000, }, }); ``` 目录命名遵循 `{channel}_{group-name}` 约定(例如 `whatsapp_family-chat`、`telegram_dev-team`)。主群组在注册时会设置 `isMain: true`。 额外挂载在容器内显示为 `/workspace/extra/{containerPath}`。 **挂载语法说明:** 读写挂载使用 `-v host:container`,但只读挂载需要使用 `--mount "type=bind,source=...,target=...,readonly"`(`:ro` 后缀可能不是所有运行时都支持)。 ### Claude 认证 在项目根目录的 `.env` 文件中配置认证。有两种选择: **选项1:Claude 订阅(OAuth token)** ```bash CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-... ``` 如果您已登录 Claude Code,可以从 `~/.claude/.credentials.json` 中提取 token。 **选项2:按用量付费的 API Key** ```bash ANTHROPIC_API_KEY=sk-ant-api03-... ``` 只有认证变量(`CLAUDE_CODE_OAUTH_TOKEN` 和 `ANTHROPIC_API_KEY`)从 `.env` 中提取并写入 `data/env/env`,然后挂载到容器中的 `/workspace/env-dir/env`,由入口点脚本加载。这确保 `.env` 中的其他环境变量不会暴露给 agent。此变通方案是必要的,因为某些容器运行时在使用 `-i`(带管道 stdin 的交互模式)时会丢失 `-e` 环境变量。 ### 更改助手名称 设置 `ASSISTANT_NAME` 环境变量: ```bash ASSISTANT_NAME=Bot pnpm start ``` 或编辑 `src/config.ts` 中的默认值。这会更改: - 触发模式(消息必须以 `@YourName` 开头) - 响应前缀(自动添加 `YourName:`) ### launchd 中的占位符值 包含 `{{PLACEHOLDER}}` 值的文件需要进行配置: - `{{PROJECT_ROOT}}` - nanoclaw 安装的绝对路径 - `{{NODE_PATH}}` - node 二进制文件的路径(通过 `which node` 检测) - `{{HOME}}` - 用户的主目录 --- ## 记忆系统 NanoClaw 使用基于 CLAUDE.md 文件的层级记忆系统。 ### 记忆层级 | 层级 | 位置 | 读取者 | 写入者 | 用途 | |-------|----------|---------|------------|---------| | **全局** | `groups/CLAUDE.md` | 所有群组 | 仅主群组 | 在所有对话之间共享的偏好、事实、上下文 | | **群组** | `groups/{name}/CLAUDE.md` | 该群组 | 该群组 | 群组专属上下文、对话记忆 | | **文件** | `groups/{name}/*.md` | 该群组 | 该群组 | 对话过程中创建的笔记、研究、文档 | ### 记忆如何运作 1. **Agent 上下文加载** - Agent 运行在 `groups/{group-name}/` 作为 `cwd` - Claude Agent SDK 使用 `settingSources: ['project']` 自动加载: - `../CLAUDE.md`(上级目录 = 全局记忆) - `./CLAUDE.md`(当前目录 = 群组记忆) 2. **写入记忆** - 当用户说"记住这个",agent 写入 `./CLAUDE.md` - 当用户说"全局记住这个"(仅主渠道),agent 写入 `../CLAUDE.md` - Agent 可以在群组目录中创建 `notes.md`、`research.md` 等文件 3. **主渠道权限** - 只有"主"群组(自我聊天)可以向全局记忆写入 - 主群组可以管理已注册的群组并为任何群组安排定时任务 - 主群组可以为任何群组配置额外的目录挂载 - 所有群组都有 Bash 访问权限(安全,因为在容器内运行) --- ## Session 管理 Session 实现了对话连续性——Claude 会记住你们之前谈过的内容。 ### Session 如何运作 1. 每个群组在 SQLite 中有一个 session ID(`sessions` 表,按 `group_folder` 索引) 2. Session ID 传递给 Claude Agent SDK 的 `resume` 选项 3. Claude 以完整上下文继续对话 4. Session 对话记录以 JSONL 文件形式存储在 `data/sessions/{group}/.claude/` --- ## 消息流转 ### 入站消息流程 ``` 1. 用户通过任何已连接的渠道发送消息 │ ▼ 2. 渠道接收消息(例如 WhatsApp 使用 Baileys,Telegram 使用 Bot API) │ ▼ 3. 消息存入 SQLite(store/messages.db) │ ▼ 4. 消息循环轮询 SQLite(每2秒) │ ▼ 5. 路由检查: ├── chat_jid 是否在已注册群组中(SQLite)?→ 否:忽略 └── 消息是否匹配触发模式?→ 否:存储但不处理 │ ▼ 6. 路由追上对话: ├── 获取自上次 agent 交互以来的所有消息 ├── 使用时间戳和发送者名称格式化 └── 构建包含完整对话上下文的 prompt │ ▼ 7. 路由调用 Claude Agent SDK: ├── cwd: groups/{group-name}/ ├── prompt: 对话历史 + 当前消息 ├── resume: session_id(用于连续性) └── mcpServers: nanoclaw(调度器) │ ▼ 8. Claude 处理消息: ├── 读取 CLAUDE.md 文件获取上下文 └── 按需使用工具(搜索、邮件等) │ ▼ 9. 路由在响应前添加助手名称前缀,并通过所属渠道发送 │ ▼ 10. 路由更新最后 agent 时间戳并保存 session ID ``` ### 触发词匹配 消息必须以触发模式开头(默认:`@Andy`): - `@Andy what's the weather?` → ✅ 触发 Claude - `@andy help me` → ✅ 触发(不区分大小写) - `Hey @Andy` → ❌ 忽略(触发词不在开头) - `What's up?` → ❌ 忽略(无触发词) ### 对话追上 当触发消息到达时,agent 会收到自上次在该聊天中交互以来的所有消息。每条消息都带有时间戳和发送者名称: ``` [Jan 31 2:32 PM] John: hey everyone, should we do pizza tonight? [Jan 31 2:33 PM] Sarah: sounds good to me [Jan 31 2:35 PM] John: @Andy what toppings do you recommend? ``` 这使得 agent 能够理解对话上下文,即使它没有在每条消息中被提及。 --- ## 命令 ### 任何群组中可用的命令 | 命令 | 示例 | 效果 | |---------|---------|--------| | `@Assistant [message]` | `@Andy what's the weather?` | 与 Claude 对话 | ### 仅主渠道可用的命令 | 命令 | 示例 | 效果 | |---------|---------|--------| | `@Assistant add group "Name"` | `@Andy add group "Family Chat"` | 注册新群组 | | `@Assistant remove group "Name"` | `@Andy remove group "Work Team"` | 取消注册群组 | | `@Assistant list groups` | `@Andy list groups` | 显示已注册群组 | | `@Assistant remember [fact]` | `@Andy remember I prefer dark mode` | 添加到全局记忆 | --- ## 定时任务 NanoClaw 内置调度器,将任务作为完整 agent 在其群组上下文中运行。 ### 调度如何运作 1. **群组上下文**:在群组中创建的任务使用该群组的工作目录和记忆运行 2. **完整的 Agent 能力**:定时任务可以访问所有工具(WebSearch、文件操作等) 3. **可选的消息发送**:任务可以使用 `send_message` 工具向其群组发送消息,也可以静默完成 4. **主渠道权限**:主渠道可以为任何群组安排任务并查看所有任务 ### 调度类型 | 类型 | 值格式 | 示例 | |------|--------------|---------| | `cron` | Cron 表达式 | `0 9 * * 1`(每周一上午9点) | | `interval` | 毫秒 | `3600000`(每小时) | | `once` | ISO 时间戳 | `2024-12-25T09:00:00Z` | ### 创建任务 ``` User: @Andy remind me every Monday at 9am to review the weekly metrics Claude: [调用 mcp__nanoclaw__schedule_task] { "prompt": "Send a reminder to review weekly metrics. Be encouraging!", "schedule_type": "cron", "schedule_value": "0 9 * * 1" } Claude: Done! I'll remind you every Monday at 9am. ``` ### 一次性任务 ``` User: @Andy at 5pm today, send me a summary of today's emails Claude: [调用 mcp__nanoclaw__schedule_task] { "prompt": "Search for today's emails, summarize the important ones, and send the summary to the group.", "schedule_type": "once", "schedule_value": "2024-01-31T17:00:00Z" } ``` ### 管理任务 在任何群组中: - `@Andy list my scheduled tasks` - 查看此群组的任务 - `@Andy pause task [id]` - 暂停任务 - `@Andy resume task [id]` - 恢复暂停的任务 - `@Andy cancel task [id]` - 删除任务 在主渠道中: - `@Andy list all tasks` - 查看所有群组的任务 - `@Andy schedule task for "Family Chat": [prompt]` - 为其他群组安排任务 --- ## MCP 服务器 ### NanoClaw MCP(内置) `nanoclaw` MCP 服务器在每次 agent 调用时动态创建,带有当前群组的上下文。 **可用工具:** | 工具 | 用途 | |------|---------| | `schedule_task` | 安排定期或一次性任务 | | `list_tasks` | 显示任务(本群组的任务,主群组则显示全部) | | `get_task` | 获取任务详情和运行历史 | | `update_task` | 修改任务的 prompt 或调度 | | `pause_task` | 暂停任务 | | `resume_task` | 恢复暂停的任务 | | `cancel_task` | 删除任务 | | `send_message` | 通过群组的渠道发送消息 | --- ## 部署 NanoClaw 作为单个 macOS launchd 服务运行。 ### 启动序列 NanoClaw 启动时会: 1. **确保容器运行时正在运行**——如需要自动启动;终止前次运行遗留的 NanoClaw 孤儿容器 2. 初始化 SQLite 数据库(如果存在 JSON 文件则从中迁移) 3. 从 SQLite 加载状态(已注册群组、sessions、路由状态) 4. **连接渠道**——遍历已注册渠道,实例化有凭证的渠道,对每个调用 `connect()` 5. 一旦至少有一个渠道连接: - 启动调度器循环 - 启动用于容器消息的 IPC 监听器 - 设置按群组队列及 `processGroupMessages` - 恢复关闭前未处理的消息 - 启动消息轮询循环 ### 服务:com.nanoclaw **launchd/com.nanoclaw.plist:** ```xml Label com.nanoclaw ProgramArguments {{NODE_PATH}} {{PROJECT_ROOT}}/dist/index.js WorkingDirectory {{PROJECT_ROOT}} RunAtLoad KeepAlive EnvironmentVariables PATH {{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin HOME {{HOME}} ASSISTANT_NAME Andy StandardOutPath {{PROJECT_ROOT}}/logs/nanoclaw.log StandardErrorPath {{PROJECT_ROOT}}/logs/nanoclaw.error.log ``` ### 管理服务 ```bash # 安装服务 cp launchd/com.nanoclaw.plist ~/Library/LaunchAgents/ # 启动服务 launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist # 停止服务 launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist # 检查状态 launchctl list | grep nanoclaw # 查看日志 tail -f logs/nanoclaw.log ``` --- ## 安全考量 ### 容器隔离 所有 agent 在容器(轻量级 Linux VM)内运行,提供: - **文件系统隔离**:Agent 只能访问已挂载的目录 - **安全的 Bash 访问**:命令在容器内运行,而不是在您的 Mac 上 - **网络隔离**:可按容器配置(如需要) - **进程隔离**:容器进程无法影响宿主机 - **非 root 用户**:容器以非特权 `node` 用户(uid 1000)运行 ### Prompt 注入风险 WhatsApp 消息可能包含试图操纵 Claude 行为的恶意指令。 **缓解措施:** - 容器隔离限制爆炸半径 - 仅处理已注册的群组 - 需要触发词(减少误处理) - Agent 只能访问其群组挂载的目录 - 主群组可以按群组配置额外目录 - Claude 内置的安全训练 **建议:** - 仅注册受信任的群组 - 仔细审查额外的目录挂载 - 定期审查定时任务 - 监控日志中的异常活动 ### 凭证存储 | 凭证 | 存储位置 | 说明 | |------------|------------------|-------| | Claude CLI Auth | data/sessions/{group}/.claude/ | 按群组隔离,挂载到 /home/node/.claude/ | | WhatsApp Session | store/auth/ | 自动创建,持续约20天 | ### 文件权限 groups/ 目录包含个人记忆,应加以保护: ```bash chmod 700 groups/ ``` --- ## 故障排除 ### 常见问题 | 问题 | 原因 | 解决方案 | |-------|-------|----------| | 消息无响应 | 服务未运行 | 检查 `launchctl list | grep nanoclaw` | | "Claude Code process exited with code 1" | 容器运行时启动失败 | 检查日志;NanoClaw 会自动启动容器运行时,但可能失败 | | "Claude Code process exited with code 1" | Session 挂载路径错误 | 确保挂载到 `/home/node/.claude/` 而非 `/root/.claude/` | | Session 无法继续 | Session ID 未保存 | 检查 SQLite:`sqlite3 store/messages.db "SELECT * FROM sessions"` | | Session 无法继续 | 挂载路径不匹配 | 容器用户是 `node`,HOME=/home/node;sessions 必须位于 `/home/node/.claude/` | | "QR code expired" | WhatsApp session 过期 | 删除 store/auth/ 并重启 | | "No groups registered" | 尚未添加群组 | 在主渠道中使用 `@Andy add group "Name"` | ### 日志位置 - `logs/nanoclaw.log` - stdout - `logs/nanoclaw.error.log` - stderr ### 调试模式 手动运行以获取详细输出: ```bash pnpm run dev # 或 node dist/index.js ```