# Kanban 源码分析：让成百个编码 Agent 并行干活的看板

> **分析对象**：kanban（`github.com/cline/kanban`），Apache-2.0，本分析基于 **v0.1.70**（2026-07-12 检出，npm 包名也是 `kanban`）。
> **代码规模**：561 个文件 / 9.1MB。`src/` 92 个 TS 文件 30,402 行（本地 runtime），`web-ui/src` 240 个 ts/tsx 文件 58,039 行（React 前端；连配置与样式，`web-ui/` 全目录 269 个文件），`packages/desktop/` Electron 壳 11 个 TS 文件 2,327 行，`test/` 59 个测试文件（全仓 143 个）。运行时依赖 `@clinebot/core ^0.0.38`（Cline 引擎的 npm 发布版）。
> **一句话定位**：Cline 团队自己的"IDE 替代品"——**一个看板，每张卡片 = 一个独立 git worktree + 一个独立终端会话，让多个编码 Agent 并行干活、互不冲突**。它不自己写 Agent 主循环，而是做"Agent 编排层"：把 Claude Code / Codex / Cline / Droid / Kiro 等 7 种 CLI Agent（以及 Cline native SDK）装进卡片，用看板四列（backlog→in_progress→review→trash）管流程，用卡片链接做依赖链，用 hooks 反向注入实时状态。
> **读者对象**：已经读过本系列至少一份分析的读者（尤其 Cline 源码分析）。本文沿用统一框架，标注 `文件:行号`，可回源码核对。

---

## 目录

1. [项目概览：IDE 的替代品——多 Agent 并行看板](#ch1)
2. [全景架构：浏览器控制面 + 本地 runtime 数据源 + 双 Agent 路径](#ch2)
3. [启动流程：CLI 拉起本地 server，浏览器是唯一界面](#ch3)
4. [看板数据模型：卡片、四列、依赖链、会话状态机](#ch4)
5. [git worktree 资源隔离：一卡一工作树 + symlink 共享依赖 + checkpoint](#ch5)
6. [终端子系统：node-pty + 双 WebSocket + 背压与协议过滤](#ch6)
7. [多 Agent 适配层：7 种 CLI Agent + hooks 反向注入](#ch7)
8. [Cline SDK 集成：ClineCore 多会话宿主 + 事件翻译 + 上下文压缩](#ch8)
9. [Provider 与 MCP：模型配置与工具注入](#ch9)
10. [tRPC API 层与认证安全：四层纵深防御](#ch10)
11. [前端看板 UI：React 状态驱动 + 持久终端 + 乐观并发](#ch11)
12. [桌面端 Electron：纯进程包装器 + OAuth relay](#ch12)
13. [工程化与发布：dogfood、CI/CD、GritQL 静态检查](#ch13)
14. [横向对比：与 Cline 引擎 / Claude Code / 任务管理类产品](#ch14)
15. [总结：三个最独特的设计与取舍](#ch15)

---

<h2 id="ch1">第 1 章 项目概览：IDE 的替代品——多 Agent 并行看板</h2>

**kanban 是什么？** README 第一行：*A replacement for your IDE better suited for running many agents in parallel and reviewing diffs*。它解决的是 Cline 团队自己的真实痛点：**当你有几十上百个编码 Agent 在跑，IDE 已经装不下它们了**——每个 Agent 都在编辑同一份代码、互相踩对方的 diff、你没法同时盯着它们的输出。

kanban 的答案：**把"Agent 会话"变成一张张可拖拽的卡片**。

- 每张卡片有自己的**独立 git worktree**（`git worktree add --detach`），Agent 在隔离的工作树里干活，天然零冲突。
- 每张卡片有自己的**独立终端**（node-pty 进程），Agent 的 TUI 直接渲染在浏览器里。
- 用**看板四列**管流程：backlog（待办）→ in_progress（执行中）→ review（待审查）→ trash（已完成/丢弃）。
- 用**卡片链接**做依赖链：`⌘`+点击链接两张卡，前面的卡完成进 trash 后，后面的卡自动启动。配合 auto-commit 就是全自主流水线。
- 用 **hooks 反向注入**：Agent 每说一句话、每次工具调用，都通过 hook 回调 `kanban` 自身 CLI，把状态实时推上卡片——你可以一眼扫几百张卡而不用打开任何一个终端。

它是 Cline 生态的一部分（`github.com/cline/` 组织），核心依赖 `@clinebot/core`（Cline 引擎的 npm 发布版）——但**不锁定 Cline**：默认检测本机装了什么 CLI Agent（claude / codex / cline / droid / kiro），全都要。

> [!WARNING]
> README 自己声明：这是 **Research Preview**，依赖 CLI Agent 的实验特性（bypass 权限、runtime hooks）——`agent-catalog.ts` 里各 Agent 的 autonomous 启动参数写得很直白：`--permission-mode auto`、`--dangerously-bypass-approvals-and-sandbox`、`--auto-approve-all`。

### 数字化的项目形状

| 维度 | 数字 | 说明 |
|---|---|---|
| 版本 | v0.1.70（2026-07） | 从 v0.1.4 到 v0.1.70 只用了约 5 个月（CHANGELOG 565 行） |
| 后端 | src/ 92 文件 / 30,402 行 | 本地 runtime：server、PTY、git、tRPC、cline-sdk |
| 前端 | web-ui/src 240 个 ts/tsx 文件 / 58,039 行（web-ui/ 全目录 269 文件） | React + Vite + Tailwind v4，单页无路由 |
| 桌面端 | packages/desktop/src 2,327 行 TS | Electron 壳，纯进程包装器 |
| 依赖 | @clinebot/core ^0.0.38 | Cline 引擎 npm 发布版（有状态会话模型） |
| 支持 Agent | 7 种 | claude/codex/cline/opencode/droid/kiro/gemini（启用 5 种） |
| 存储 | JSON 文件 + git refs | `~/.cline/kanban/`（看板状态）+ `~/.cline/worktrees/`（工作树）+ git refs（checkpoint） |
| 许可 | Apache-2.0 | 商业友好 |

### 它和 Cline / Claude Code 的关系

- **和 Cline**：Cline（上一份分析）是"引擎 + 界面"monorepo，核心是单 Agent 的 `AgentRuntime`；kanban 直接 import `@clinebot/core`，把 Cline 引擎塞进卡片跑。**Cline 是单 Agent 引擎，kanban 是 N 个 Agent 的调度看板**——它们的关系是"引擎 vs 编排层"。
- **和 Claude Code**：Claude Code 是单会话 TUI（Ink）；kanban 把 Claude Code 当**子进程**跑（`--settings` 注入 hooks），每个任务一个。README 说得很直白：IDE（Claude Code 住的地方）装不下并行 Agent 了。

---

<h2 id="ch2">第 2 章 全景架构：浏览器控制面 + 本地 runtime 数据源 + 双 Agent 路径</h2>

`docs/architecture.md` 给了三个核心理念，是整个架构的钥匙：

1. **浏览器只是控制面（control plane）**：渲染、发命令、收实时更新。不存状态、不做业务逻辑。
2. **本地 runtime 是唯一数据源（source of truth）**：projects / worktrees / sessions / git / 流式状态，全部在本地 Node 进程里。
3. **两条 Agent 执行路径**：绝大多数 Agent 是 **PTY 进程**（CLI 二进制直接跑在 node-pty 里）；Cline 走 **native SDK 会话**（`ClineCore` 进程内宿主，session-oriented）。

### 全景分层

```
┌─────────────────────────────────────────────────────────┐
│  浏览器（控制面）  React SPA：看板 / xterm 终端 / git 界面  │
│  tRPC httpBatchLink ── WS 状态流 ── WS 终端 io ── WS control │
└──────────────┬──────────────────────────────────────────┘
               │ localhost HTTP + WebSocket
┌──────────────▼──────────────────────────────────────────┐
│  本地 runtime（唯一数据源）  src/ 约 3 万行                 │
│  ┌──────────┬─────────────┬──────────────┬─────────────┐ │
│  │ tRPC 层   │ 状态流 hub   │ 终端管理器    │ git 层       │ │
│  │ 4 router  │ runtime-    │ session-     │ task-       │ │
│  │ 60+ proc  │ state-hub   │ manager      │ worktree    │ │
│  └──────────┴──────┬──────┴──────┬───────┴──────┬──────┘ │
│            ┌───────▼──────┐ ┌────▼─────┐ ┌───────▼─────┐  │
│            │ Cline SDK    │ │ PTY 进程  │ │ 7 种 Agent  │  │
│            │ ClineCore    │ │ node-pty │ │ CLI 适配器   │  │
│            │ (进程内)      │ │          │ │ (子进程)     │  │
│            └──────────────┘ └──────────┘ └─────────────┘  │
└──────────────────────────────────────────────────────────┘
```

### 两条执行路径的细节

**路径 A：PTY 子进程**（claude / codex / droid / kiro / opencode / gemini）
`session-manager.ts` 给每张卡 spawn 一个 node-pty 进程，Agent 的 TUI 跑在里面。Agent 的 hook 配置（claude 的 `--settings`、codex 的 `-c hooks.*`、kiro 的 `~/.kiro/agents/kanban.json` 等）把每个事件回调成 `kanban hooks notify`——这是"反向注入"：**Agent 在干活时主动调用 kanban 的 CLI**。

**路径 B：Cline native SDK**（内置 cline）
`cline-sdk/` 层在**同一进程内** import `@clinebot/core` 的 `ClineCore` 类，`ClineCore.create({backendMode:"auto"})` 起一个多会话宿主，kanban 自己包一层 `ClineSessionRuntime` 做"任务 ↔ 会话"映射。事件直接订阅，不需要 hooks。

### 为什么需要两条路径？

Cline SDK 能给最深的集成（Provider 目录、OAuth、MCP 工具注入、账户余额），但**只能跑 Cline**；PTY 路径啥都能跑但只能靠 hooks 捞状态。kanban 的策略是：**默认 Cline（深度集成），其他 Agent 自动降级为 PTY + hooks**。

---

<h2 id="ch3">第 3 章 启动流程：CLI 拉起本地 server，浏览器是唯一界面</h2>

### 入口（`src/cli.ts`，755 行）

`src/index.ts` 只是 re-export。真正入口 `cli.ts` 用 **commander**：

- 主命令 `kanban`：启动本地 server 并自动开浏览器。`shouldAutoOpenBrowserTabForInvocation` 判断哪些调用方式该开浏览器（直接 npx 开，被其他进程调用则不开）。
- **端口复用**：`tryOpenExistingServer` 探测端口是否已被 kanban 占用——是则复用现有实例，只开浏览器，不重复起 server。
- 选项：`--host/--port/--no-open/--skip-shutdown-cleanup/--https/--cert/--key/--update/--no-passcode`。
- 子命令（`src/commands/`）：`task`（list/create/update/trash/delete/link/unlink/start）、`hooks`（ingest/notify/gemini-hook/codex-hook/codex-wrapper）、`update`；`mcp` 已废弃。
- **懒加载**：server 栈（tRPC、终端、git）全部动态 import，跑 `task` 子命令时只走轻量路径，秒起。

### HTTP 服务器（`src/server/runtime-server.ts`，519 行）

不用 express——**原生 `node:http`/`node:https`**。请求链路：

```
middleware（Host 白名单 + CORS 门）
  → passcode gate（仅远程模式）
  → MCP OAuth callback（/kanban-mcp/mcp-oauth-callback）
  → tRPC handler（createHTTPHandler，basePath /api/trpc）
  → 静态资源（assets.ts：多路径探测 web-ui 产物、防路径穿越、SPA fallback）
```

**实时推送用 WebSocket（`ws` 库）而非 SSE**：`/api/runtime/ws` 由 `runtime-state-hub.ts`（604 行）fanout——首次连接发全量 `snapshot`，之后增量推 `workspace_state_updated` / `task_sessions_updated`（150ms 批合并）/ `task_chat_message` / `task_ready_for_review` 等 11 种消息。终端走独立的 io / control 两个 WS（见第 6 章）。

浏览器用 `open` 包打开（`browser.ts`）。

---

<h2 id="ch4">第 4 章 看板数据模型：卡片、四列、依赖链、会话状态机</h2>

### API 契约（`src/core/api-contract.ts`，1277 行）

全部 API 契约用 **zod** 定义，前后端 + CLI 三方共享（web-ui 通过 alias `@runtime-trpc` import 同一份）。这是全项目最稳的根基：**一个 z 类型定义全 API**，服务端校验、CLI 构造、前端渲染读的都是同一份 schema。

### 卡片模型（`RuntimeBoardCard`）

| 字段 | 说明 |
|---|---|
| `id` | 5 位短 id（`task-id.ts`，可读可记） |
| `title` | 由 prompt 自动推导（`task-title.ts` 截 80 字） |
| `prompt` | 发给 Agent 的任务描述 |
| `startInPlanMode` | 是否以 plan 模式启动 |
| `autoReviewEnabled` / `autoReviewMode` | 自动审查（commit / pr） |
| `images` | 附带的截图 |
| `agentId` | 用哪个 Agent（claude/codex/cline/...） |
| `clineSettings` | Cline 会话专属设置 |
| `baseRef` | 基于哪个 git ref 建 worktree |
| `createdAt` / `updatedAt` | 时间戳 |

### 四列 + 依赖链

看板固定 **4 列**：`backlog → in_progress → review → trash`（"done" 兼容映射到 trash）。

`dependencies`（fromTaskId→toTaskId）是自动化核心：
- **只有 backlog 任务能被链接**（作为依赖或等待者）。
- **review 卡被 trash 时，解锁其 backlog 依赖并自动启动**——这就是"依赖链自动流水线"：任务 A 完成进 trash → 任务 B 自动开始。

### 会话状态机（独立于列的迁移）

`session-state-machine.ts`（78 行）纯函数 `reduceSessionTransition`：

```
idle → running → awaiting_review（reviewReason: attention|exit|error|interrupted|hook）
                → interrupted → running
```

- `hook.to_review`：running → awaiting_review（Agent 请求人工介入）
- `hook.to_in_progress` / `agent.prompt-ready`：回 running
- `process.exit`：→ interrupted 或 awaiting_review

卡片 UI 上的状态点颜色、`Waiting for review` 标签都来自这个状态机。

### 持久化与并发（`src/state/workspace-state.ts`，745 行）

- 存储：`~/.cline/kanban/workspaces/<id>/{board.json, sessions.json, meta.json}`。
- **revision 乐观并发**：`saveWorkspaceState` 带 `expectedRevision`，meta.json 自增，冲突抛 `WorkspaceStateConflictError` 并把 `currentRevision` 透出——前端拿到后自动同步再重试。
- 所有 mutation（`task-board-mutations.ts` 657 行）是**纯函数式**：add/move/update/trash/delete/link 每次返回新 board，由调用方持久化。看板状态变化跟 React 的不可变更新一个思路。

---

<h2 id="ch5">第 5 章 git worktree 资源隔离：一卡一工作树 + symlink 共享依赖 + checkpoint</h2>

这是 kanban 最核心的工程设计——**用 git 的机制做资源隔离**。

### 一卡一 worktree（`src/workspace/task-worktree.ts`，695 行）

```
git worktree add --detach <baseCommit>
路径：~/.cline/worktrees/<taskId>/<workspaceLabel>
```

关键决策：
- **detached HEAD，不建分支**。分支在 Commit / Open PR 时才动态创建（commit 模板 cherry-pick 回 base worktree）。
- **worktree 一经存在视为权威**，不因 base 前进而重建（避免误删任务进度）。
- **trash 时捕获 diff patch** 存 `trashed-task-patches/<taskId>.<commit>.patch`，`git worktree remove --force` 清理；resume 时 `git apply` 恢复——卡片可以"死了再活"。
- 整个创建过程在 `.git/kanban-task-worktree-setup.lock` 锁内串行。

### symlink 共享依赖（`syncIgnoredPathsIntoWorktree`，L358）

多 worktree 最大的成本是每个都要 `npm install`。kanban 的解法：

1. `git ls-files --others --ignored --exclude-per-directory=.gitignore --directory` 列出主仓库所有 gitignored 路径（node_modules、dist 等）。
2. 对每个路径在原位 **symlink（主仓库 → worktree）**——Agent 在 worktree 里读 node_modules，实际读到的是主仓库的那份。
3. 同时在 `.git/info/exclude` 写入 `# kanban-managed-symlinked-ignored-paths` block，保证 symlink 本身不被提交。
4. 失败静默跳过（`mirrorIgnoredPath` 捕获一切错误返回 "skipped"），不阻断 worktree 创建。
5. `getUniquePaths` 深度排序只保留最浅根路径（有 node_modules 就不重复建 node_modules/foo/bar）。

**Turbopack 例外**（`task-worktree-turbopack.ts`，167 行）：Turbopack 会**跟随 symlink 走真实路径**，若 node_modules 被 symlink 会导致增量缓存失效、多 worktree 争用同一 `node_modules/.cache`——所以检测到 Turbopack 项目时这些 `node_modules` **不 symlink**（加入 skip 集）。

### checkpoint（`src/workspace/turn-checkpoints.ts`，92 行）

与 Cline 引擎的 checkpoint 无关，是 kanban 自建：**每个消息轮结束后**，在 worktree 里用**临时 index**（`GIT_INDEX_FILE` 指向 /tmp 临时文件 → `read-tree HEAD` → `add -A` → `write-tree` → `commit-tree`）创建真实 commit（作者 `kanban-checkpoint`），再 `update-ref refs/kanban/checkpoints/<base64(taskId)>/turn/<N>`。

关键：**不污染工作区、不碰分支，纯 ref**。回滚 = 切到对应 ref。调用点在 `runtime-api.ts` 每轮会话收尾时 best-effort 捕获。

### git 界面数据（`git-history.ts`，478 行）

浏览器里的 commit 图：`git log --topo-order --date-order`，字段用 `\x1f`/`\x1e` 分隔（hash/作者/日期/主题/parentHashes），配 `rev-list --count` 分页；分支/上游用 `for-each-ref` + `rev-list --not` 算出每个 commit 的 relation（selected/upstream/shared）供提交图着色。

### 合并策略（`git-sync.ts`，385 行）

**kanban 不自动 commit/merge**——`runGitSyncAction` 只支持 fetch / pull --ff-only / push / checkout / discard。auto-commit / auto-open-pr 的实现方式是**注入 prompt 让 Agent 自己 git add/commit/push/开 PR**（见第 10 章 append-system-prompt）。这是很聪明的取舍：git 冲突的处理交给 Agent（它懂上下文），kanban 只负责流程。

### 文件锁（`src/fs/locked-file-system.ts`，165 行）

基于 proper-lockfile：`withLocks` 按 lockfile 路径**排序后逐个加锁**（防死锁）；`writeTextFileAtomic` 写临时文件 + `rename` 原子替换。worktree 创建、patch 写入、info/exclude 更新全走它。

---

<h2 id="ch6">第 6 章 终端子系统：node-pty + 双 WebSocket + 背压与协议过滤</h2>

### PTY（`src/terminal/pty-session.ts`，161 行）

用 **node-pty**：`PtySession.spawn()` 创建真实 PTY 进程，Agent CLI 直接跑在里面。`onData` 把输出字节块交给 manager；`write()` 把浏览器输入写回；`stop()` 时 `kill()` 后 `process.kill(-pid, SIGTERM)` 杀**整个进程组**（POSIX 侧）。

数据流：`PTY → onData → 协议过滤 → TerminalStateMirror（headless xterm 镜像）→ 广播给所有 WS listener → 浏览器 xterm 渲染`。

### 双 WebSocket（`src/terminal/ws-server.ts`，591 行）

两个 `WebSocketServer({noServer:true})`，在 `server.on("upgrade")` 按 pathname 分流，`clientId` query 区分多标签页：

- **io 流**：服务端直接 `ws.send(chunk)` 原始 Buffer 字节流（浏览器 xterm 直接喂）；浏览器 `message` 即键盘字节透传 `writeInput`。
- **control 流**：JSON 消息 `{type:"state", summary}` / `{type:"exit", code}` / `{type:"restore", snapshot, cols, rows}`；浏览器发 `resize` / `stop` / `output_ack` / `restore_complete`。

**多标签页共享**：同一任务多个 viewer 共享同一个 PTY，用 `TerminalStateMirror`（xterm headless + SerializeAddon）给新 viewer 做**离线滚动快照恢复**。

**背压（仿 VS Code）**：跟踪每个 viewer 的 `bufferedAmount` + 未 ack 字节（高水位 100KB），任一 viewer 落后就 `pauseOutput` 暂停共享 PTY，全部跟上才 `resumeOutput`——防止慢浏览器拖垮 Agent 进程。

### 协议过滤（`terminal-protocol-filter.ts`）

逐字节解析 ESC/OSC/CSI 序列：**拦截 `OSC 10;?`/`11;?`**（浏览器未连时由服务端代答前景/背景色）、**丢弃 Droid 的 DA（Device Attributes）查询**（`CSI c`）——避免 TUI 等待一个还没连上的浏览器而卡死；跨 chunk 半截序列用 `pendingChunk` 暂存。ANSI 剥色用自研 `stripAnsi()`（`output-utils.ts`，不引第三方库）。

### workspace-trust 自动确认（`claude-workspace-trust.ts` / `codex-workspace-trust.ts`）

检测 PTY 输出的信任提示文本（claude 正则 "trust this folder"；codex 有序 token "do you trust the contents of this directory"），滚动缓冲 16KB 延迟 100ms **自动发送回车确认**——但**只对 kanban 自己创建的 worktree** 自动信任。启动期间还拦截 TUI 的 OSC 10/11 颜色查询并合成回复。

### 进程管理（`session-manager.ts`，1040 行）

`TerminalSessionManager` 用 `Map<taskId, SessionEntry>` 管并发会话；崩溃恢复 `shouldAutoRestart`（5 秒窗口内最多 3 次）→ `scheduleAutoRestart` 用保存的 `restartRequest` 重跑；`markInterruptedAndStopAll` 停全部；`hydrateFromRecord` 重启后从磁盘恢复 summary。进程树清理双保险：PTY 杀进程组 + `server/process-termination.ts` tree-kill（Windows SIGTERM 树杀）。

---

<h2 id="ch7">第 7 章 多 Agent 适配层：7 种 CLI Agent + hooks 反向注入</h2>

### 命令发现（`command-discovery.ts`，78 行）

**不做 `which`**（避免 spawn shell/conda/nvm 的副作用），直接用 `accessSync(X_OK)` 扫描 `process.env.PATH`（Windows 走 PATHEXT）。检测 7 个 binary：`claude/codex/cline/opencode/droid/kiro-cli/gemini`；`RUNTIME_LAUNCH_SUPPORTED_AGENT_IDS` 只启用 **cline/claude/codex/droid/kiro**（opencode/gemini 注释掉了）。**默认 cline**（恒 installed:true）。

### 7 个适配器（`agent-session-adapters.ts`，1451 行）

`prepareAgentLaunch()` 按 agentId 分发：

| Agent | 注入方式 | 机制 |
|---|---|---|
| **claude** | `--settings <hooks目录>/settings.json` | Stop/SubagentStop/PreToolUse/PermissionRequest/PostToolUse/Notification/UserPromptSubmit 7 种 hook 命令全指向 `kanban hooks notify --event ...`；autonomous 加 `--permission-mode auto` |
| **codex** | `-c features.hooks=true` + `-c hooks.Stop=...` 等 5 个 config override | 计算 hook 配置 **sha256 trust hash** 写入 `hooks.state` 避免 Codex 弹信任确认；输出解析靠 TUI session log |
| **gemini** | `GEMINI_CLI_SYSTEM_SETTINGS_PATH` | 指向生成的 settings.json（BeforeTool/AfterTool/BeforeAgent/AfterAgent/Notification） |
| **opencode** | 动态生成 **JS plugin** | 监听 `message.updated/session.status/tool.execute.before/permission.ask`，payload **base64 编码**后走 `$` shell 调 `kanban hooks notify --metadata-base64`；从 config/auth/model state 文件解析首选 model 防 provider 漂移 |
| **droid** | settings.json（autonomyMode: spec/auto-high/normal） | PreToolUse matcher 区分活跃工具 |
| **kiro** | 写 `~/.kiro/agents/kanban.json`（tools:["*"] + hooks） | `--agent kanban` 启动 |
| **cline** | `--hooks-dir` 下生成 bash/PowerShell 脚本 | 从 stdin 读 JSON、grep 判定 user_attention 后转 `to_review` |

### hooks 反向注入（`src/commands/hooks.ts`，820 行）

核心机制一句话：**Agent 在干活时，主动调用 kanban 自己的 CLI 来汇报状态**。

```
Agent 事件 → hook 命令（kanban hooks notify --event ...）
  → 子进程从 stdin 读 JSON payload（或 --metadata-base64）
  → 解析出 toolName/activityText/finalMessage
  → 经 tRPC hooks.ingest.mutate 回传 runtime server
  → 状态机迁移 + 广播 workspace_state_updated / task_ready_for_review
```

- `ingest` 是**同步阻塞**（等待 ack）；`notify` 是 `spawnBackgroundKanban()` 分离的后台进程（best-effort 不阻塞 Agent）。
- **Codex 特殊**：`hooks codex-wrapper` 包一层真实 codex，设 `CODEX_TUI_RECORD_SESSION=1` 让它写 session log + rollout jsonl，然后 `startCodexSessionWatcher()` 每 200ms 轮询文件增量，`parseCodexEventLine` 把 `task_started/agent_message/task_complete/exec_command_begin` 映射成活动事件（去重靠 fingerprint）。
- **状态归属**：`KANBAN_HOOK_TASK_ID/WS_ID` 环境变量（`hook-runtime-context.ts`）在 adapter ↔ hook 子进程间传递 task 归属。

**为什么不用轮询终端输出？** 因为各 Agent 的 TUI 输出格式完全不可靠；hook 是 Agent 官方支持的"事件出口"，语义干净（"这个工具跑完了""这条消息是给用户的"）。

---

<h2 id="ch8">第 8 章 Cline SDK 集成：ClineCore 多会话宿主 + 事件翻译 + 上下文压缩</h2>

`src/cline-sdk/` 17 个文件 / 7,094 行——kanban 与 Cline 引擎的深度集成层。这是与上一份 Cline 分析最有对照价值的章节。

### 不是 import SessionRuntime，而是 ClineCore（`sdk-runtime-boundary.ts`，126 行）

唯一 import 边界，重导出 `ClineCore.create({backendMode:"auto"})`。**关键认知**：npm 发布的 `@clinebot/core@0.0.38` 暴露的是**有状态的多会话宿主**——`ClineCore` 有 `sessions` Map（start/send/stop/abort/delete/get/list/update/readMessages/subscribe），会话是一等公民，`send()` 增量续聊、`subscribe()` 推流式事件、`SessionHistoryRecord` 落盘。

这与 Cline main 分支的**无状态 AgentRuntime**（`new AgentRuntime()` + `run("")` 重入）完全不同——**说明 @clinebot/core 的 npm 发布版与 cline 当前 main 分支的 API 演进方向不同**（0.0.36 兼容分支的注释也印证 API 层仍在快速变动）。kanban 依赖的是"有状态会话"模型，因为它天然需要"一个任务 = 一个可续聊的会话"。

### 任务 ↔ 会话映射（`cline-session-runtime.ts`，580 行）

`ClineSessionRuntime` 接口 + `InMemoryClineSessionRuntime` 实现：
- `sessionIdByTaskId / taskIdBySessionId` 双向 Map 绑定。
- `createSessionId(taskId)` = `"<taskId清洗>-<ts>-<rand>"`，重启后靠前缀从 `host.list()` 找回。
- `send()` 带 `delivery: "queue"|"steer"`——queue 排队，steer 打断当前回合。

### 事件翻译（`cline-event-adapter.ts`，816 行）

`applyClineSessionEvent(input)` 把 `subscribe()` 推来的事件解析为 5 类（agent_event / chunk / hook / status / ended），每种 `AgentEvent` 映射成 summary patch + 消息 mutation：

| SDK 事件 | 卡片效果 |
|---|---|
| `assistant-text-delta / content_start / content_end` | assistant 消息 |
| `tool-started / tool-finished` | tool 消息（`Tool: xxx / Input: ...`） |
| `run-finished / done` | awaiting_review（hook/error/interrupted） |
| `error / run-failed` | awaiting_review(error) |
| `assistant-reasoning-delta` | reasoning 消息 |
| `ask_followup_question / plan_mode_respond` | 需要人注意 → awaiting_review(attention) |

### 消息仓库（`cline-message-repository.ts`，364 行）

SDK 侧消息在 `~/.cline` 的会话存储里（`host.readMessages(sessionId)` 读回）；kanban 侧只在内存存一份 `ClineTaskMessage` 视图，页面刷新后 `hydrateTaskMessages` 从 SDK 持久化会话灌水。**kanban 不重复持久化 Cline 会话**——它信任引擎自己的存储。

### 上下文溢出压缩（`cline-context-overflow-compaction.ts`，125 行）

send/start 报错后用 **32 条正则**判定"prompt/context 超长"，然后 stop 旧会话，`compactPersistedMessagesForContextOverflow` 取后一半消息（截到 user 消息为界），把首条 user 消息预览嵌入 Cline 官方风格的压缩说明（`[Previous conversation history was removed...]`），`restartTaskSession(initialMessages=compacted)` 重跑。注释明确这是**临时方案**，等 SDK 提供可插拔 compaction。

### 工具调用显示（`cline-tool-call-display.ts`，352 行）

`getClineToolCallDisplay(toolName, input)` 把工具调用压缩成一行摘要（`read_files(a.ts:1-20, b.ts)`），针对 read_files/run_commands/search_codebase/editor/fetch_web_content/skills/ask_question 特化；`resolveKanbanCommandDisplay` 识别 `kanban task <create|link|...>` 转成人话（"Creating task"）——这样卡片上能直接看到"Agent 正在建任务"。

---

<h2 id="ch9">第 9 章 Provider 与 MCP：模型配置与工具注入</h2>

### Provider（`cline-provider-service.ts`，1294 行）

面向 UI 的 provider 门面 `createClineProviderService()`：
- **provider 目录**：`Llms.getAllProviders()`（Cline 生态全量）。
- **模型列表**：本地 + 远程 catalog + LiteLLM `/models` 探测。
- **登录方式**：managed OAuth 三类（cline / oca=Oracle Code Assist / openai-codex，token 加 `workos:` 前缀）、device auth、手动 apiKey、环境变量 `CLINE_API_KEY`/`OCA_API_KEY`。
- **账户**：`ClineAccountService` 查 profile/balance/org，OAuth 过期自动 refresh。
- `resolveLaunchConfig()` 组装 `{providerId, modelId, apiKey, baseUrl, reasoningEffort}` 注入 start。

默认 `cline` provider、默认模型 `anthropic/claude-sonnet-4.6`。

### MCP（`cline-mcp-settings-service.ts` 216 行 + `cline-mcp-runtime-service.ts` 879 行）

- settings-service 读写 **Cline 标准配置文件** `~/.cline/data/settings/cline_mcp_settings.json`——**与 Cline 扩展共享同一份 MCP 配置**（zod 校验，兼容 stdio/sse/streamableHttp）。
- runtime-service 在每次 `startTaskSession` 时 `createToolBundle()`：从 settings 读服务器 → `InMemoryMcpManager` 注册 → `createMcpTools` 生成 AgentTool → 通过 `localRuntime.extraTools` 注入 ClineCore，并设 `disableMcpSettingsTools:true` 去重。
- 自带**完整 MCP OAuth 流**：`@modelcontextprotocol/sdk` 的 Stdio/SSE/StreamableHTTP 客户端 + 自实现 `OAuthClientProvider`（持久化到 `cline_mcp_oauth_settings.json`），回调走 kanban 自己的 HTTP 端点 `/kanban-mcp/mcp-oauth-callback`。

### CLI 路径的 Provider

PTY 路径的 Agent 用**它们自己的配置**（claude 的 `~/.claude/settings.json`、codex 的 `~/.codex/` 等）——kanban 不碰，因为那是 Agent 自己的事。**只有 Cline SDK 路径需要 kanban 管理 provider**，这就是为什么 provider 服务全在 cline-sdk 层。

---

<h2 id="ch10">第 10 章 tRPC API 层与认证安全：四层纵深防御</h2>

### tRPC 边界（`src/trpc/app-router.ts`，731 行）

`runtimeAppRouter` 暴露 4 个 router：**runtime（约 40 个 procedure）、workspace（14 个）、projects（5 个）、hooks（1 个）**。模式是"薄路由 + 依赖注入"——每个 procedure 只是把输入/输出 schema 配对后委托给 context 里注入的 service。

- `workspaceProcedure` 中间件强制工作区作用域：从 `x-kanban-workspace-id` 头解析 `{workspaceId, workspacePath}`，缺失抛 BAD_REQUEST。
- `errorFormatter` 把 CONFLICT 错误的 `currentRevision` 透出给前端做乐观并发重试。
- 传输是**纯 HTTP fetch**（`httpBatchLink`），客户端有三个：web-ui 的 `getRuntimeTrpcClient`（按 workspaceId 缓存）、CLI `task.ts`/`hooks.ts`（自定义 fetch 自动附内部 Bearer token）。**没有 tRPC ws adapter**——WS 只用于状态流和终端。

### runtime API 核心 procedure

- `startTaskSession`：agentId 优先级 = 上次会话 > 卡片覆盖 > 配置默认；`cline` 走 SDK 路径，否则走 PTY 路径；trash 恢复时探测持久化 Cline 会话；best-effort 捕获 turn checkpoint。
- `stopTaskSession` / `sendTaskSessionInput`（先试 Cline 再试终端）/ `getTaskChatMessages` / `sendTaskChatMessage`（识别 `/clear`）/ `reloadTaskChatSession` / `abortTaskChatTurn` / `cancelTaskChatTurn`。
- `startShellSession` / `runCommand`（快捷按钮跑 `npm run dev`）。
- Cline 配置域：provider 增删改、账户 profile/balance/org/switch、OAuth 登录/设备码、MCP 设置/OAuth。
- `resetAllState`（删 `~/.cline/data|kanban|worktrees` 一键重置）。

### 注入给 home Agent 的看板指令（`src/prompts/append-system-prompt.ts`，316 行）

侧边栏聊天里那个"帮我把活儿拆成任务"的 Agent，被注入一段系统指令：
- **角色**："Kanban board management helper"，**明确禁止做编码/改文件**——实现请求一律转为建任务。
- **完整 CLI 参考**：`task list/create/update/done/delete/link/unlink/start` 的参数与语义。
- **链接与自动审查流水线**：review 完成 → 自动启动链接的 backlog 任务；auto-commit/auto-open-pr 组合实现全自主链式执行。
- **工作区识别**：检测 cwd 在 `.cline/worktrees/` 时强制 `--project-path`（从 worktree 回指主工作区）。

### 认证：四层纵深防御

1. **默认只绑 127.0.0.1**——其他机器根本不可达（`runtime-server.ts`）。
2. **Host 白名单**（`middleware.ts`）——防 DNS rebinding：非白名单 Host 直接拒绝。
3. **CORS 精确 Origin 白名单**——阻止本机其他网页跨源读取（恶意网页无法通过浏览器偷你的 API）。
4. **远程模式叠加 passcode**（`src/security/passcode-manager.ts`，247 行）：`--host` 绑定非 loopback 时启用。8 位随机码（`randomBytes` 拒绝采样、纯内存）、`timingSafeEqual` 比较、每 IP 5 次失败锁 30 秒、验证后发 32 字节随机 token（HttpOnly + SameSite=Strict Cookie，24h TTL）。另设 `KANBAN_INTERNAL_AUTH_TOKEN` bearer token 供 CLI 子进程（hooks ingest / task）认证。

**威胁模型**：当远程绑定，本地 runtime 暴露任意代码执行能力（git、shell、文件系统）。passcode 防的是**未经授权的浏览器访问者**（及暴力破解）——静态资源放行（让 React 的 PasscodeGate 先渲染），API 硬拦截。

---

<h2 id="ch11">第 11 章 前端看板 UI：React 状态驱动 + 持久终端 + 乐观并发</h2>

### 入口与布局（`web-ui/src/App.tsx`，1199 行）

单一根组件，**无 React Router，纯状态驱动视图切换**。Provider 栈：`PasscodeGateProvider → TelemetryProvider → AppErrorBoundary → TooltipProvider`。

布局 = 左侧 `ProjectNavigationPanel`（可拖拽/折叠）+ 右侧 TopBar + 主区。主区三种形态：
- **Home**：`KanbanBoard` 或 `GitHistoryView`（`isGitHistoryOpen` 切换）+ 底部 `ResizableBottomPane`（home 终端——一个不用建卡也能跑的 shell）。
- **CardDetailView**：选中卡片后 `absolute inset-0` 覆盖 Home，内部是 Chat 面板 + diff/文件树 + 底部 `AgentTerminalPanel`（URL query 记录 task id）。
- **Fallback**：断连 → `RuntimeDisconnectedFallback`；无权限 → `KanbanAccessBlockedFallback`。

### 看板状态机（`state/board-state.ts`，约 2.1 万行）

纯函数式看板状态机：`normalizeBoardData`（对 WS 原始数据做 schema 校验/清洗）、`addTaskToColumn`、`applyDragResult`、`moveTaskToColumn`、`trashTaskAndGetReadyLinkedTaskIds`（级联依赖）。`drag-rules.ts` 定义拖拽合法性（review 列不可手动放入、trash 只能进不能出）。

### 数据同步三件套（一致性核心）

1. **use-runtime-state-stream.ts**（约 1.5 万行）：连接 `ws://host/api/runtime/ws`，`useReducer` 维护 store，指数退避重连（500ms→5s）。收到 `snapshot` 全量灌入，增量消息按 `updatedAt` 合并、chat 按 id upsert。
2. **use-workspace-sync.ts**：stream 的 workspaceState → `normalizeBoardData` → setBoard，**用 `revision` 判重**（旧 revision 丢弃），页面重新可见时 refetch 全量校准。
3. **use-workspace-persistence.ts**：120ms 防抖 `saveWorkspaceState({board, sessions, expectedRevision})`；收到 CONFLICT 用返回的 `currentRevision` 更新本地、toast "Synced latest state" 并 refetch 覆盖。

策略一句话：**本地乐观更新（拖拽即时移动，失败回滚）→ 防抖保存 + expectedRevision 乐观锁 → WS 权威推送 + revision 单调性防旧写覆盖新写**。

### 持久终端（`terminal/persistent-terminal-manager.ts`，约 2.1 万行）

xterm@6 封装 + **双 WebSocket**（io 二进制 + control JSON）。`clientId`（randomUUID）标识浏览器标签页，后端按 taskId 共享同一 PTY、按 clientId 隔离 socket 状态。

**"持久"的含义**：DOM 节点可"停车"到隐藏的 `#kb-persistent-terminal-parking-root`——切卡片不销毁终端，`restore` 时把滚动缓冲快照写回（control 协议里的 `restore/snapshot`）。插件：Fit、WebGL（上下文丢失自动降级）、Clipboard、WebLinks、Unicode11；ResizeObserver 50ms 防抖上报 cols/rows。

### git 界面与提交/PR（`git-actions/build-task-git-action-prompt.ts`）

commit/PR prompt = 模板插值，四层回退：用户模板 → 用户 default 模板 → 服务端 default → 兜底句；唯一变量 `{{base_ref}}`。`use-git-actions.ts` 的 `handleCommitTask` → 组 prompt → `sendTaskSessionInput`（**paste 模式写入终端**）让 Agent 自己执行 git——又一次"把活儿交给 Agent"。

### 组件库与体验

- **非 shadcn**：`@radix-ui/react-*` 原语 + **Tailwind CSS v4** + `lucide-react` 图标 + `@hello-pangea/dnd`（看板拖拽）+ `react-virtuoso`（虚拟列表）+ `sonner`（toast）+ `motion`。`components/ui/` 是手写薄封装。
- **PWA**：`sw.js` 只拦截 navigation 请求，失败时返回品牌化 "Waiting for Cline" fallback 页并每 2s HEAD 轮询 `/` 自动刷新（服务端崩溃自动恢复）。
- **onboarding**：首次引导（选 Agent / 配 Cline provider），`isSelectedAgentAuthenticated` 判断门槛。
- **telemetry**：sentry（release `kanban@版本`）+ posthog（**仅 4 个手动事件**：task_created / task_dependency_created / tasks_auto_started_from_dependency / task_resumed_from_trash——只埋看板关键行为）。
- **open-targets**：按平台生成打开命令（`open -a "VS Code"` / `code <path>`），支持 vscode/cursor/windsurf/zed/xcode/ghostty 等——点卡片就能在 IDE 里打开 worktree。

---

<h2 id="ch12">第 12 章 桌面端 Electron：纯进程包装器 + OAuth relay</h2>

### 定位（`packages/desktop/`，src 11 个 TS 文件 2,327 行 + `disconnected.html` 152 行 + 13 个测试）

**桌面端是纯"进程包装器"，不 import 根包任何代码**。CLI 作为子进程 spawn（`spawn("kanban", ["--no-open","--port",3484,...])`），通过 HTTP 健康探测（检查 `<title>Kanban</title>`）确认就绪后，BrowserWindow 才 loadURL 到 `http://127.0.0.1:3484`。

模块分工：

| 模块 | 行数 | 职责 |
|---|---|---|
| `main.ts` | 311 | 单实例锁、`kanban://` 协议深链、窗口状态持久化、before-quit 强同步 shutdown |
| `runtime-orchestrator.ts` | 604 | 生命周期核心：connect（先探测已有 runtime，无则 startOwnRuntime）/restart/shutdown/dispose；双探针（attached 500ms 检测外部 runtime 崩溃、recovery 2s 自动重连）；powerSaveBlocker 防 App Nap |
| `runtime-child.ts` | 334 | spawn + 30s 就绪轮询 + treeKill 优雅关闭，4GB V8 堆上限 |
| `window-registry.ts` / `window-state.ts` | 298 / 178 | 多窗口管理、原子写状态文件、离屏窗口 clamp |
| `window-factory.ts` | 221 | renderer 崩溃恢复（did-fail-load + render-process-gone → 健康探测 → disconnected 屏/重试） |
| `oauth-relay.ts` | 46 | 见下 |

### OAuth relay

Agent 的 OAuth 回调以 `kanban://oauth/callback?...` 深链到达桌面端，main 进程解析后把参数转发给 `http://127.0.0.1:3484/kanban-mcp/mcp-oauth-callback`，重试 3 次失败弹窗提示——**纯 web 无法拦截系统级自定义协议**，这是桌面端存在的硬理由之一。

### 为什么需要桌面端？

1. **常驻进程**：原生菜单、多窗口、系统托盘。
2. **自带 Node 运行时**：shim 用 `ELECTRON_RUN_AS_NODE=1` 复用 Electron 二进制，用户不用装 Node。
3. **GUI 启动 PATH 增强**（`runtime-child-env.ts`）：补全 Homebrew/nvm 等路径——纯 CLI 场景没有这个问题，但双击打开的场景有。
4. **崩溃自动拉起**：recovery probe 2s 自动重连。

打包链路：root `build.mjs` esbuild 产出 `dist/cli.js`+`dist/web-ui/` → `stage-cli.mjs` 复制进 `packages/desktop/cli/` → electron-builder `extraResources` + `asarUnpack`（只解包 cli/node-pty/*.node）→ `patch-node-pty.mjs` postinstall 修复 `app.asar→unpacked` 双后缀 bug。

---

<h2 id="ch13">第 13 章 工程化与发布：dogfood、CI/CD、GritQL 静态检查</h2>

### dogfood：用 kanban 开发 kanban

- `scripts/dogfood.mjs`（470 行）：先 `npm run build`，再用独立进程组 spawn `dist/cli.js` 跑**生产构建**；`--skip-shutdown-cleanup` 交给锁文件选举的"清理 owner"执行（防多实例重复清理）；剥离 repo 的 `node_modules/.bin`（避免遮蔽全局 Agent CLI）；10s SIGKILL 兜底。
- 仓库自带 `.claude/`、`.cline/kanban/config.json`（shortcuts 跑 dogfood/dev-full）、`.clinerules/workflows/release.md`、`.codex/environments/environment.toml`（setup 脚本自动 symlink node_modules 到主 worktree）、`.factory/settings.json`——**同一个仓库同时是 Claude Code / Cline / Codex / Factory Droid 四个 Agent 的真实使用场景**，worktree+symlink 技巧被 .codex 配置直接复用。

### 开发规范（AGENTS.md，8KB "部落知识"）

- 无 `any`、禁 inline import、依赖升级而非降级。
- web-ui 用 Tailwind v4 + Radix + Lucide。
- **优先直接 PATH 检测而非交互 shell 启动 Agent**（conda/nvm 会卡死 runtime）。
- Cline 行为查 `@clinebot/core` + `src/cline-sdk/`。

### 静态检查与 CI

- **GritQL**（`grit/`，4 个 .grit）：禁 console.*、禁 `__home_agent__:` 字面量、禁 process.env 解构、禁 process.exit（除 CLI 入口）——比 lint 更强的结构性约束。
- `.husky/pre-commit`：biome check staged → typecheck → `test:fast`。
- `.github/`：`ci.yml`（push/PR）+ `test.yml`（3 平台矩阵：ubuntu20/22 + macos22）+ **`publish.yml`**：手动 workflow_dispatch 指定 tag → 校验版本一致 → `npm run prepublishOnly` → **OIDC trusted publishing `npm publish`** → 自动提取 changelog 建 GitHub Release → 发 Slack。

### 版本演进（CHANGELOG，v0.1.4 → v0.1.70）

| 版本 | 里程碑 |
|---|---|
| 0.1.4 | 起点：git worktree 并行任务、diff review、链接任务、MCP 集成 |
| 0.1.5 | Droid agent、dogfood、xterm + node-pty 终端 |
| 0.1.11 | **移除 MCP 改 skill**（架构转向的证据） |
| 0.1.12 | UI 重设计 |
| 0.1.15 | 跨平台 Windows/Linux |
| 0.1.65+ | runtime child manager、新版本通知、Electron 桌面 app 脚手架 |

5 个月从 0.1.4 到 0.1.70——**这正是"看板 + Agent 流水线"自己跑出来的速度**。

---

<h2 id="ch14">第 14 章 横向对比：与 Cline 引擎 / Claude Code / 任务管理类产品</h2>

### 14.1 与 Cline 引擎（上一份分析）

| 维度 | Cline（引擎） | kanban（编排层） |
|---|---|---|
| 定位 | 单 Agent 引擎（主循环 + 工具 + Provider） | N 个 Agent 的调度看板 |
| 核心抽象 | AgentRuntime 无状态循环 | 卡片 = worktree + 会话 |
| 会话模型 | 无状态 `run("")` 重入 | 有状态 `ClineCore` 会话（npm 0.0.38） |
| 状态展示 | TUI / WebView 单个 | 看板批量 + hook 实时 |
| 资源隔离 | 单工作区 | git worktree 每卡一个 |
| 关系 | kanban 依赖它（@clinebot/core） | 引擎中立（7 种 Agent 都支持） |

**本质差异**：Cline 解决"一个 Agent 怎么聪明地干活"，kanban 解决"一堆 Agent 怎么组织起来干活"。kanban 的编排能力（依赖链、自动启动、批量监控）是 Cline 引擎没有的；Cline 的深度（Provider/OAuth/MCP）kanban 通过 SDK 白拿。

### 14.2 与 Claude Code

| 维度 | Claude Code | kanban |
|---|---|---|
| 交互 | 单会话 Ink TUI | 看板 + xterm（多会话并行） |
| 多任务 | `--resume`/`--fork-session` 手动 | 卡片 + 依赖链自动 |
| 并行 | 单进程（可 spawn subagent） | 每卡独立 worktree + 进程 |
| 审查 | 无内置 | review 列 + 行内评论回传 |
| 关系 | kanban 把它当子进程（注入 hooks） | Claude Code 是 kanban 的 7 种 Agent 之一 |

### 14.3 与任务分解类多 Agent（OpenWorker / Manus 系）

那些是"Agent **内部**的多 Agent"（coordinator 拆任务 → 委派 specialist，共享一个会话上下文）；kanban 是"**开发流程级**的多 Agent"（每个 Agent 独立会话 + 独立 worktree + 人工 review 关卡）。前者做的是**推理分工**，后者做的是**工程并行**——一个是思维层面的，一个是仓库层面的。

### 14.4 与项目管理工具（Linear / Jira / GitHub Projects）

kanban 的卡片是**活的**：有终端、有 worktree、能启动 Agent、能被链接成流水线。传统看板的卡片是"需求描述"，kanban 的卡片是"一个正在干活的进程"——**把任务管理的对象从文档变成了执行单元**。

### 14.5 一句话各自的分工

- **Cline 引擎**：单 Agent 的思考与执行。
- **Claude Code**：单 Agent 的交互式终端。
- **kanban**：多 Agent 的组织、隔离、监控、审查与自动化流水线。
- **Claude Code / Codex / Cline / Droid**：kanban 的执行器（可插拔）。

---

<h2 id="ch15">第 15 章 总结：三个最独特的设计与取舍</h2>

### 三件事（如果只记三个设计）

**1. git worktree 即资源隔离，symlink 即成本优化**
不用容器、不用沙箱，`git worktree add --detach` 一行命令就让每个 Agent 拥有隔离工作树，零冲突并行；gitignored 目录（node_modules）symlink 回主仓库，省掉每卡一份 `npm install`。配合 `.git/info/exclude` 管理 symlink 自身。这是"用 git 的机制解决工程问题"的教科书案例——**代价**：Agent 修改 node_modules 之类的 gitignored 文件时共享会串（README 明说"don't use Kanban"），Turbopack 缓存会踩 symlink，所以又加了 Turbopack 检测例外。

**2. hooks 反向注入：Agent 主动调用 kanban 的 CLI 汇报状态**
不轮询终端输出（格式不可靠），而是在每个 Agent 的官方 hook 机制里注入 `kanban hooks notify`——Agent 每说一句话、每次工具调用都主动回调 kanban。这让几百张卡片的实时状态扫描成为可能，也天然适配 7 种 Agent（各自生成不同的 hook 配置）。**代价**：每个 Agent 都要写适配器，Codex 还得包 wrapper + 轮询 rollout jsonl（它连 hooks 都不可靠）；依赖实验特性（bypass 权限、runtime hooks），README 自己标了 Research Preview。

**3. 引擎中立 + 深度集成并存的双路径**
默认 Cline 走 native SDK（同进程、Provider/OAuth/MCP 全深度集成），其他 Agent 走 PTY 子进程（只靠 hooks）。一条路径拿到最深的集成，一条路径拿到最广的兼容。**代价**：两套执行路径、两套状态来源（SDK 事件 vs hook 事件），`sendTaskSessionInput` 得先试 Cline 再试终端；@clinebot/core 0.0.38 的 API 还在快速变动（0.0.36 兼容分支都留着）。

### 取舍与"适合谁"

- **适合**：多 Agent 并行开发的团队、想给 Agent 建自主流水线的个人、研究 Agent 编排的开发者。
- **不适合**：单 Agent 用户（Claude Code 本身就够）、需要强审计/权限控制的企业场景（默认 bypass 权限）、修改 gitignored 文件的场景（symlink 共享会串）。
- **与生态的关系**：它是 Cline 生态的"指挥台"——引擎（Cline）、执行器（各 CLI Agent）、编排层（kanban）三层分明。

### 🔍 源码指路（回源码核对）

| 想知道 | 看这里 |
|---|---|
| 卡片模型 / 四列 / 依赖 | `src/core/api-contract.ts`（1277 行）+ `task-board-mutations.ts` |
| 会话状态机 | `src/terminal/session-state-machine.ts`（78 行） |
| worktree + symlink | `src/workspace/task-worktree.ts`（695 行，L358 symlink） |
| checkpoint | `src/workspace/turn-checkpoints.ts`（92 行） |
| 终端 + 双 WS + 背压 | `src/terminal/pty-session.ts` + `ws-server.ts`（591 行） |
| 7 种 Agent 适配 | `src/terminal/agent-session-adapters.ts`（1451 行） |
| hooks 反向注入 | `src/commands/hooks.ts`（820 行） |
| Cline 集成边界 | `src/cline-sdk/sdk-runtime-boundary.ts`（126 行） |
| 事件翻译 | `src/cline-sdk/cline-event-adapter.ts`（816 行） |
| tRPC + 认证 | `src/trpc/app-router.ts` + `src/server/middleware.ts` + `passcode-manager.ts` |
| 前端同步三件套 | `web-ui/src/runtime/use-runtime-state-stream.ts` + `use-workspace-persistence.ts` |
| 持久终端 | `web-ui/src/terminal/persistent-terminal-manager.ts` |
| 桌面端 | `packages/desktop/src/runtime-orchestrator.ts`（604 行） |
| 看板注入指令 | `src/prompts/append-system-prompt.ts`（316 行） |
