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

81 lines
6.4 KiB
Markdown
Raw Permalink 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 运行的是分拆式技术栈:宿主机使用 Node + pnpmagent 容器使用 Bun。它们之间仅通过每个 session会话的两个 SQLite 文件来通信——它们之间没有共享模块,这也是它们能干净地使用不同运行时的原因。
## 为什么分拆
- **宿主机保持使用 Node**,因为 BaileysWhatsApp依赖 `libsignal-node` 原生绑定和久经考验的 WebSocket/HTTP 栈。Bun 的 Node-API 兼容性已经有所改善,但这不是我们想冒险的地方。
- **容器使用 Bun**,因为 `bun:sqlite` 是内置的(无需每次镜像重建都编译 `better-sqlite3` 原生模块),源码直接运行(无需在镜像构建或 session 唤醒时做 tsc 构建步骤),而且 `bun install``npm install` 快大约 5-10 倍。
宿主机和容器各有自己的包树:
```
/ pnpm + Node 22
pnpm-lock.yaml 宿主机依赖频道、Chat SDK、Baileys、better-sqlite3 等)
pnpm-workspace.yaml minimumReleaseAge + onlyBuiltDependencies 策略
/container/agent-runner/ Bun 1.3+
bun.lock agent-runner 运行时依赖Claude Agent SDK、MCP SDK、zod 等)
package.json @types/bun、typescript 开发依赖用于类型检查
```
容器镜像内部也有 pnpm + Node用于全局 CLI`@anthropic-ai/claude-code``agent-browser``vercel`)。这些是 agent 在运行时调用的 Node 二进制文件,不是库依赖。将它们保持在 pnpm 上可以保留 CLI 版本的供应链策略。
## 锁文件
| 树 | 锁文件 | 管理器 | 依赖变更后重新生成 |
|------|----------|---------|----------------------------|
| 宿主机 | `pnpm-lock.yaml` | pnpm 10 | `pnpm install` |
| Agent-runner | `container/agent-runner/bun.lock` | Bun 1.3+ | `cd container/agent-runner && bun install` |
两者都已提交。CI 和 Dockerfile 运行 `--frozen-lockfile` 变体——`package.json` 与锁文件之间的任何偏差都会导致构建失败。
## 供应链
- **宿主机 + 全局 CLI**pnpm`minimumReleaseAge: 4320`(新版本需要等待 3 天),`onlyBuiltDependencies` 白名单用于 postinstall 脚本。见 `pnpm-workspace.yaml``docs/SECURITY.md`
- **Agent-runner**Bun无发布年龄策略——Bun 目前没有等效机制。防御措施是 `bun.lock` 锁定版本加上通过 Dockerfile ARG 固定版本的 CLI/Bun 本身。当升级 `@anthropic-ai/claude-agent-sdk` 或任何运行时依赖时,请查看 npm 上的发布日期并有意识地升级,而不是通过 `bun update`
## 镜像构建范围
`container/Dockerfile` 是基于 `node:22-slim` 的单阶段构建:
- **固定的 ARG**——`BUN_VERSION``CLAUDE_CODE_VERSION``AGENT_BROWSER_VERSION``VERCEL_VERSION`。在 PR 中有意识地升级。
- **CJK 字体**——`ARG INSTALL_CJK_FONTS=false``container/build.sh``.env` 读取 `INSTALL_CJK_FONTS` 并传递。默认构建节省约 200MB当用户处理中文/日文/韩文内容时选择加入。
- **BuildKit 缓存挂载**——`/var/cache/apt``/var/lib/apt``/root/.bun/install/cache``/root/.cache/pnpm`。当 `package.json`/`bun.lock` 未变更时的重建速度很快。需要 BuildKitDocker 23+、Apple Container 兼容下默认启用)。
- **`tini` 作为 init**——回收 Chromium 僵尸进程,转发信号以便在 SIGTERM 时完成正在进行的 `outbound.db` 写入。
- **`entrypoint.sh`**(已提取)——`exec bun run /app/src/index.ts` 在 tini 下运行。可读且可 diff。
- **无编译后的 `/app/dist`**——Bun 直接运行 TS。宿主机在 session 启动时还会将最新源码挂载到 `/app/src` 上,因此宿主机的编辑无需重建镜像即可生效。
## Session 唤醒(两条路径)
1. **基础镜像 ENTRYPOINT**——用于 stdin 管道测试调用,如 `container/build.sh` 中的示例:`tini --> entrypoint.sh` 捕获 stdin 到 `/tmp/input.json`,然后 `exec bun run src/index.ts`
2. **宿主机生成的 session**——`src/container-runner.ts` 第 301 行附近使用 `--entrypoint bash``-c 'exec bun run /app/src/index.ts'`。绕过 tiniDocker 默认的 PID 1 处理生效。stdin 未使用;所有 IO 通过挂载的 session DB 流动。
两条路径最终都由 Bun 运行同一个源码文件 `/app/src/index.ts`
## CI 流程
`.github/workflows/ci.yml` 安装 Node带 pnpm 缓存)和 Bun然后按顺序运行
1. `pnpm install --frozen-lockfile`(宿主机)
2. `bun install --frozen-lockfile``container/agent-runner/` 中(容器)
3. `pnpm run format:check`
4. `pnpm exec tsc --noEmit`(宿主机类型检查)
5. `pnpm exec tsc -p container/agent-runner/tsconfig.json --noEmit`(容器类型检查)
6. `pnpm exec vitest run`(宿主机测试)
7. `bun test``container/agent-runner/` 中(容器测试)
任何失败都会导致 PR 失败。
## 关键不变条件
- **Session DB 必须使用 `journal_mode=DELETE`。** WAL 的 `-shm` 内存映射不会跨宿主机和客户机之间的 VirtioFS 传递。参见 `container/agent-runner/src/db/connection.ts` 顶部的文档注释和 `src/session-manager.ts`
- **容器中的命名 SQL 参数要求在 JS 对象键中使用前缀。** `bun:sqlite` 不会像宿主机上的 `better-sqlite3` 那样自动去除 `@`/`$`/`:`。在 SQL 和键中都使用 `$name``.run({ $id: msg.id })`。位置 `?` 参数正常工作。
- **Agent-runner 测试在 `bun:test` 下运行,而非 vitest。** `vitest.config.ts` 排除了 `container/agent-runner/` 树,因为 vitest 在 Node 上运行,无法加载 `bun:sqlite`
- **容器镜像中没有 tsc 构建步骤。** 重新添加一个会重新引入我们已移除的每个 session 唤醒约 200-500ms 的成本。
- **全局容器 CLI 保持使用 pnpm而非 Bun。** `agent-browser``@anthropic-ai/claude-code``vercel` 以及 agent 调用的任何未来 Node CLI 都应在 Dockerfile 的 pnpm 全局安装块下固定版本。`bun install -g` 会绕过 pnpm 供应链策略。
## 迁移历史
这一结构替换了统一的 npm-on-Node 技术栈(宿主机和容器均为 Node。pnpm 迁移首先落地PR #1771),使宿主机纳入供应链策略,然后容器迁移到 Bun 以消除原生模块编译和每次唤醒的 tsc 步骤。选择分拆而非完全转用 Bun是因为 Baileys 的原生依赖是宿主机上的主要风险面——容器没有这样的依赖,因此可以在不承担风险的情况下受益于 Bun。