多模型 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 行)的启动序列:
- Sentry 初始化(可 opt-out)
utilityProcess.fork启动打包的 Next.js standalone server- 监听空闲端口(
serverProcess) GET /api/bridge健康检查- SIGTERM → 3s → SIGKILL 关停
mainWindow.loadURL(http://127.0.0.1:${port})- TerminalManager(pty)初始化
- Tray / 通知 / 文件对话框 注册
- 日志轮转(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():
-- 核心表
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)的完整流程:
- 校验 body(session_id, content, model, …)
- 前置检查:
hasCodePilotProvider(412 NEEDS_PROVIDER_SETUP) - 会话锁:
acquireSessionLock(409 SESSION_BUSY,600s TTL + 续期/watchdog) - 解析 Provider + Runtime:
resolveRuntime()五级选择 - 消息入库:
addMessage - 上下文组装:
assembleContext(含 context-compressor) - 分流到 Runtime:
- SDK:
streamClaudeSdk() - Native:
runAgentLoop() - Codex:
codex app-server - SSE 流式返回
- 后台持久化:
collectStreamResponse(renderer 断开也继续) - 释放锁
4.3 会话锁
session_runtime_locks 表(db.ts:891)实现会话级互斥:
acquireSessionLock:获取锁,600s TTLisLockOwner:校验锁所有权(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):
// 这不是真循环,是事件泵
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):
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:
// 标准 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():
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时写 DBpermission_requests、发permission_requestSSE(带 HMACapprovalToken防伪造)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二进制并 spawncodex 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:
full_accessprofile 且非 human-only 工具 → 直接resolvePendingPermission(allow)- 30 秒去重窗口防重复卡片
- 按渠道能力分流:无按钮渠道(QQ/微信)拒绝 AskUserQuestion
- 发出带
inlineButtons的卡片,写permission_link表 - 用户点按钮 →
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 三个最独特设计
-
Runtime 可替换:同一个前端,三条不同的 Agent 引擎。这是 CodePilot 与所有其他 Agent 客户端的本质区别。
-
Harness 能力契约:
harness/context-compiler.ts是纯函数编译层,保证三条 Runtime 给模型看的能力描述不漂移。这是"统一基础设施"和"模块独立演进"之间的精妙平衡。 -
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 工程带来了三个前面十五家都没有的设计:
-
Runtime 可替换:同一个前端,三条不同的 Agent 引擎。这不是"多 Provider"(那是模型层面的开放),而是"多引擎"(循环层面的开放)。
-
Harness 能力契约:
harness/context-compiler.ts是纯函数编译层,保证三条 Runtime 给模型看的能力描述不漂移。这是"统一基础设施"和"模块独立演进"之间的精妙平衡。 -
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 回验。