分析对象: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/src240 个 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 源码分析)。本文沿用统一框架,标注文件:行号,可回源码核对。
目录
- 项目概览:IDE 的替代品——多 Agent 并行看板
- 全景架构:浏览器控制面 + 本地 runtime 数据源 + 双 Agent 路径
- 启动流程:CLI 拉起本地 server,浏览器是唯一界面
- 看板数据模型:卡片、四列、依赖链、会话状态机
- git worktree 资源隔离:一卡一工作树 + symlink 共享依赖 + checkpoint
- 终端子系统:node-pty + 双 WebSocket + 背压与协议过滤
- 多 Agent 适配层:7 种 CLI Agent + hooks 反向注入
- Cline SDK 集成:ClineCore 多会话宿主 + 事件翻译 + 上下文压缩
- Provider 与 MCP:模型配置与工具注入
- tRPC API 层与认证安全:四层纵深防御
- 前端看板 UI:React 状态驱动 + 持久终端 + 乐观并发
- 桌面端 Electron:纯进程包装器 + OAuth relay
- 工程化与发布:dogfood、CI/CD、GritQL 静态检查
- 横向对比:与 Cline 引擎 / Claude Code / 任务管理类产品
- 总结:三个最独特的设计与取舍
第 1 章 项目概览:IDE 的替代品——多 Agent 并行看板
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 了。
第 2 章 全景架构:浏览器控制面 + 本地 runtime 数据源 + 双 Agent 路径
docs/architecture.md 给了三个核心理念,是整个架构的钥匙:
- 浏览器只是控制面(control plane):渲染、发命令、收实时更新。不存状态、不做业务逻辑。
- 本地 runtime 是唯一数据源(source of truth):projects / worktrees / sessions / git / 流式状态,全部在本地 Node 进程里。
- 两条 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。
第 3 章 启动流程:CLI 拉起本地 server,浏览器是唯一界面
入口(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)。
第 4 章 看板数据模型:卡片、四列、依赖链、会话状态机
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:回 runningprocess.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.ts657 行)是纯函数式:add/move/update/trash/delete/link 每次返回新 board,由调用方持久化。看板状态变化跟 React 的不可变更新一个思路。
第 5 章 git worktree 资源隔离:一卡一工作树 + symlink 共享依赖 + checkpoint
这是 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 的解法:
git ls-files --others --ignored --exclude-per-directory=.gitignore --directory列出主仓库所有 gitignored 路径(node_modules、dist 等)。- 对每个路径在原位 symlink(主仓库 → worktree)——Agent 在 worktree 里读 node_modules,实际读到的是主仓库的那份。
- 同时在
.git/info/exclude写入# kanban-managed-symlinked-ignored-pathsblock,保证 symlink 本身不被提交。 - 失败静默跳过(
mirrorIgnoredPath捕获一切错误返回 "skipped"),不阻断 worktree 创建。 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 更新全走它。
第 6 章 终端子系统:node-pty + 双 WebSocket + 背压与协议过滤
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 树杀)。
第 7 章 多 Agent 适配层:7 种 CLI Agent + hooks 反向注入
命令发现(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 官方支持的"事件出口",语义干净("这个工具跑完了""这条消息是给用户的")。
第 8 章 Cline SDK 集成:ClineCore 多会话宿主 + 事件翻译 + 上下文压缩
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 正在建任务"。
第 9 章 Provider 与 MCP:模型配置与工具注入
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 层。
第 10 章 tRPC API 层与认证安全:四层纵深防御
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 缓存)、CLItask.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 回指主工作区)。
认证:四层纵深防御
- 默认只绑 127.0.0.1——其他机器根本不可达(
runtime-server.ts)。 - Host 白名单(
middleware.ts)——防 DNS rebinding:非白名单 Host 直接拒绝。 - CORS 精确 Origin 白名单——阻止本机其他网页跨源读取(恶意网页无法通过浏览器偷你的 API)。
- 远程模式叠加 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_TOKENbearer token 供 CLI 子进程(hooks ingest / task)认证。
威胁模型:当远程绑定,本地 runtime 暴露任意代码执行能力(git、shell、文件系统)。passcode 防的是未经授权的浏览器访问者(及暴力破解)——静态资源放行(让 React 的 PasscodeGate 先渲染),API 硬拦截。
第 11 章 前端看板 UI:React 状态驱动 + 持久终端 + 乐观并发
入口与布局(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 只能进不能出)。
数据同步三件套(一致性核心)
- use-runtime-state-stream.ts(约 1.5 万行):连接
ws://host/api/runtime/ws,useReducer维护 store,指数退避重连(500ms→5s)。收到snapshot全量灌入,增量消息按updatedAt合并、chat 按 id upsert。 - use-workspace-sync.ts:stream 的 workspaceState →
normalizeBoardData→ setBoard,用revision判重(旧 revision 丢弃),页面重新可见时 refetch 全量校准。 - 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。
第 12 章 桌面端 Electron:纯进程包装器 + OAuth relay
定位(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 无法拦截系统级自定义协议,这是桌面端存在的硬理由之一。
为什么需要桌面端?
- 常驻进程:原生菜单、多窗口、系统托盘。
- 自带 Node 运行时:shim 用
ELECTRON_RUN_AS_NODE=1复用 Electron 二进制,用户不用装 Node。 - GUI 启动 PATH 增强(
runtime-child-env.ts):补全 Homebrew/nvm 等路径——纯 CLI 场景没有这个问题,但双击打开的场景有。 - 崩溃自动拉起: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。
第 13 章 工程化与发布:dogfood、CI/CD、GritQL 静态检查
dogfood:用 kanban 开发 kanban
scripts/dogfood.mjs(470 行):先npm run build,再用独立进程组 spawndist/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 publishingnpm 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 流水线"自己跑出来的速度。
第 14 章 横向对比:与 Cline 引擎 / Claude Code / 任务管理类产品
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 的执行器(可插拔)。
第 15 章 总结:三个最独特的设计与取舍
三件事(如果只记三个设计)
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 行) |