
这是什么
一个给「开发者机器上本来就装好的编码 agent」用的终端工作台,而且它不重写任何一个:每个会话启动的是用户自己那份 CLI,所以登录、订阅、配置文件、MCP 服务器以及那个工具自带的一切功能都原样沿用。开箱支持十种——Claude Code、OpenCode、Codex、Grok Build、Gemini CLI、Antigravity CLI、Pi、Command Code、Hermes Agent 与 Muse Code——每种都跑在私有 tmux 服务器上自己的常驻会话里,全部收在同一张带实时状态的列表里。要干活不必离开这张列表:space 把一句话送进选中会话的面板,enter 就地接管它而列表仍在屏幕上,v 让死掉的会话带着自己的对话回来。ctrl+r 把 agent 改过的东西以整文件、带语法高亮的 diff 打开,在行上留下的评论会作为一整轮编号反馈回到那个 agent 的面板里。会话还能派生、给别的会话发消息并等它结束,因为每个支持 MCP 的会话在启动时就带着这些工具。Apache-2.0,用 Go 写在 Bubble Tea 上,跑在 macOS、Linux,以及 WSL2 里的 Windows。
谁做的仓库的 647 次提交来自 27 个账号,其中 563 次是他的;接下来是 18 次、一个依赖机器人 11 次,以及一位做测试覆盖的贡献者 9 次。41 条共同作者尾注里有 13 条点名了 Claude 模型,写清版本的那些是百万上下文的 Opus 5 与 Opus 5.5。
它是怎么搭起来的
组成 · 6一个长驻的终端程序,持有一批比它自己活得更久的 agent 进程。程序拥有自己的一套 tmux 服务器,把用户装好的 CLI 启进「每个 agent 一个会话」里,此后完全靠观察与注入工作:读面板判断每个 agent 在干什么,往面板里写来回答它们,从不通过 agent 本身并不暴露的协议去跟它说话。两个后果塑造了代码。凡是各家 CLI 不一样的东西都是数据而不是代码——启动与恢复参数、状态模式、每个界面的怪癖——于是一个新模型、一屏改版都只是配置改动;指令把「维护一份会被上游 release 改掉的取值表」直接定义为缺陷。而因为界面是一个绝不允许阻塞的 Bubble Tea 模型,轮询、diff、剪贴板检查和更新检查都是命令而不是行内工作,这也是为什么轮询器和状态机是树里最大的两个源码文件。
- internal/status 与 internal/ui/poller.go
- 状态机,以及喂给它的东西:36,736 字节的分类器配 123,149 字节的测试,加一个 52,629 字节的轮询器,按用户可配置的间隔走遍每个会话的面板。它们施加的规则与启动参数住在隔壁那个 38,344 字节的配置文件里,其中内置工具表是整个程序里工具定义的唯一来源。
- internal/tmux
- 一套通过单一驱动访问的专用 tmux 服务器:40,294 字节的 tmux 层配 63,488 字节的测试,以及 9,529 字节的控制模式客户端。每次调用都点名
agentmgr这个 socket;面板基准下标被钉在 0,因为某个 CLI 会把自己的窗口切分;粘贴走一个名字里带进程号的缓冲区。 - internal/ui
- 107 个文件、1,785 KB:一个 Bubble Tea 模型,外壳 73,774 字节,外围是 63,274 字节的列表视图和 39,324 字节的生命周期文件,再往外是按功能分的文件——快捷提示、输入框、焦点与选中、设置、键位表、行菜单、重命名、重排、拖放、分栏布局、主题、通知与浮条。
- internal/agentsession、internal/mcpserver 与 internal/mcpreg
- 管理器怎么知道身份、又怎么声明身份。九个文件从各家 CLI 自己的存储里读出会话标识——每种 CLI 一个文件,外加一层 31,389 字节的抽取——而一个 44,127 字节、配 48,382 字节测试的 MCP 服务器给 agent 提供互相派生、发消息和等待的工具,另有 13,440 字节的注册器把服务器条目写进每种 CLI 的配置。
- internal/git、internal/diff 与 internal/store
- 程序自己持有的三块状态:25,422 字节的 git 管道配 48,647 字节的测试、13,478 字节的 diff 引擎,以及 56,283 字节配 59,598 字节测试的 SQLite 存储——它的迁移列表只许追加,因为循环会吞掉「列已存在」的错误。
- 文档与素材
- 十份文档管着这里的活:11,313 字符的 agent 说明,里面是不变量与坑;4,426 字节、以「操作员的战地桌」为名的设计系统,用 token 写成;4,372 字节、装着薄封装原则的产品文档;1,932 字节的审查指南;以及
docs/下四份文档,领头的使用参考有 66,562 字节。
取舍,以及它替代了什么
凡是会变的取值都在运行时问 CLI 要 替代 在二进制里维护一份模型与选项清单
那条加入「按会话选模型、思考强度与 profile」的 pull request 把理由写明了:别的工具里的模型选择器在一个新模型发布的当周就过期了,所以取值是运行时通过 CLI 提供给程序的接口读出来的,二进制里只留那个 CLI 的启动参数。选择存在会话上,于是重启、复活、面板里重新拉起和分叉都按它跑。
继续做各家 CLI 的薄封装 替代 在管理器里镜像每个工具的功能
产品边界是写下来的:保持它是受支持界面之上的薄封装,保留上游默认值与用户配置,避免写死的模型和版本相关的行为。支持一个 CLI 不等于复制它的功能集——而在没有稳定、有文档的接口可供发现的地方,那个功能就留在底层工具里。
会话跑在私有 tmux 服务器上,只走一条驱动 替代 借用用户自己那套 tmux 服务器
会话住在名为
agentmgr的服务器上,所以永远不会和用户自己跑的 tmux 混在一起。随之而来的一条不变量是:每次调用都要通过驱动点名自己的 socket,因为一条裸的 tmux 命令会顺着环境变量解析到开发者自己那台服务器,而在并不拥有的 socket 上执行全局选项或杀会话就是数据丢失。把「每个会话一份 worktree」做成可选属性 替代 把 worktree 当成工具围绕的中心单位
对 CCManager 来说 worktree 是工作单位、生命周期由程序负责,恢复会话就是重跑命令。这里单位是会话:worktree 开关放在新建会话表单、快捷提示和设置里,而复活恢复的是对话,不是重放一条启动命令。
批量动作先问一句,每个键留在自己的视图里 替代 让一个键在所有地方做同一件事
复活不止一个死会话时要先确认,理由写在那份 pull request 里:每个被复活的 agent 都是用户账号上正在跑的 CLI,所以批量复活应该和批量杀掉问同样的问题。随后恢复键变成档案键的镜像、只在已归档视图里生效:此前在活动视图里,它会在没有归档内容的行上打开卡片。
把 release note 当成喂给界面的结构化输入 替代 只写给网页看的变更日志
消息面板只读两个指定小节里的条目,别的一概不读,于是 release note 实际上是一份 schema:条目要短,超过 120 字符就会在面板里被从中间截断;致谢小节一行一个人;散文留在网页上。每条消息还要带上自己宣告的版本,因为不设上限的条目会永远显示给所有人。
依据AGENTS.md(11,313 字符)、DESIGN.md、PRODUCT.md、REVIEW.md、.github/CONTRIBUTING.md、README 全文、docs/usage.md(66,562 字节)与 docs/ 下另外三份文档、完整的 311 个文件树及其体积。
制作过程
6 个阶段- 01
状态是从面板上读出来的,不是 agent 报上来的
这里没有一处是让 agent 自报在干什么。管理器轮询每个会话的 tmux 面板、抓下文本、做一次归一化,再按每种 CLI 自己的一套规则,把结果归入界面要画的那几种状态——工作中、等待、已完成、空闲或出错。所以
internal/status是一个 36,736 字节的文件,旁边躺着 123,149 字节的测试;也所以这些规则住在配置里而不是代码的某个分支上:builtinTools是工具定义的唯一来源,而它每次启动都会加载,那里改过一个模式,这个 release 的所有安装就都会拿到。有两条不变量让这次读屏保持诚实。面板走引擎的纯文本路径,而不是 tmux 保留转义的抓取,因为正在跑的步骤标记会闪——它的暗帧是只有保留转义的抓取才照得出的带样式空白单元,规则匹配到它就会来回抖。抓取只归一化一次,状态、活跃度哈希、被引用的提示词与输入守卫读的是同一份结果,只有预览留着原始帧。剩下的活在逐个 CLI 上:OpenCode 的行要用渲染出来的输入框边框和按字素计算的宽度把侧边栏从抓取里切掉;Codex 需要一条跳过「一轮进行中按回车排队的消息」的规则,因为那一行原本被当作 agent 的最后一条回复;Gemini 需要同一套机制,外加一条可选规则,好让批准对话框引用它正在问的问题,而不是提示词的回显。 - 02
diff 审查就在管理器里做,评论还能走回去
ctrl+r把一个会话的改动以整文件、带语法高亮的 diff 打开,而不是一份补丁:改动行放在整个文件的上下文里看。用c在行上写评论,C就把这一轮作为一整条编号的审查提示送回 agent 的面板,而不是一串零散消息;已经送出去的评论会以「未处理/已处理」留在原地。同一套审查不必进界面也能用,命令行子命令接收仓库、基线 ref 与范围;一轮审查的状态存在管理器目录下的一个信箱文件里。审查跟着 agent 走而不是跟着启动目录走:这一段时间里的一处修复,让列表行、JSON 会话列表和审查都跟着 tmux 对「面板在哪」的记录,于是用过 CLI 自带切目录命令的会话,会在它现在所在的地方被审查。支撑这一切的验证规矩具体得少见:测试套件驱动一个真实的 tmux 服务器;指向开发者自己服务器的环境变量必须在跑之前清掉;socket 目录必须短到 tmux 不会悄悄退回默认 socket;而杀掉那个服务器是被禁止的。对界面、轮询器或某个工具状态规则的改动,绿灯的套件明确不算数:要在丢弃式的主目录下用自己的 socket 运行二进制,再从真正的 CLI 上把画面读回来。 - 03
单位是会话,worktree 只是会话的一个属性
这是本项目与本档案里另一个终端会话管理器之间最清楚的一条分界线。CCManager 是围绕 git worktree 建的:worktree 就是它列出、创建、合并与删除的单位,而它的恢复刻意只重跑启动命令,不把对话带回来。agent-manager 从 tmux 会话出发:每个 agent 跑在名为
agentmgr的私有 tmux 服务器上自己的常驻会话里,所以永远不会和用户自己那套 tmux 混在一起,退出管理器也不会结束它们。worktree 是一个会话可以拥有的属性:默认关闭,可在新建会话表单、快捷提示里打开,或在设置里设为默认,落点是<repo>-worktrees/<name>,分支名am/<name>。被恢复的是对话本身。v让死掉的会话带着自己的对话复活,R用同样的名字、分组、目录和工具在一个空上下文里重启它,f把对话续到一个单独命名的分叉里。每种 CLI 是「问它支持什么」,而不是假定它和别家一样:Hermes 与 Antigravity 没有分叉。worktree 相关的管道是实打实的,指令还记着它带来的那个坑:git stash跨 worktree 全仓库生效,同一个检出上开着几个会话时,一次 pop 会落到别人的活上,规矩是往分支上提交。 - 04
十一周、65 个 release,以及作者针对自己提交的重构
仓库建于 2026-07-15,当时是 1,545 行 Go、最大的文件 266 行、一个只有 20 个字段的状态模型。十一周后作者针对自己的代码开了一个 issue:65 个 release、429 个已合并的 pull request 之后,main 上是 43,491 行源码和 58,355 行测试,状态模型有 119 个字段和 560 个方法,装它的那个文件从 1,270 行长到 2,330 行。中途唯一一次重排有日期:2026-08-01 的一处改动把 1,866 行的快捷键文件拆成按功能分的文件,并把模型散落的字段收进子结构,降到 54 个——此后新功能继续落进同样的中心文件,而这正是那个 issue 真正在抱怨的事。它的标题要求按功能重构代码,作者自己在评论里写出了他心里的终局:一个约 25 个字段的模型,每个功能自己持有状态,另有一个 services 结构装配置、存储、tmux 驱动、钩子和 git。647 次提交按月是 277、216、154。第一条外部回复指向一个 fork 上的 pull request,重构就停在这里:写清了,没做。本档案里的 release 证据覆盖最近二十个,从八月的 v0.23.0 到 2026-09-26 的 v0.39.0。
- 05
release note 是一份带 schema 的数据源
推一个版本 tag 是唯一的发布方式:goreleaser 在 Actions 上跑、用两个 secret,并对校验和文件做构建溯源证明,而只有仓库的 admin 角色能推这个 tag。指令专门点出一种沉默的失败——没有 Arch 打包的 secret,Arch 那一步会跳过而不报错,于是整次运行仍然显示成功,而包已经过期——切版本的人因此被要求从日志里确认 release、Homebrew cask 推送和 Arch 推送三样都出现了。让这件事不寻常的地方在于:这份说明本身是程序读的。管理器的消息面板只读 Highlights 标题下的条目和 Thank you 标题下的条目;这两节里的散文留在网页上,只有条目会流动。这也是为什么文风要求在上面自己写一段摘要:生成出来的 pull request 清单说的是哪些落了地,而不是现在有什么不一样。Thank you 的条目一行一个人,指令还要求同时覆盖提 issue 的人,因为生成的变更日志只写作者,而一个修复之所以存在,是因为有人把 bug 写清楚了。这个格式有两条限制:超过 120 字符的条目会在面板里被从中间截断;消息文件里的每一条都要带上它宣告的版本,因为不设上限的条目会永远显示给所有人。
- 06
一轮测试覆盖、一个审查机器人,以及作者自己报的 bug
最忙的外部贡献者在九月底做的是提高测试覆盖。一个 issue 是工单,这段时间里有十一个 pull request 回应它,每一个都写明它抬的是哪个包、以及那个包原有的覆盖为什么不算数:只能靠端到端测试走到的代码,不会给它自己所在的包贡献行数。数字是公开变化的——一个终端转义包从 56% 到 75%,钩子与剪贴板两个包越过 80% 这条线,命令层从 73% 到约 84%。当贡献者问「这些 pull request 分配给我,是不是意味着我要做什么」,维护者的回答是:分配只是记账——这里每份 pull request 都分配给它的作者,他自己的也一样——真正表示轮到谁的是一个标签,而不是被指派人。审查有一部分交给了一个代码审查机器人,它的配置有 12,142 字节。他也自己开 issue:这段时间里开着的 issue 有四个是他的,其中两个写成带复现的 bug——一个是在 shell 为 dash 的 Linux 上,被 Ctrl-C 杀掉的 agent 会把启动脚本一起带走、面板关掉,既没有重启提示也没有留下 shell;另一个是某个 CLI 自带的切目录命令在它自己的界面里换了目录,而进程仍然守在原工作目录。外面的活也确实落地:更新路径学会了识别躺在只读软件仓库里的二进制,于是打印建议而不是往旁边写——这个修复来自把本程序打包进一个 Nix 仓库。
相关档案
全部档案 →第 108 号
agy-staff
它把 Google 的 Antigravity CLI 雇成一名员工。宿主 agent——Claude Code、Codex 或 Pi——留住决策权,把调研、评审和范围明确的改动交给一个跑在后台的 Gemini 3.8 Flash 员工,用一个 job id 来回传话。
第 070 号
delegate-skills
一个技能包,给每一种编码 agent CLI 各配一份委派技能:编排方写好自足的任务书,另一条 CLI 去改真实工作树,而审查与提交留给人。
第 052 号
OpenWolf
一个放在项目里的 .wolf 文件夹,为正在跑的那个编码 agent 留住任务状态、文件索引、项目笔记与已知修复;它只在会话开始、提交提示词与压缩前后注入其中变化过的部分,并把从 Claude 会话记录、Codex rollout 与 OpenCode 插件记录里读到的 provider token 计数按 agent 与模型汇总出来。