12 KiB
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非空 → v2engage_mode='pattern'、engage_pattern = <the regex> - v1
requires_trigger=0或 pattern 为./.*→ v2engage_mode='pattern'、engage_pattern='.'("始终"变体) - 无 pattern 且需要触发器 → v2
engage_mode='mention' sender_scope和ignored_message_policy是新的;默认值all/drop
- v1
- 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 形态。
data/v2.db——中央库。所有非 per-session 的东西:users、roles、agent groups、messaging groups、连线、pending approvals、user DMs、schema 迁移。data/v2-sessions/<session_id>/inbound.db——宿主机写入,容器读取。messages_in、路由、destinations、pending questions、processing_ack。计划任务存于此处(见"调度")。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 idstatus——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> 技能:
git fetch origin channelsgit show channels:src/channels/<name>.ts > src/channels/<name>.ts- 将
import './<name>.js';追加到src/channels/index.ts pnpm install @chat-adapter/<name>@<pinned>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_configJSON 接近但不完全相同——迁移将 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作为一个单独的表的模式——已移入 sessioninbound.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。