From a8d90d2980a8bd97ac08a6183eddd4d37373ecc9 Mon Sep 17 00:00:00 2001 From: wang Date: Wed, 13 May 2026 03:08:45 +0000 Subject: [PATCH] docs: add source code learning roadmap --- docs/learning-roadmap.md | 192 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 docs/learning-roadmap.md diff --git a/docs/learning-roadmap.md b/docs/learning-roadmap.md new file mode 100644 index 0000000..7b53f08 --- /dev/null +++ b/docs/learning-roadmap.md @@ -0,0 +1,192 @@ +# NanoClaw 源码学习路线图 + +## 前置知识 + +阅读源码前,确保理解以下核心概念: + +- **两层运行时**:Host 进程用 Node/pnpm,Container 进程用 Bun。两者不共享代码,不共享模块。 +- **唯一的 IO 界面**:Host 和 Container 之间没有 IPC、没有 stdin pipe。两个 SQLite 文件是唯一的通讯界面: + - `inbound.db` — Host 写,Container 读 + - `outbound.db` — Container 写,Host 读 +- **seq 奇偶规则**:Host 使用偶数 seq,Container 使用奇数 seq,避免双方同时写同一个 DB 时的冲突。 +- **实体模型**:`users → messaging_groups → agent_groups → sessions`,中间通过 `messaging_group_agents` 多对多连接。 +- **会话不等于代理组**:一个 agent group 可以有多个 session(比如不同线程),每个 session 有自己独立的 `inbound.db` + `outbound.db`。 + +> 建议:先快速浏览 `docs/architecture.md` 和 `docs/architecture-diagram.md`,建立一个心智模型再开始。 + +--- + +## 第一层:全局鸟瞰(先读文档) + +| 顺序 | 文件 | 解决什么问题 | +|------|------|-------------| +| 1 | `docs/architecture.md` | 整个系统的架构全貌,消息怎么流转 | +| 2 | `docs/architecture-diagram.md` | 消息从进到出的可视化链路 | +| 3 | `docs/db.md` | 三层 DB 模型(central + inbound + outbound) | +| 4 | `docs/db-central.md` | 中央库 `data/v2.db` 每一张表的职责和字段 | +| 5 | `docs/db-session.md` | 会话库的 schema + seq 奇偶分配机制 + cross-mount 不变式 | +| 6 | `docs/build-and-runtime.md` | 为什么 Host 用 Node,Container 用 Bun?两套锁文件互不干扰 | + +--- + +## 第二层:主循环 — 消息从进到出的主干链路 + +这是整个系统的脊椎。按调用顺序读,每个文件不必求甚解,但要理解数据流向。 + +| 顺序 | 文件 | 在链路中的角色 | +|------|------|---------------| +| 7 | `src/index.ts` | 程序入口:初始化 DB、运行迁移、启动所有 channel adapter、启动 poll sweep、启动 CLI socket server | +| 8 | `src/router.ts` | 入站路由:收到消息 → 解析 messaging group → 解析 agent group → 权限门检查 → 创建/查找 session → 写 `inbound.db` → 唤醒容器 | +| 9 | `src/session-manager.ts` | 会话生命周期:管理 session 目录、打开/关闭 DB、心跳检测、跨 mount 的 `journal_mode=DELETE` 不变式 | +| 10 | `src/delivery.ts` | 出站投递:轮询 `outbound.db` → 通过 channel adapter 发送消息 → 处理系统动作(审批、调度等) | +| 11 | `src/host-sweep.ts` | 后台清扫:60 秒周期 — `processing_ack` 同步、僵死容器检测、到期消息唤醒、定时任务触发 | + +> **到这里你应该能回答:一条用户消息从发出来到 agent 回复,经历了哪些步骤。** + +--- + +## 第三层:容器侧 — Agent 在里面干什么 + +理解 Host 怎么把消息交给容器之后,钻进去看容器内部。 + +| 顺序 | 文件 | 在链路中的角色 | +|------|------|---------------| +| 12 | `container/agent-runner/src/index.ts` | 容器入口:加载 `/workspace/agent/container.json`、构建 MCP server 配置、创建 provider、进入 poll loop | +| 13 | `container/agent-runner/src/poll-loop.ts` | 核心大循环:读 `inbound.db` → 调 provider(Claude/OpenCode)→ 格式化输出 → 写 `outbound.db` | +| 14 | `container/agent-runner/src/providers/factory.ts` | Provider 工厂:根据 `container.json` 中的 `agent_provider` 字段选择 Claude 或 OpenCode | +| 15 | `container/agent-runner/src/providers/claude.ts` | Claude Agent SDK 的具体对接实现 | +| 16 | `container/agent-runner/src/formatter.ts` | 出站消息格式化:按目标 channel 类型做内容适配 | +| 17 | `container/agent-runner/src/mcp-tools/index.ts` | MCP 工具注册入口:agent 能调用的所有工具列表 | + +--- + +## 第四层:带着问题深入子系统 + +以下问题按系统层次组织,每个问题后面标注了需要读的关键文件。带着问题去读比按文件线性读高效得多。 + +--- + +### 全局架构 + +#### Q1: 一条用户消息从 Slack 发出,到 agent 回复出现在聊天框里,完整路径是什么? +> 追踪:`src/router.ts` → `src/session-manager.ts` → `container/agent-runner/src/poll-loop.ts` → `src/delivery.ts`。动手画一张时序图,标注每个环节读/写了哪个数据库。 + +#### Q2: 为什么 Host 用 Node,Container 用 Bun?两套运行时之间的"协议"是什么? +> `docs/build-and-runtime.md` + `src/container-runner.ts` + +#### Q3: `inbound.db` 和 `outbound.db` 为什么各只能有一个 writer?`journal_mode=DELETE` 为什么是必须的? +> `docs/db-session.md` + `src/session-manager.ts` 中的注释块 + +--- + +### 路由与会话 + +#### Q4: 一个 messaging group(比如 Slack 频道)怎么决定路由到哪个 agent group?没匹配上怎么办? +> `src/router.ts` 的 `routeInbound()` + `src/db/messaging-groups.ts` + +#### Q5: Session 什么时候创建?`agent-shared`、`shared`、per-thread 三种隔离模式的区别是什么? +> `docs/isolation-model.md` + `src/session-manager.ts` + +#### Q6: 容器 idle 后被 kill,用户再发消息时怎么被唤醒?`on_wake` 消息为什么不会被旧容器偷走? +> `src/container-runner.ts` 的 `killContainer()` + `src/container-restart.ts` + +--- + +### 权限与安全 + +#### Q7: 用户身份怎么确定?owner / admin / member 三级的权限检查在哪张表、哪段代码里完成? +> `src/modules/permissions/access.ts` + `src/modules/permissions/db/user-roles.ts` + +#### Q8: 陌生人在群里 @bot,系统怎么决定是忽略、审批、还是直接响应? +> `src/modules/permissions/sender-approval.ts` + `src/router.ts` 中的 access gate 回调 + +#### Q9: Agent 在容器里能用 `ncl` 命令吗?能查其他 agent group 的数据吗?`cli_scope` 在哪里被检查? +> `src/cli/dispatch.ts` + `src/command-gate.ts` + `src/db/migrations/015-cli-scope.ts` + +--- + +### 容器生命周期 + +#### Q10: 启动 agent 容器时 mount 了哪些东西?`/workspace`、session DB、CLAUDE.md、skills 分别从哪里来? +> `src/container-runner.ts` 的 mount 参数 + `src/group-init.ts` + +#### Q11: Agent 的 system prompt 是怎么拼出来的?CLAUDE.md + 全局指令 + container config 各自贡献了什么? +> `src/claude-md-compose.ts` + `container/agent-runner/src/index.ts` + +#### Q12: 容器心跳怎么检测?进程活着但 poll loop 卡死了,host 怎么发现? +> `src/host-sweep.ts` + `src/session-manager.ts` 中 `.heartbeat` 文件的逻辑 + +--- + +### 出站投递与系统动作 + +#### Q13: Agent 回复消息后,delivery.ts 怎么知道用哪个 channel adapter 发送?重试和失败怎么处理? +> `src/delivery.ts` + `src/channels/adapter.ts` + +#### Q14: Agent 发起 `install_packages` 或 `add_mcp_server`,从发出请求到容器重建完成,完整的审批-执行链路是什么? +> `container/agent-runner/src/mcp-tools/self-mod.ts` → `src/modules/self-mod/request.ts` → `src/modules/approvals/primitive.ts` → `src/modules/self-mod/apply.ts` + +#### Q15: 定时任务(cron)怎么实现?Agent 在容器里能创建吗?触发时谁写消息进 `inbound.db`? +> `src/modules/scheduling/recurrence.ts` + `src/modules/scheduling/db.ts` + `src/host-sweep.ts` 中 recurrence 处理 + +--- + +### 数据模型 + +#### Q16: 中央库 `data/v2.db` 有哪些表?它们之间的外键关系是怎样的? +> `src/db/schema.ts` + `docs/db-central.md`。建议自己画一张 ER 图。 + +#### Q17: DB 迁移怎么组织?我要加一张表或一个字段该改哪些文件? +> `src/db/migrations/index.ts` + 任意一个 `src/db/migrations/0XX-*.ts` + +--- + +### Provider 与 MCP + +#### Q18: Claude Agent SDK、OpenCode、Ollama 三个 provider 怎么抽象成统一接口?切换 provider 改什么? +> `container/agent-runner/src/providers/factory.ts` + `container/agent-runner/src/providers/types.ts` + +#### Q19: 容器里的 MCP server 怎么启动?内置工具(core、agents、self-mod 等)和外部 MCP server 有什么不同? +> `container/agent-runner/src/mcp-tools/server.ts` + `container/agent-runner/src/index.ts` 中的 MCP 构建逻辑 + +--- + +### Channel 适配器 + +#### Q20: 如果要加一个新的 channel(比如钉钉),需要实现什么接口、改哪些文件? +> `src/channels/adapter.ts`(接口定义)+ `src/channels/channel-registry.ts`(注册表)+ `src/channels/chat-sdk-bridge.ts`(如果用 Chat SDK 模式) + +#### Q21: Chat SDK bridge 是什么?为什么 Discord/Slack/Telegram 等共用它? +> `src/channels/chat-sdk-bridge.ts` + +--- + +## 建议的阅读策略 + +1. **先跑通主干**(第一层→第二层:Q1),不求甚解,但要能画出消息的完整流转路径 +2. **再读容器侧**(第三层),理解 agent 内部怎么调 Claude、怎么写回结果 +3. **挑一个子系统深入**(第四层),根据你最关心的方向: + - 关注**多租户/权限** → Q7, Q8, Q9 + - 关注**容器/部署** → Q6, Q10, Q11, Q12 + - 关注**扩展新 provider/channel** → Q18, Q19, Q20, Q21 + - 关注**运维/稳定性** → Q2, Q3, Q5, Q14, Q15 +4. **最后回到文档**,通读 `docs/api-details.md` 和 `docs/agent-runner-details.md` + +--- + +## 关键文件速查表 + +| 想了解... | 先读这个文件 | +|-----------|-------------| +| 整体启动流程 | `src/index.ts` | +| 消息怎么进来 | `src/router.ts` | +| 消息怎么出去 | `src/delivery.ts` | +| Session 怎么管理 | `src/session-manager.ts` | +| 容器怎么启动/杀死 | `src/container-runner.ts` | +| Agent 的主循环 | `container/agent-runner/src/poll-loop.ts` | +| Agent 能调用哪些工具 | `container/agent-runner/src/mcp-tools/index.ts` | +| 权限检查入口 | `src/modules/permissions/access.ts` | +| ncl CLI 怎么工作 | `src/cli/dispatch.ts` | +| DB 有哪些表 | `src/db/schema.ts` | +| 审批流程 | `src/modules/approvals/primitive.ts` | +| 自我修改如何实现 | `src/modules/self-mod/apply.ts` |