源码解析 · 第十九份 · v0.1.70

让成百个 Agent
并行干活的看板

kanban 是 Cline 团队自己的「IDE 替代品」——每张卡片 = 一个独立 git worktree + 一个独立终端会话,把 7 种编码 Agent 并行组织起来。浏览器只是控制面,本地 runtime 是唯一数据源。hooks 反向注入让几百张卡片的实时状态尽收眼底,卡片链接构成全自主依赖链。全文标注 文件:行号,可回源码核对。

7
种 Agent
4
列看板
WS 终端
四层
安全防御

分析对象: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 源码分析)。本文沿用统一框架,标注 文件:行号,可回源码核对。


Part 2

第 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 worktreegit 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 了。

Part 3

第 2 章 全景架构:浏览器控制面 + 本地 runtime 数据源 + 双 Agent 路径

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/coreClineCore 类,ClineCore.create({backendMode:"auto"}) 起一个多会话宿主,kanban 自己包一层 ClineSessionRuntime 做"任务 ↔ 会话"映射。事件直接订阅,不需要 hooks。

为什么需要两条路径?

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


Part 4

第 3 章 启动流程:CLI 拉起本地 server,浏览器是唯一界面

入口(src/cli.ts,755 行)

src/index.ts 只是 re-export。真正入口 cli.tscommander

  • 主命令 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)、updatemcp 已废弃。
  • 懒加载: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/wsruntime-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)。


Part 5

第 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:回 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 乐观并发saveWorkspaceStateexpectedRevision,meta.json 自增,冲突抛 WorkspaceStateConflictError 并把 currentRevision 透出——前端拿到后自动同步再重试。
  • 所有 mutation(task-board-mutations.ts 657 行)是纯函数式:add/move/update/trash/delete/link 每次返回新 board,由调用方持久化。看板状态变化跟 React 的不可变更新一个思路。

Part 6

第 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 patchtrashed-task-patches/<taskId>.<commit>.patchgit worktree remove --force 清理;resume 时 git apply 恢复——卡片可以"死了再活"。 - 整个创建过程在 .git/kanban-task-worktree-setup.lock 锁内串行。

checkpoint(src/workspace/turn-checkpoints.ts,92 行)

与 Cline 引擎的 checkpoint 无关,是 kanban 自建:每个消息轮结束后,在 worktree 里用临时 indexGIT_INDEX_FILE 指向 /tmp 临时文件 → read-tree HEADadd -Awrite-treecommit-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 更新全走它。


Part 7

第 6 章 终端子系统:node-pty + 双 WebSocket + 背压与协议过滤

PTY(src/terminal/pty-session.ts,161 行)

node-ptyPtySession.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 行)

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


Part 8

第 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/geminiRUNTIME_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);notifyspawnBackgroundKanban() 分离的后台进程(best-effort 不阻塞 Agent)。
  • Codex 特殊hooks codex-wrapper 包一层真实 codex,设 CODEX_TUI_RECORD_SESSION=1 让它写 session log + rollout jsonl,然后 startCodexSessionWatcher() 每 200ms 轮询文件增量,parseCodexEventLinetask_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 官方支持的"事件出口",语义干净("这个工具跑完了""这条消息是给用户的")。


Part 9

第 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 暴露的是有状态的多会话宿主——ClineCoresessions Map(start/send/stop/abort/delete/get/list/update/readMessages/subscribe),会话是一等公民,send() 增量续聊、subscribe() 推流式事件、SessionHistoryRecord 落盘。

这与 Cline main 分支的无状态 AgentRuntimenew 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 正在建任务"。


Part 10

第 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 在每次 startTaskSessioncreateToolBundle():从 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 层。


Part 11

第 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 fetchhttpBatchLink),客户端有三个: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. 远程模式叠加 passcodesrc/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 硬拦截。


Part 12

第 11 章 前端看板 UI:React 状态驱动 + 持久终端 + 乐观并发

入口与布局(web-ui/src/App.tsx,1199 行)

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

布局 = 左侧 ProjectNavigationPanel(可拖拽/折叠)+ 右侧 TopBar + 主区。主区三种形态: - HomeKanbanBoardGitHistoryViewisGitHistoryOpen 切换)+ 底部 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 校验/清洗)、addTaskToColumnapplyDragResultmoveTaskToColumntrashTaskAndGetReadyLinkedTaskIds(级联依赖)。drag-rules.ts 定义拖拽合法性(review 列不可手动放入、trash 只能进不能出)。

数据同步三件套(一致性核心)

  1. use-runtime-state-stream.ts(约 1.5 万行):连接 ws://host/api/runtime/wsuseReducer 维护 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.tshandleCommitTask → 组 prompt → sendTaskSessionInputpaste 模式写入终端)让 Agent 自己执行 git——又一次"把活儿交给 Agent"。

组件库与体验

  • 非 shadcn@radix-ui/react-* 原语 + Tailwind CSS v4 + lucide-react 图标 + @hello-pangea/dnd(看板拖拽)+ react-virtuoso(虚拟列表)+ sonner(toast)+ motioncomponents/ui/ 是手写薄封装。
  • PWAsw.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。

Part 13

第 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 无法拦截系统级自定义协议,这是桌面端存在的硬理由之一。

为什么需要桌面端?

  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。


Part 14

第 13 章 工程化与发布:dogfood、CI/CD、GritQL 静态检查

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

  • GritQLgrit/,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 prepublishOnlyOIDC 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 流水线"自己跑出来的速度


Part 15

第 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 的执行器(可插拔)。

Part 16

第 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 行)