# CodePilot 源码分析

> **多模型 AI Agent 桌面客户端** · Electron 40 + Next.js 16 + SQLite + Claude Agent SDK + Native Runtime + Codex Runtime
>
> commit `v0.62.0` · 约 1266 个产品 TS/TSX（含 `资料/` vendor 合计约 1448）· BUSL-1.1
>
> 全部标注 `文件:行号`，可追溯到具体源码。

---

## 一、项目概览

### 1.1 CodePilot 是什么

CodePilot 不是又一个编程 Agent。它是一个**多模型 AI Agent 桌面客户端**——一个 Electron 外壳包裹的 Next.js 应用，把"和 AI 对话"这件事从终端搬到了桌面上，并且把"对话"扩展成了"通用 Agent 平台"：

- **17+ AI Provider**（Anthropic / OpenAI / Google / xAI / GLM / Kimi / DeepSeek / Ollama / Bedrock / Vertex…）
- **三条 Runtime**（Claude Code SDK / Native 自研 / Codex app-server）
- **远程 IM Bridge**（Telegram / 飞书 / Discord / QQ / 微信）
- **MCP + Skills 生态**
- **媒体生成**（Gemini 图片）
- **任务调度**（cron）
- **会话回放**（rewind 到任意 checkpoint）

**它和 Claude Code 的关系**：Claude Code 是终端里的编程 Agent，一行一行读你的输入，调用工具改文件。CodePilot 是桌面上的通用 Agent 客户端——它**复用 Claude Code 的 SDK** 作为三条 Runtime 之一，同时自研了一条 Native Runtime 用来跑非 Claude 模型，还接入了 OpenAI Codex 作为第三条 Runtime。

**它和 open-design 的关系**：open-design 也是"不写 Agent 主循环的宿主"，但它把宿主做的是**设计**（生成 HTML/PPT）。CodePilot 做的是**通用 Agent 桌面**——聊天、编程、任务、媒体、远程控制，全都塞在一个 Electron 窗口里。

### 1.2 数字化的项目形状

| 维度 | 数字 | 说明 |
|---|---|---|
| 源码文件 | 约 1266 个产品 TS/TSX（含资料约 1448） | 不含 node_modules；`资料/` 为微信/飞书 vendor |
| 总文件数 | 2522 | 含 JSON/MD/图片等 |
| 核心引擎 | claude-client.ts 3628 行 | SDK 封装 + 消息流 |
| Native 循环 | agent-loop.ts 1019 行 | while 循环驱动 |
| 数据库 | db.ts 5752 行 | SQLite schema + 迁移 |
| API 端点 | 约 170+ route handlers（约 29 个一级域） | 真正跑 Agent 的是 `POST /api/chat`；`/api/chat/messages` 仅持久化 |
| Bridge 适配器 | 5 个 | Telegram/飞书/Discord/QQ/微信 |
| Provider 预设 | 17+ | provider-catalog.ts 2400 行 |
| 技能目录 | public/skills/ | 兼容 Claude Code 格式 |
| 许可证 | BUSL-1.1 | 商业使用受限 |

### 1.3 三条 Runtime 的本质差异

这是 CodePilot 最核心的架构决策——**同一个前端，三条不同的 Agent 引擎**：

| Runtime | 引擎 | 主循环位置 | 适用场景 |
|---|---|---|---|
| **claude-code-sdk** | `@anthropic-ai/claude-agent-sdk` | SDK 子进程内部 | Claude 模型，完整 Claude Code 能力 |
| **native** | 自研 while 循环 + Vercel AI SDK | `agent-loop.ts:373` | 非 Claude 模型（GLM/Kimi/DeepSeek…） |
| **codex** | `codex app-server` 子进程 | Codex 内部 | OpenAI Codex 模型 |

三条 Runtime 共享同一个前端、同一个数据库、同一个权限系统、同一个 Bridge——但**主循环的实现完全不同**。这是 CodePilot 最独特的架构决策：**Runtime 是可替换的**。

### 1.4 与 Claude Code 的根本区别

| 维度 | Claude Code | CodePilot |
|---|---|---|
| 界面 | 终端 TUI | Electron 桌面 GUI |
| 模型 | 锁 Anthropic | 17+ Provider |
| Runtime | 单一（自研） | 三条可替换 |
| 主循环 | 自研 while 循环 | SDK 外包 / Native 自研 / Codex 外包 |
| 工具 | 硬编码内置 | 标准 AI SDK ToolSet |
| 权限 | 五关裁决 | 三层（规则引擎 + 弹窗 + Profile） |
| 会话 | 终端会话 | SQLite 持久化 + rewind |
| 远程 | 无 | IM Bridge |
| 子 Agent | Task 工具 | subagent_runs durable lifecycle |

---

## 二、全景架构

### 2.1 进程拓扑

```
┌─────────────────────────────────────────────────────────┐
│  Electron 主进程 (electron/main.ts, 2566 行)             │
│  ├── 窗口管理 (BrowserWindow)                            │
│  ├── utilityProcess.fork → Next.js standalone server     │
│  ├── TerminalManager (pty)                               │
│  ├── Tray / 通知 / 文件对话框                             │
│  └── 日志轮转 (50MB×5)                                   │
└────────────────────┬────────────────────────────────────┘
                     │ HTTP/SSE (localhost)
┌────────────────────▼────────────────────────────────────┐
│  Next.js 16 Server (App Router)                          │
│  ├── src/app/api/ — 约 170+ route handlers（约 29 一级域） │
│  │   ├── /api/chat — 聊天（消息/中断/权限）              │
│  │   ├── /api/media — 图片生成                          │
│  │   ├── /api/settings — 设置                           │
│  │   ├── /api/providers — Provider 管理                 │
│  │   ├── /api/tasks — 任务调度                          │
│  │   └── …                                              │
│  ├── src/lib/ — 核心业务逻辑                             │
│  │   ├── claude-client.ts — SDK 封装（3628 行）         │
│  │   ├── agent-loop.ts — Native 循环（1019 行）         │
│  │   ├── stream-session-manager.ts — SSE 流管理         │
│  │   ├── conversation-registry.ts — 会话注册表          │
│  │   ├── db.ts — SQLite（5752 行）                      │
│  │   ├── runtime/ — Runtime 注册表 + 三实现              │
│  │   ├── tools/ — 8 个编码工具                          │
│  │   ├── builtin-tools/ — 平台工具                      │
│  │   ├── permission/ — 三层权限                         │
│  │   ├── bridge/ — IM Bridge 子系统                     │
│  │   ├── channels/ — 渠道插件                           │
│  │   ├── codex/ — Codex 集成                            │
│  │   └── harness/ — 能力契约 + 上下文编译               │
│  └── src/components/ — React 组件                        │
│      ├── chat/ — 聊天界面                               │
│      ├── ai-elements/ — AI 响应渲染                     │
│      ├── layout/ — 布局                                 │
│      └── …                                              │
└────────────────────┬────────────────────────────────────┘
                     │ SQLite (better-sqlite3, WAL)
┌────────────────────▼────────────────────────────────────┐
│  ~/.codepilot/codepilot.db                              │
│  ├── chat_sessions / messages                           │
│  ├── subagent_runs / subagent_run_events                │
│  ├── api_providers / settings / tasks                   │
│  ├── media_generations / media_tags / media_jobs        │
│  └── channel_bindings / channel_offsets                 │
└─────────────────────────────────────────────────────────┘
```

### 2.2 四条必须记牢的边界

**① Electron 只做壳。** `electron/main.ts` 用 `utilityProcess.fork` 启动打包的 Next.js standalone server（`serverProcess`），监听空闲端口，`GET /api/bridge` 健康检查通过后 `mainWindow.loadURL(http://127.0.0.1:${port})`。Electron 不感知任何 Agent 逻辑——它只负责窗口、终端（pty）、通知、文件对话框。

**② 三条 Runtime 共享同一套基础设施。** `runtime/registry.ts:72-152` 的 `resolveRuntime()`（文件共 198 行；L21 仅为 Map 声明）按"Codex 显式 → 显式 override → cli_enabled=false → 全局设置 → auto"五级选择 Runtime。选完后，前端、DB、权限、Bridge 全部复用。

**③ SDK 路径的"主循环"是事件泵，不是真循环。** `claude-client.ts:2239` 的 `for await (const message of conversation)` 只是把 SDK 子进程的事件翻译成 SSE。真正的"模型→工具→回灌→模型"循环发生在 SDK 子进程（claude CLI）内部。CodePilot 应用层只做反应式转发 + 护栏。

**④ Native 路径才是真循环。** `agent-loop.ts:373` 的 `while (step < maxSteps)` 每一步手动调用 `streamText()`，在步骤之间插入权限检查、DB 持久化、超时预算、上下文裁剪、SSE 转发——这些在 AI SDK 的自动模式里无法挂钩。

### 2.3 数据流：从用户输入到 AI 响应

```
用户输入 → MessageInput / startStream
  → POST /api/chat          ← 真正触发 Agent（chat/route.ts）
  → stream-session-manager 会话锁（chat 路径常传 600s TTL；锁默认常量另有 300s）
  → streamClaude()（claude-client.ts）解析 Provider + Runtime
  → addMessage 入库 + assembleContext（含 context-compressor / context-compiler）
  → runtime.stream() 分流：
    ├── SDK: streamClaudeSdk() → SDK query() 子进程
    ├── Native: runAgentLoop() → while (step < maxSteps) → streamText()
    └── Codex: getCodexAppServer() → app-server → thread/start → turn/start
  → SSE 流式返回 → useSSEStream → MessageList
  → db.ts 持久化到 SQLite

说明：POST /api/chat/messages 只持久化消息、不跑模型（图片生成等模式用）。
```

**Bridge 数据流（远程 IM 控制）：**
```
Telegram/飞书消息
  → Adapter 长轮询/WebSocket 接收
  → channelRouter 路由到 CodePilot session
  → conversationEngine 调用 SDK
  → SDK SSE 响应
  → deliveryLayer 格式化 + 分片
  → Adapter 发送回 IM
```

---

## 三、启动流程

### 3.1 Electron 主进程启动

`electron/main.ts`（2566 行）的启动序列：

1. **Sentry 初始化**（可 opt-out）
2. **`utilityProcess.fork`** 启动打包的 Next.js standalone server
   - 监听空闲端口（`serverProcess`）
   - `GET /api/bridge` 健康检查
   - SIGTERM → 3s → SIGKILL 关停
3. **`mainWindow.loadURL(http://127.0.0.1:${port})`**
4. **TerminalManager**（pty）初始化
5. **Tray / 通知 / 文件对话框** 注册
6. **日志轮转**（50MB×5）

**关键设计**：Electron 主进程和 Next.js server 是**两个独立进程**，通过 HTTP/SSE 通信。Dev 模式下直接连 `next dev` 的 dev server；Prod 模式下用 `utilityProcess.fork` 启动 standalone server。

### 3.2 Next.js Server 启动

Next.js 16 App Router 启动时：
1. `src/lib/db.ts` 初始化 SQLite（WAL + 外键 + busy_timeout=5000）
2. `src/lib/runtime-log.ts` 初始化 console 环形缓冲（200 条，自动脱敏）
3. `src/lib/bridge/bridge-manager.ts` 初始化 Bridge 管理器（globalThis 单例，抗 HMR）
4. `src/lib/conversation-registry.ts` 初始化活跃会话注册表

### 3.3 数据库初始化

`db.ts:186` 的 `initDb()`：

```sql
-- 核心表
chat_sessions (id, title, model, provider_id, sdk_session_id, …)
messages (id, session_id, role, content, …)
subagent_runs (id, parent_session_id, status, …)
subagent_run_events (id, run_id, event_type, …)
settings (key, value)
tasks (id, session_id, title, status, …)
api_providers (id, name, protocol, base_url, …)

-- 媒体表
media_generations / media_tags / media_jobs / media_job_items / media_context_events

-- Bridge 表
channel_bindings / channel_offsets / permission_link / …
```

**迁移机制**：`withMigrationLock`（`db.ts:84`）用 `O_CREAT|O_EXCL` 文件锁 + 10s 重试，防多 Next.js worker 并发迁移。`migrateDb`（`db.ts:482`）用 `PRAGMA table_info` 探测 + `safeAddColumn` 做幂等 `ALTER TABLE` 增量加列。

---

## 四、输入捕获与分流

### 4.1 聊天输入

`src/components/chat/MessageInput.tsx` 捕获用户输入 → 经 `startStream` 走 `POST /api/chat`（真正触发 Agent）。`POST /api/chat/messages` 仅持久化、不跑模型。

### 4.2 API 路由处理

`src/app/api/chat/route.ts`（990 行 POST）的完整流程：

1. **校验 body**（session_id, content, model, …）
2. **前置检查**：`hasCodePilotProvider`（412 NEEDS_PROVIDER_SETUP）
3. **会话锁**：`acquireSessionLock`（409 SESSION_BUSY，600s TTL + 续期/watchdog）
4. **解析 Provider + Runtime**：`resolveRuntime()` 五级选择
5. **消息入库**：`addMessage`
6. **上下文组装**：`assembleContext`（含 context-compressor）
7. **分流到 Runtime**：
   - SDK: `streamClaudeSdk()`
   - Native: `runAgentLoop()`
   - Codex: `codex app-server`
8. **SSE 流式返回**
9. **后台持久化**：`collectStreamResponse`（renderer 断开也继续）
10. **释放锁**

### 4.3 会话锁

`session_runtime_locks` 表（`db.ts:891`）实现会话级互斥：

- `acquireSessionLock`：获取锁，600s TTL
- `isLockOwner`：校验锁所有权（`lockId`）
- 续期/watchdog：防止锁过期导致并发问题

**为什么需要锁**：同一个会话不能同时有两个流在跑——否则消息顺序会乱。

### 4.4 中断

`/api/chat/interrupt`：
- 显式 Stop：abort 父 turn 并向 child 传播
- `stream-session-manager.ts:1046` 的 `stopStreamWith`：先无条件武装 2s force-abort 安全网（#578 修复），再 best-effort 调 `/api/chat/interrupt`

---

## 五、上下文组装

### 5.1 assembleContext

上下文组装是"把用户输入 + 历史消息 + 系统提示词 + 工具描述"打包成发给模型的请求。

关键组件：
- **`agent-system-prompt.ts`**：构建系统提示词
- **`harness/context-compiler.ts`**：纯函数 `compileContext(input)`——给定 enabled capabilities + 用户/外部扩展扫描结果，产出有序去重的 system prompt + 工具面 + artifact 契约
- **`context-compressor`**：上下文压缩

### 5.2 Harness 能力契约

`harness/` 是 CodePilot 最独特的子系统之一——**能力契约 + 上下文编译层**：

- **`capability-contract.ts`**：声明所有能力（memory/widget/dashboard/tasks_and_notify/cli_tools/media_import/image_generation…）的 canonical prompt 片段与工具描述符
- **`context-compiler.ts`**：纯函数 `compileContext(input)`——不 IO、不执行工具、不做权限决策，只产出编译后的上下文
- **`mutation-level.ts`**：工具变更级别分类（safe_read / safe_write / dangerous），权限 allowlist 之源
- **`builtin-event-bus.ts`**：工具媒体结果侧信道
- **`auto-invoke-accounting.ts`**：上下文记账

**为什么需要 Harness**：三条 Runtime 需要给模型看一致的能力描述。Harness 保证 Native/SDK/Codex 的 system prompt 不漂移。

### 5.3 上下文压缩

当上下文超过模型窗口时，`context-compressor` 会触发压缩。SDK 路径的压缩在 SDK 子进程内部完成；Native 路径在 `agent-loop.ts` 的步骤之间做 `pruneOldToolResults` 裁剪。

---

## 六、Agent 主循环

### 6.1 三条 Runtime 的主循环对比

这是 CodePilot 最核心的章节。三条 Runtime 的主循环实现完全不同：

#### SDK 路径（claude-client.ts）

`claude-client.ts:2239` 的 `for await (const message of conversation)`：

```typescript
// 这不是真循环，是事件泵
for await (const message of conversation) {
  switch (message.type) {
    case 'assistant': // 提取 tool_use → SSE
    case 'user':      // tool_result / 媒体块 / TodoWrite 同步
    case 'stream_event': // 文本 delta
    case 'system':    // init 元数据
    case 'result':    // extractTokenUsage + terminal_reason
  }
}
```

**关键点**：
- 真正的 Agent 循环在 SDK 子进程（claude CLI）内部
- CodePilot 只做事件转发 + 护栏
- 护栏包括：两级 idle 预算（首 token 前 10min，之后 5.5min）、PTL 压缩重试、#577 result 权威性

#### Native 路径（agent-loop.ts）

`agent-loop.ts:373` 的 `while (step < maxSteps)`（默认 50）：

```typescript
while (step < maxSteps) {
  // 1. 清洗模型参数
  sanitizeClaudeModelOptions()
  buildAnthropicProviderOptions()
  
  // 2. 上下文裁剪
  pruneOldToolResults()
  
  // 3. 超时预算上膛
  timeoutCtl.onStepRequest()
  
  // 4. 单次 streamText()
  const result = streamText({ model, tools, messages, … })
  
  // 5. 逐事件翻译为 SSE
  for await (event of timeoutCtl.guardStream(result.fullStream)) {
    // text-delta→text, reasoning-delta→thinking,
    // tool-call→tool_use, tool-result→tool_result, …
  }
  
  // 6. 终止判断
  if (!hasToolCalls) break
  if (doomLoopDetected) break
  
  // 7. 进入下一轮
  messages += response.messages
  step++
}
```

**关键点**：
- 手动循环是为了在每步之间插入权限检查、DB 持久化、超时预算、上下文裁剪
- 这些在 AI SDK 的自动模式里无法挂钩
- doom-loop 检测：同工具连续调用

#### Codex 路径（codex/runtime.ts）

`codex/runtime.ts`：
- 每次对话 `thread/resume|thread/start`
- 订阅通知
- `turn/start`
- `event-mapper.translateCodexNotification` 翻成 canonical 事件再发 SSE

### 6.2 主循环的本质差异

| 维度 | Claude Code | CodePilot SDK | CodePilot Native | CodePilot Codex |
|---|---|---|---|---|
| 循环位置 | CLI 内部 | SDK 子进程内部 | agent-loop.ts while | Codex 内部 |
| 循环驱动 | 自研 | SDK query() | 手动 while + streamText | Codex turn |
| 事件转发 | 无（终端直出） | for await 事件泵 | for await 翻译 | event-mapper |
| 护栏 | 无 | idle 预算 + PTL 重试 | 超时预算 + doom 检测 | 无 |
| 上下文压缩 | 内部 | SDK 内部 | pruneOldToolResults | Codex 内部 |

### 6.3 流管理

`stream-session-manager.ts`（1439 行）管理 SSE 流的生命周期：

- **`startStream`**：创建流，`fetch('/api/chat')`，`consumeSSEStream`
- **两级 idle 预算**（#635）：首 token 前 10min、之后 5.5min，`setInterval` 检测超时即 abort
- **完成**：`buildFinalMessageContent` → phase=completed → `scheduleGC`（5min 后回收）
- **错误分支**：idle 超时 / tool 超时自动重试 / 用户停止 / 一般错误
- **停止**：`stopStreamWith` 先武装 2s force-abort 安全网，再调 `/api/chat/interrupt`

### 6.4 会话注册表

`conversation-registry.ts`（70 行）管理活跃 SDK 会话：

- globalThis `__activeConversationsV2__`（V2 key 防 HMR 拿到旧形状 Map）
- `Map<sessionId, {query, lockId, abortController}>`
- **`unregisterConversation` 用 lockId 门控**——只有 lockId 匹配才删除，防止被取代的旧 turn 的迟到 teardown 驱逐新 turn 注册的 Query（I1 竞态）

---

## 七、工具系统与权限

### 7.1 两层工具源

CodePilot 的工具系统分两层：

#### 编码核心工具（tools/）

8 个工具：Read / Write / Edit / Bash / Glob / Grep / Skill / Agent

每个是工厂函数 `createXxxTool(ctx)`，`tools/index.ts:45 createBuiltinTools()` 聚合成 AI SDK `ToolSet`：

```typescript
// 标准 Vercel AI SDK ToolSet
{
  name: {
    description: string,
    inputSchema: zodSchema,
    execute: async (input) => result
  }
}
```

直接喂给 `streamText({ tools })`。

#### 平台工具（builtin-tools/）

7 组平台工具：
- **notification**：notify / schedule_task / list / cancel / hatch_buddy
- **memory-search**：3 个记忆搜索工具
- **dashboard**：5 个 Dashboard 工具
- **media**：import + generate_image
- **widget-guidelines**：Widget 指南
- **session-search**：会话搜索
- **cli-tools**：6 个 CLI 工具
- **ask-user-question**：用户问答

`builtin-tools/index.ts:134 getBuiltinTools()` 按 `always / workspace / keywords` 三种 gating 条件挂载，经 `harness/runtime-adapter.ts adaptForNative()` 生成对应 system prompt 片段。

### 7.2 工具装配

`agent-tools.ts:114 assembleTools()`：

```typescript
const tools = {
  ...createBuiltinTools(ctx),        // 编码工具
  ...getBuiltinTools(ctx),           // 平台工具
  ...buildMcpToolSet(mcpServers),    // 外部 MCP 工具
}

// 权限包裹
if (permissionContext) {
  tools = wrapWithPermissions(tools, permissionContext)
}
```

**plan 模式**只保留 `PERMISSION_SAFE_TOOLS`（`agent-tools.ts:53`，由 `harness/mutation-level.ts` 的 `mutationLevel==='safe_read'` 派生，fail-safe：未声明工具默认 ask）。

### 7.3 与 Claude Code 工具系统的本质区别

| 维度 | Claude Code | CodePilot |
|---|---|---|
| 工具格式 | 硬编码在 bundle 内 | 标准 AI SDK ToolSet |
| 工具注册 | 配置式 permission 规则 | zod schema + execute 函数 |
| 工具组合 | 固定 | 可自由组合、gating、运行时包裹 |
| 平台工具 | 无 | builtin-tools（通知/记忆/Dashboard/媒体…） |
| MCP 集成 | 有 | 有（mcp-tool-adapter.buildMcpToolSet） |

### 7.4 三层权限系统

#### 第一层：规则引擎

`permission-checker.ts:127 checkPermission`：

- 三模式：**explore**（只读）/ **normal**（写放行、Bash ask）/ **trust**（全放行）
- 规则数组 `findLast` 语义（具体规则覆盖通用）
- glob 通配匹配命令/路径
- 硬底线：`DANGEROUS_PATTERNS`（rm -rf/sudo/kill/dd…）与 `ALWAYS_ASK_TOOLS`（AskUserQuestion/ExitPlanMode）**任何模式都必问**

#### 第二层：执行时弹窗

`agent-tools.ts:236-341`：

- 非 safe_read 工具 execute 前 `checkPermission`
- `ask` 时写 DB `permission_requests`、发 `permission_request` SSE（带 HMAC `approvalToken` 防伪造）
- `registerPendingPermission` 挂起等 `/api/chat/permission` 回包（5 分钟超时自动 deny）
- 支持用户改输入（updatedInput）和"本会话不再询问"（sessionApprovals）

#### 第三层：会话 Profile

`permission/profile.ts:23`：

- **default**：问用户
- **auto_review**：交给受限 reviewer 模型审批，human-only 类别（凭据/计费/外发/高影响/交互问答）绝不代批，fail-closed
- **full_access**：完全跳过

`review-event.ts` + `review-audit.ts` 统一三 Runtime 的代批审计事件。

### 7.5 沙箱

CodePilot 本身**无 OS 沙箱**：
- Codex 路径依赖 codex app-server 自带沙箱
- Native 靠规则引擎 + 人工确认
- 这是与 Claude Code（Seatbelt/Landlock/seccomp）的本质区别

---

## 八、上下文压缩与记忆

### 8.1 上下文压缩

- **SDK 路径**：压缩在 SDK 子进程内部完成
- **Native 路径**：`agent-loop.ts` 步骤之间 `pruneOldToolResults` 裁剪
- **PTL 压缩重试**：`claude-client.ts:2859-3152`，`CONTEXT_TOO_LONG` 触发自动压缩重试，`ptlRetryAttempted` 防死循环

### 8.2 SQLite 持久化

`db.ts`（5752 行）：
- WAL 模式 + 外键约束 + busy_timeout=5000
- 消息 content 为 JSON 数组
- `sdk_session_id` 用于 SDK 会话 resume

### 8.3 会话回放（rewind）

CodePilot 支持** rewind 到任意 checkpoint**：
- `file-checkpoint.createCheckpoint` 在关键节点创建快照
- 用户可以回滚到之前的 checkpoint 继续对话

### 8.4 记忆系统

- **memory-search**：builtin-tools 中的 3 个记忆搜索工具
- **Assistant Workspace**：Persona 文件、持久记忆、onboarding 流程、daily check-ins

---

## 九、子 Agent

### 9.1 三种 spawn 拓扑

| Runtime | spawn 方式 | 隔离级别 |
|---|---|---|
| CodePilot Runtime | 同一 Next/Electron server 进程内独立 `runAgentLoop` | 非 OS 子进程 |
| Claude Runtime | Agent SDK `query()` 起独立 OS 子进程 | 隔离 shadow HOME/凭据 |
| Codex Runtime | 复用 app-server 连接，新建隔离 `thread/start` | 线程级隔离 |

### 9.2 Durable Lifecycle

`subagent_runs` 表实现持久化生命周期：

```
running → settling → completed/failed/partial/cancelled/timed_out
```

- 启动 child 前先写 `subagent_runs.running`（写不进去就 fail-closed 不启动）
- 结束后 `settling → terminal`，terminal 只原子收口一次
- 用户任务用 `logical_run_id` 聚合，每次执行/重试是递增 attempt

### 9.3 依赖编排

`workflow_id + task_key + depends_on` 声明式 DAG：

- 下游先 `queued`，不占并发位不调 Provider
- resolver 轮询 durable 表等上游 terminal（30 分钟 deadline、5 秒 missing 宽限）
- 上游 `result_text` 以 `<codepilot_dependency_results>` 标记为"不可信数据"编译进下游 prompt
- 拒绝自依赖/循环/占位式"等待"prompt

### 9.4 约束

- **depth 固定为 1**：child 内硬移除 Agent/spawn 工具
- **每父 session 并发 2**
- **Claude child 5 分钟 idle + 30 分钟硬顶**
- **父 abort 向下传播**

### 9.5 多模型 Sub-agent

`subagent-models.ts` 从所有 Provider resolver + runtime-compat 矩阵生成精确 `providerId + modelId` route。child 的 `assembleTools` 和 `runAgentLoop` 都改用目标 Provider。

---

## 十、生态

### 10.1 MCP 集成

- `mcp-tool-adapter.buildMcpToolSet`：把外部 MCP server 的工具聚合进 ToolSet
- 支持 stdio / sse / http 三种传输
- 运行时监控

### 10.2 技能系统

- **格式兼容 Claude Code**：YAML frontmatter + Markdown body
- **扫描路径**：`.claude/skills`、`.claude/commands`、`~/.claude/skills`、`~/.claude/commands`、`~/.agents/skills`
- **marketplace**：安装/卸载 API
- **fork 模式**：落到 subagent_runs durable 体系

### 10.3 Bridge 子系统

见第十二章"Bridge 与远程控制"。

### 10.4 Codex 集成

- **直接集成**：`codex/app-server-manager.ts:603 getCodexAppServer()` 查找 `codex` 二进制并 spawn `codex app-server`（stdio JSON-RPC，缓存单例）
- **codex/proxy**：本地 Responses API 代理（`/api/codex/proxy/v1/responses`），让 Codex CLI 引擎跑在用户自配的任意 provider 上

---

## 十一、功能特性

### 11.1 Provider 管理

`provider-catalog.ts`（2400 行）：
- `VENDOR_PRESETS` 数组定义 17+ 厂商预设
- `VendorPreset` 含 protocol、authStyle、baseUrl、defaultEnvOverrides、defaultModels、roleModels、计费模型、sdkProxyOnly、claudeCodeVerified 兼容分级
- 启动时 Zod 校验全表

`provider-resolver.ts`（1793 行）：
- `resolveProvider()` 优先级 `providerId > sessionProviderId > default_provider_id > active`
- 虚拟 provider（`env`/`openai-oauth`/`xai-oauth`/`codex_account`）
- `resolveExactProvider()` fail-closed 供后台任务

### 11.2 任务调度

- `tasks` 表存储任务
- `ensureSchedulerRunning` 在首个 chat 请求时启动
- cron 表达式或间隔调度

### 11.3 媒体生成

- `image-generator.ts`：Gemini/Anthropic 图片生成
- `job-executor.ts`：批量图片生成任务执行器
- Gallery：图片画廊、标签、批量任务

### 11.4 错误分类与诊断

`error-classifier.ts`（589 行）：
- 28 类结构化错误
- `ERROR_PATTERNS` 每类定义 patterns + errno codes + userMessage + actionHint + retryable
- `PROCESS_CRASH` 含 session 关键词时重分类为 `SESSION_STATE_ERROR`

`provider-doctor.ts`（1093 行）：
- 5 个快探针并行（CLI 安装/版本、凭据/auth、默认 provider/模型、特性兼容性、DNS/连通性）
- 第 6 个 live probe：真实 spawn CLI 发最小请求
- 修复动作：`REPAIR_ACTIONS` + `computeRepairs`

---

## 十二、Bridge 与远程控制

### 12.1 Bridge 架构

`bridge-manager.ts`（1371 行，globalThis 单例）编排四大模块：

| 模块 | 职责 |
|---|---|
| `channel-adapter.ts` | `BaseChannelAdapter` 抽象基类 + 工厂注册表 |
| `channel-router.ts` | 消息路由（IM → session） |
| `conversation-engine.ts` | 消费 SSE 流、保存消息 |
| `delivery-layer.ts` | 出站可靠投递 |

### 12.2 渠道适配器

5 个适配器：
- **telegram**：长轮询 + draft 流式预览
- **discord**：Discord.js
- **qq**：QQ Bot
- **weixin**：微信（auth/media/session-guard）
- **feishu**：飞书（WS 长连接 + 卡片流式）

### 12.3 权限转发

`permission-broker.ts`：

1. `full_access` profile 且非 human-only 工具 → 直接 `resolvePendingPermission(allow)`
2. 30 秒去重窗口防重复卡片
3. 按渠道能力分流：无按钮渠道（QQ/微信）拒绝 AskUserQuestion
4. 发出带 `inlineButtons` 的卡片，写 `permission_link` 表
5. 用户点按钮 → `handlePermissionCallback()` → `resolvePendingPermission()` 解开阻塞的流

### 12.4 Markdown → IR → 渠道格式

三段式管线：
1. `ir.ts`（829 行）：markdown-it token 流 → MarkdownIR = `{text, styles[], links[]}`
2. `render.ts`：通用算法——按 span 边界切分文本、按 `STYLE_ORDER` 排序嵌套样式
3. `telegram.ts`/`discord.ts`/`feishu.ts`：只提供 `styleMarkers + escapeText + buildLink` 配置

---

## 十三、设计哲学与取舍

### 13.1 为什么做三条 Runtime

CodePilot 最核心的架构决策是**Runtime 可替换**。为什么？

- **Claude Code SDK**：Claude 模型的最佳体验，但锁 Anthropic
- **Native**：非 Claude 模型需要自己的循环，但不能复制 Claude Code 的全部能力
- **Codex**：OpenAI 模型需要自己的引擎

三条 Runtime 共享前端、DB、权限、Bridge——但主循环完全不同。这是"产品定位决定技术形态"的典型案例。

### 13.2 为什么用 Electron + Next.js

- **Electron**：跨平台桌面（macOS/Windows/Linux）、系统能力（pty/通知/文件对话框）
- **Next.js**：App Router 同时做前端和 API 层、SSR/SSG、React 19 生态
- **utilityProcess.fork**：Next.js server 跑在独立进程，主进程只做壳

### 13.3 为什么用 SQLite

- **better-sqlite3**：同步 API、WAL 模式、零配置
- **本地优先**：所有数据在 `~/.codepilot/`，不依赖云端
- **会话回放**：SQLite 持久化支持 rewind

### 13.4 与 Claude Code 的取舍对比

| 维度 | Claude Code | CodePilot | 取舍理由 |
|---|---|---|---|
| 界面 | 终端 TUI | Electron GUI | 桌面用户 vs 终端用户 |
| 模型 | 锁 Anthropic | 17+ Provider | 开放性 vs 深度优化 |
| Runtime | 单一 | 三条可替换 | 灵活性 vs 复杂度 |
| 工具 | 硬编码 | 标准 ToolSet | 可组合性 vs 一致性 |
| 权限 | 五关裁决 | 三层 | GUI 弹窗 vs 终端交互 |
| 会话 | 终端会话 | SQLite + rewind | 持久化 vs 轻量 |
| 远程 | 无 | IM Bridge | 移动场景 vs 桌面场景 |
| 子 Agent | Task 工具 | durable lifecycle | 可靠性 vs 简单性 |

### 13.5 三个最独特设计

1. **Runtime 可替换**：同一个前端，三条不同的 Agent 引擎。这是 CodePilot 与所有其他 Agent 客户端的本质区别。

2. **Harness 能力契约**：`harness/context-compiler.ts` 是纯函数编译层，保证三条 Runtime 给模型看的能力描述不漂移。这是"统一基础设施"和"模块独立演进"之间的精妙平衡。

3. **Bridge 权限转发**：`permission-broker.ts` 把权限请求转成 IM 内联按钮，打破"Claude 流阻塞等权限"的死锁。这是"桌面 Agent 如何远程控制"的关键创新。

---

## 横向对比：CodePilot 在十三家之外的位置

> CodePilot 是本仓库分析的**第十六个项目**。它与前面十五家的关系不是"又一个编程 Agent"，而是"把 Agent 从终端搬到桌面"的形态跃迁。

### 与 Claude Code 的对比（最直接的对标）

| 维度 | Claude Code | CodePilot | 差异本质 |
|---|---|---|---|
| 产品定位 | 终端编程 Agent | 桌面通用 Agent 客户端 | 终端 vs 桌面 |
| 界面 | Ink TUI | Electron + React | 终端 vs GUI |
| 模型 | 锁 Anthropic | 17+ Provider | 封闭 vs 开放 |
| Runtime | 单一自研 | 三条可替换 | 统一 vs 灵活 |
| 主循环 | 自研 while 循环 | SDK 外包 / Native 自研 / Codex 外包 | 自主 vs 混合 |
| 工具 | 硬编码 43 个 | 标准 ToolSet 8+7 组 | 私有 vs 标准 |
| 权限 | 五关裁决 | 三层（规则+弹窗+Profile） | 终端 vs GUI |
| 会话 | 终端会话 | SQLite + rewind | 临时 vs 持久 |
| 远程 | 无 | IM Bridge 5 渠道 | 无 vs 有 |
| 子 Agent | Task 工具 | durable lifecycle | 简单 vs 可靠 |
| 沙箱 | Seatbelt/Landlock/seccomp | 无（Codex 自带） | 有 vs 无 |

### 与 open-design 的对比（同为"宿主"形态）

| 维度 | open-design | CodePilot | 差异本质 |
|---|---|---|---|
| 宿主对象 | 25 个 CLI Agent | 3 条 Runtime | 数量 vs 深度 |
| 核心创新 | 适配器即数据 | Runtime 可替换 | 广度 vs 深度 |
| 主循环 | 不写（外包给 CLI） | 三条（外包+自研+外包） | 纯宿主 vs 混合 |
| 内容 | 设计系统/品牌/模板 | 聊天/任务/媒体/远程 | 设计 vs 通用 |
| 输出 | HTML/PPT/PDF | 对话/代码/图片 | 文档 vs 交互 |
| 权限 | 无（CLI 自带） | 三层 | 无 vs 有 |

### 与 openworker 的对比（同为"通用任务同事"）

| 维度 | openworker | CodePilot | 差异本质 |
|---|---|---|---|
| 定位 | 无人值守同事 | 桌面 Agent 客户端 | 后台 vs 前台 |
| 交互 | Slack/邮件 | 桌面 GUI + IM Bridge | 异步 vs 同步 |
| 模型 | provider 无关 | 17+ Provider | 类似 |
| 工具 | 40 个连接器 | 8+7 组 + MCP | 连接器 vs 标准 |
| 权限 | 五档模式 | 三层 | 类似 |
| 持久化 | SQLite | SQLite | 相同 |

### 与 Codex 的对比（第三条 Runtime 的来源）

| 维度 | OpenAI Codex CLI | CodePilot Codex Runtime | 差异本质 |
|---|---|---|---|
| 形态 | 终端 CLI | 桌面 GUI 内嵌 | 独立 vs 集成 |
| 模型 | 锁 OpenAI | 锁 OpenAI（但通过 proxy 可跑任意 provider） | 封闭 vs 半开放 |
| 主循环 | run_turn 两层循环 | Codex 内部 | 相同 |
| 沙箱 | 三平台 OS 沙箱 | Codex 自带 | 相同 |
| 权限 | AskForApproval × 沙箱 | 复用 CodePilot 三层 | 独立 vs 复用 |

### CodePilot 的独特贡献

CodePilot 给 Agent 工程带来了三个前面十五家都没有的设计：

1. **Runtime 可替换**：同一个前端，三条不同的 Agent 引擎。这不是"多 Provider"（那是模型层面的开放），而是"多引擎"（循环层面的开放）。

2. **Harness 能力契约**：`harness/context-compiler.ts` 是纯函数编译层，保证三条 Runtime 给模型看的能力描述不漂移。这是"统一基础设施"和"模块独立演进"之间的精妙平衡。

3. **Bridge 权限转发**：`permission-broker.ts` 把权限请求转成 IM 内联按钮，打破"Claude 流阻塞等权限"的死锁。这是"桌面 Agent 如何远程控制"的关键创新。

---

## 附录：关键文件索引

| 文件 | 行数 | 说明 |
|---|---|---|
| `src/lib/claude-client.ts` | 3628 | SDK 封装 + 消息流 |
| `src/lib/agent-loop.ts` | 1019 | Native 循环 |
| `src/lib/db.ts` | 5752 | SQLite schema + 迁移 |
| `src/lib/stream-session-manager.ts` | 1439 | SSE 流管理 |
| `src/lib/conversation-registry.ts` | 70 | 会话注册表 |
| `src/lib/error-classifier.ts` | 589 | 错误分类 |
| `src/lib/provider-doctor.ts` | 1093 | Provider 诊断 |
| `src/lib/runtime/registry.ts` | 198（`resolveRuntime` @72-152） | Runtime 注册表 |
| `src/lib/tools/index.ts` | 67（`createBuiltinTools` @45） | 工具聚合 |
| `src/lib/builtin-tools/index.ts` | 351（`getBuiltinTools` @134） | 平台工具 |
| `src/lib/permission-checker.ts` | （`checkPermission` @127；文件总行数以仓库为准） | 权限规则引擎 |
| `src/lib/bridge/bridge-manager.ts` | 1371 | Bridge 管理器 |
| `src/lib/harness/context-compiler.ts` | — | 上下文编译 |
| `src/lib/codex/app-server-manager.ts` | 769（`getCodexAppServer` @603） | Codex 集成 |
| `src/lib/provider-catalog.ts` | 2400 | Provider 预设 |
| `src/lib/provider-resolver.ts` | 1793 | Provider 解析 |
| `electron/main.ts` | 2566 | Electron 主进程 |
| `src/app/api/chat/route.ts` | 990 | 聊天 API |

---

*本文基于 CodePilot v0.62.0 源码逐文件分析写成。所有 `文件:行号` 引用已用 `grep -n` / `sed -n` 回验。*
