添加中文文档

This commit is contained in:
2026-05-12 13:14:17 +00:00
parent 61d7ca6bba
commit 38bb076ac6
24 changed files with 6876 additions and 0 deletions

172
docs/zh/v1-to-v2-changes.md Normal file
View File

@@ -0,0 +1,172 @@
# NanoClaw v1 → v2 — 变更内容
NanoClaw v1你一直在运行的 `~/nanoclaw` 检出)与 v2本次重写之间的宏观差异。这不是迁移指南——那是 `bash migrate-v2.sh``/migrate-from-v1` 技能的角色。本文档是**词汇表**:当某些东西被移动或重命名时,在这里找到它。
在接触迁移代码或将定制向前移植之前,请阅读本文档。
---
## 一句话总结
v1 是一个 Node 进程,带有一个 SQLite 文件和原生频道适配器。v2 是一个生成每个 session会话Docker 容器的宿主机,将状态拆分到中央库 + 每个 session 的 DB 对中,通过显式实体模型路由,并将频道作为技能从兄弟分支安装。
---
## 实体模型——最大的变化
**v1** 一张扁平的 `registered_groups(jid, name, folder, trigger_pattern, requires_trigger, is_main, channel_name)` 表。一个 group 目录是 agent 身份的单位。一个聊天JID连接到恰好一个目录`trigger_pattern` 是路由器应用于每条入站消息的不透明正则表达式。
**v2** 三张表,中间有一个有意的多对多关系:
```
agent_groups ─┐
├─ messaging_group_agents ─┬─ messaging_groups
│ (engage_mode, │ (channel_type,
│ engage_pattern, │ platform_id,
│ sender_scope, │ unknown_sender_policy)
│ ignored_message_policy,
│ session_mode, priority)
```
后果:
- **一个 agent 可以在多个聊天上应答,一个聊天可以扇出到多个 agent。** v1 两者都做不到。
- **没有 `is_main` 标志。** 权限现在通过 `user_roles`owner/admin全局或限定范围显式化。见下文。
- **没有 `trigger_pattern` 正则表达式。** 替换为四个正交列。自动迁移和 `/migrate-from-v1` 技能使用的映射规则:
- v1 `trigger_pattern` 非空 → v2 `engage_mode='pattern'``engage_pattern = <the regex>`
- v1 `requires_trigger=0` 或 pattern 为 `.`/`.*` → v2 `engage_mode='pattern'``engage_pattern='.'`"始终"变体)
- 无 pattern 且需要触发器 → v2 `engage_mode='mention'`
- `sender_scope``ignored_message_policy` 是新的;默认值 `all` / `drop`
- **JID 分解。** v1 的 `jid` 列存储 `dc:12345` / `tg:67890`。v2 将其拆分为 `channel_type` + `platform_id`。具体来说:`dc:12345` 变为 `channel_type='discord'``platform_id='discord:12345'`。前缀别名(`dc``discord``tg``telegram``wa``whatsapp`)在 `setup/migrate-v2/shared.ts` 中。
- **v1 中 `channel_name` 不可靠。** 许多行的该列为空;实际频道只能从 JID 前缀猜测。v2 的 `channel_type` 始终是显式的。
---
## 中央库 vs. Session DB
**v1** 一个 SQLite 文件位于 `store/messages.db`。每个聊天、消息、注册 group、计划任务和 session 都存在那里。宿主机和任何 agent 进程都打开同一个文件。
**v2** 三种 DB 形态。
1. `data/v2.db`——**中央库**。所有非 per-session 的东西users、roles、agent groups、messaging groups、连线、pending approvals、user DMs、schema 迁移。
2. `data/v2-sessions/<session_id>/inbound.db`——**宿主机写入,容器读取**。`messages_in`、路由、destinations、pending questions、processing_ack。计划任务存于此处见"调度")。
3. `data/v2-sessions/<session_id>/outbound.db`——**容器写入,宿主机读取**。`messages_out`、session_state。
每个文件有且仅有一个写入者。无跨挂载锁竞争。心跳是对 `/workspace/.heartbeat` 的文件 touch而非 DB 更新。宿主机使用偶数 `seq` 号,容器使用奇数。
消息历史v1 `messages` 表、v1 `chats` 表)**不被迁移**。迁移将运行上重要的状态向前复制agent、频道、连线、计划任务、group 目录),留下聊天日志。
---
## 调度
**v1**`store/messages.db` 中有专门的 `scheduled_tasks` 表,拥有自己的列(`schedule_type``schedule_value``next_run``last_run``context_mode``script``status`)。一个独立的类似 cron 的调度进程从中读取。
**v2** 计划任务是 session `inbound.db` 中带有 `kind='task'`**`messages_in` 行**。相关列:
- `process_after`ISO8601——宿主机 sweep 在 `datetime(process_after) <= datetime('now')` 时唤醒容器
- `recurrence`——cron 字符串;`NULL` = 单次执行
- `series_id`——将重复事件分组;首次插入时设置为 task id
- `status`——`pending` | `processing` | `completed` | `failed` | `paused`
公共 API 是 `src/modules/scheduling/db.ts` 中的 `insertTask()`。重复执行通过 `cron-parser` 在用户时区计算(见 `src/modules/scheduling/recurrence.ts`)。迁移将 v1 的 `schedule_type`+`schedule_value` 对映射为单个 cron 字符串,然后调用 `insertTask()`
任务可以在 session 唤醒之前存在——宿主机 sweep 在首次到期时创建/唤醒容器。
---
## 凭据
**v1** `.env`——明文环境变量。`DISCORD_BOT_TOKEN``ANTHROPIC_API_KEY` 等。宿主机直接读取它们并传递给任何需要它们的代码。
**v2** OneCLI Agent Vault。一个位于 `http://127.0.0.1:10254` 的独立本地服务持有秘密。Agent 被*限定*到特定秘密vault 在批准后将它们注入到离开容器的 API 请求中。容器永远看不到原始秘密值。
注意:自动创建的 agent 默认为 `selective` 秘密模式——没有附加秘密,即使匹配的秘密存在于 vault 中。关于修复方案(`onecli agents set-secret-mode --mode all`),见根 CLAUDE.md 中"auto-created agents start in selective secret mode"部分。
**自动迁移做了什么:** 将每个 v1 `.env` 键逐字复制到 v2 `.env`,永不覆盖现有的 v2 键。OneCLI vault 迁移是一个单独的步骤,由 `/init-onecli` 技能处理,该技能知道如何从 `.env` 中拉取。
---
## 频道适配器
**v1**`src/channels/` 中导入的原生适配器(例如直接使用 `discord.js`)。安装一个频道意味着编辑代码、添加依赖并设置环境变量。
**v2** 频道适配器存在于兄弟 `channels` 分支上。每个 `/add-<channel>` 技能:
1. `git fetch origin channels`
2. `git show channels:src/channels/<name>.ts > src/channels/<name>.ts`
3.`import './<name>.js';` 追加到 `src/channels/index.ts`
4. `pnpm install @chat-adapter/<name>@<pinned>`
5. `pnpm run build`
幂等——重新运行为无操作。固定版本保持供应链诚信。自动迁移检测 v1 中哪些频道已被连接(通过不同的 `channel_name` / JID 前缀),并为每个频道运行匹配的 `setup/install-<channel>.sh`。v1 中没有 v2 技能的频道(目前少见,随着 v2 的完善将更常见)被记录在交接文件中,由 `/migrate-from-v1` 技能向用户提出。
**`.env` 之外的频道认证。** 某些频道在磁盘上存储 session 状态Baileys WhatsApp 密钥库、Matrix 同步状态、iMessage tokens`channel-auth` 步骤有一个按频道的注册表(`setup/migrate-v2/shared.ts: CHANNEL_AUTH_REGISTRY`),知道除了 env 键之外还要复制哪些文件 glob。
---
## 权限——从隐式到显式
**v1** `registered_groups.is_main = 1` 标记一个 group 为特权级别。没有 `users` 表。权限是约定而非强制执行。
**v2** 显式表。
- `users(id = "<channel_type>:<handle>", kind, display_name)`——每个消息平台标识符一行
- `user_roles(user_id, role ∈ {owner, admin}, agent_group_id nullable, granted_by, granted_at)`——owner 始终全局admin 可以是全局或限定范围
- `agent_group_members(user_id, agent_group_id, ...)`——用于 `sender_scope='known'` 关卡的"已知"成员资格
Owner 在 `/migrate-from-v1` 技能的访谈阶段("哪个 handle 是你?"被种子。自动迁移不会猜测——v1 没有这方面的真源。
**默认访问——"任何人都可以与 bot 通话" vs "仅已知用户"。** v1 隐式存储(通过触发器 regex + `is_main`。v2 通过 `messaging_groups.unknown_sender_policy ∈ {'strict', 'request_approval', 'public'}` 暴露它。技能询问用户 v1 以哪种模式运行,并相应地翻转已迁移的 messaging group。
---
## 磁盘上的 Group 目录
**v1** `groups/<folder>/CLAUDE.md` 和可选的 `logs/``CLAUDE.md` 是一个纯指令文件group 特定。
**v2** 每个 group 仍然位于 `groups/<folder>/`,但形态更丰富:
- `CLAUDE.md`——**在容器启动时组合**,来自 `.claude-shared.md`(到全局的 symlink+ `.claude-fragments/*.md`(模块片段)+ `CLAUDE.local.md`。**不要直接编辑 `CLAUDE.md`。**
- `CLAUDE.local.md`——每个 group 的内容。迁移将 v1 的旧 `CLAUDE.md` 写到这里。
- `container.json`——可选的每个 group 的容器配置apt 依赖、env、挂载。v1 的 `registered_groups.container_config` JSON 接近但不完全相同——迁移将 v1 的 payload 存储在 `groups/<folder>/.v1-container-config.json` 中供技能协调,而不是静默映射。
- `.claude-fragments/``.claude-shared.md` 在宿主机首次接触到该 group 时由 `initGroupFilesystem()` 安装,因此迁移只需写入 `CLAUDE.local.md`,将脚手架工作留给宿主机。
---
## 宿主机进程 vs. 容器
**v1** 单个 Node 进程。"Agent"与路由器是同一个进程。
**v2** 顶层的 Node 宿主机,每个 session 一个 Bun 运行时的 Docker 容器。它们仅通过两个 session DB 通信。无共享模块,无 IPC无 stdin 管道。如果你编写了从 agent 进入宿主机内部(或反之)的自定义代码,那个接口已不复存在——移植它是 `/migrate-from-v1` 技能的话题,而非机械复制。
锁文件:宿主机使用 `pnpm-lock.yaml`agent-runner 使用 `bun.lock`。宿主机侧 `minimumReleaseAge: 4320`3 天供应链等待agent-runner 没有发布年龄限制。
---
## 自我修改和 MCP 工具
**v1** 如果你添加了 MCP 服务器或自我修改管道,通常是直接编辑长期运行的进程。
**v2**
- MCP 服务器通过 `container/agent-runner/src/mcp-tools/*.ts` 注册并按 session 加载。还有 `install_packages``add_mcp_server` 自我修改工具,在重建容器镜像之前经过管理员审批流程(`src/modules/self-mod/apply.ts`)。
- 你在 v1 中编写的自定义 MCP 工具可以清晰地映射到 v2 的工具注册表但导入路径、运行时Bun vs Node和 SQL 辅助函数差异(`bun:sqlite` 使用 `$name` 前缀的参数)可能需要调整。技能将引导你完成这些。
---
## 已消失或无法映射的内容
- **`scheduled_tasks` 作为一个单独的表的模式**——已移入 session `inbound.db``kind='task'` 下。迁移移植活跃行;非活跃/已完成的行导出到 `logs/setup-migration/inactive-tasks.json` 供参考。
- **`messages` / `chats` 表(聊天历史)**——不迁移。如需要,保留在 v1 检出中。
- **`router_state`(键/值)**——不迁移。v2 状态存于上述显式表中。
- **`sessions`v1 group→session_id**——v1 session 不映射v2 session 以 `(agent_group_id, messaging_group_id, thread_id)` 为键并按需创建。
- **对旧 `store/messages.db` 的原始访问**——v1 DB 被保留在原位且不被触碰。如果迁移出错可以重新运行(对于 agent/频道/连线,迁移子步是幂等的;目录使用 rsync 语义)。
---
## 迁移范围——代码所在位置
- `migrate-v2.sh`——入口点:从 v2 检出运行 `bash migrate-v2.sh`
- `setup/migrate-v2/*.ts`——各个迁移步骤env、db、groups、sessions、tasks、channel-auth、select-channels、switchover-prompt
- `setup/migrate-v2/shared.ts`——JID 解析、触发器映射、频道认证注册表。
- `logs/setup-migration/handoff.json`——由 `migrate-v2.sh` 写入,由 `/migrate-from-v1` 技能读取。
- `logs/migrate-steps/*.log`——每个步骤的原始 stdout。
- `.claude/skills/migrate-from-v1/SKILL.md`——用于 owner 种子、CLAUDE.md 清理、容器配置验证、fork 移植的 Claude 技能。
- `migrate-v2-reset.sh`——开发辅助工具,清除 v2 状态以重新测试。
- 完整开发指南见 [docs/migration-dev.md](migration-dev.md)。