Files
nanoclaw/docs/zh/SPEC.md
2026-05-12 13:14:17 +00:00

783 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | ContainersLinux 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<string, ChannelFactory>();
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<void>;
sendMessage(jid: string, text: string): Promise<void>;
isConnected(): boolean;
ownsJid(jid: string): boolean;
disconnect(): Promise<void>;
setTyping?(jid: string, isTyping: boolean): Promise<void>;
syncGroups?(force: boolean): Promise<void>;
}
```
### 自行注册模式
渠道使用 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-<name>/` 贡献一个 skill该 skill 需要:
1. 添加一个 `src/channels/<name>.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/ # 编译后的 JavaScriptgitignored
├── .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/ # 容器 IPCmessages/、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` 文件中配置认证。有两种选择:
**选项1Claude 订阅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 使用 BaileysTelegram 使用 Bot API
3. 消息存入 SQLitestore/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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "...">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.nanoclaw</string>
<key>ProgramArguments</key>
<array>
<string>{{NODE_PATH}}</string>
<string>{{PROJECT_ROOT}}/dist/index.js</string>
</array>
<key>WorkingDirectory</key>
<string>{{PROJECT_ROOT}}</string>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
<key>HOME</key>
<string>{{HOME}}</string>
<key>ASSISTANT_NAME</key>
<string>Andy</string>
</dict>
<key>StandardOutPath</key>
<string>{{PROJECT_ROOT}}/logs/nanoclaw.log</string>
<key>StandardErrorPath</key>
<string>{{PROJECT_ROOT}}/logs/nanoclaw.error.log</string>
</dict>
</plist>
```
### 管理服务
```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/nodesessions 必须位于 `/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
```