conmux

Conmux 手册

Conmux 是一个为 Windows 而生的类 tmux 中间件:把任意 CLI —— 原生程序、PowerShell、乃至 WSL 里的工具 —— 接成同一套受监管的会话。它的目标很朴素:让 agent CLI 在 Windows 上跑得起来、看得住,又不用背上一整个 IDE。

这份手册按「你是谁」分成几条路径,挑最贴近你的那条读就行:

你是…从这里开始
第一次用终端复用器(没听过 tmux 也没关系)这是什么、为什么值得用
tmux 老手,只想知道 Windows 上哪里不一样差异速查表
从代码驱动它 / 做自动化进阶 · 控制面
在做自己的 agent 框架,想接进来给 agent 框架作者

老实话(诚实边界):Conmux 还年轻(v0.1.x),只支持 Windows。内核——会话、分屏、进程整树监管、detach/attach 断线重连——已经在真实使用里跑;跨守护进程重启的持久化、远程 attach 这些还在路上。手册里会一处处如实标出「已经能用」和「还没做」,绝不把路线图当现状讲。

如果哪里读着别扭、装不上、或者你有更好的点子,开个 issue 或 discussion 告诉我。这是一个华南师范大学学生的开源项目,很想和你一起,把 Windows 上的 agent 生态做得更好。

这是什么、为什么值得用

(这一页假设你没用过 tmux 或任何"终端复用器"。如果你用过,直接跳到 从 tmux 过来。)

先说问题

你在命令行里跑东西——比如一个 AI 编程 agent(Claude Code、Codex…),或者一次编译、一个本地服务器。一个终端窗口跑一个。想同时跑三四个?那就得开三四个窗口,然后在它们之间来回点:一个个看谁卡住了、谁在等你输入、谁跑完了。

窗口一多,你就成了那个"人肉调度器"——不停切来切去,还生怕漏掉哪个正在等你。

Conmux 做什么

Conmux 把多个命令行会话装进一个窗口,各占一块(叫「pane / 面板」)。在这个窗口里你可以:

  • 分屏——一个窗口切成几格,几个会话并排看。
  • 切换——一排小圆点代表每个会话,点一下就跳过去。
  • 离开再回来——关掉 Conmux 窗口,里面的会话不会死:它们在后台继续跑;下次打开,画面原样接回来(这叫 detach / attach)。
  • 看得住——哪个会话需要你(比如某个 agent 弹了权限请求),Conmux 会把它标出来,而不是让你自己一个个翻。

一句话:它替你盯着那些命令行,你只在真正需要你的时候出手。

为什么是 Conmux,而不是 tmux

如果你搜过"终端复用器",多半会看到 tmux——Linux / macOS 上的经典工具。问题是:tmux 不为 Windows 而生。在 Windows 上用它,你得先装一层 WSL(一个跑在 Windows 里的 Linux 子系统)。

Conmux 直接跑在 Windows 自己的终端底层(叫 ConPTY)上,不需要 WSL。而如果你把 WSL 里的工具接进来,它也能——单独一节 讲这个。

它也比"把 agent 塞进一个完整 IDE(比如 VS Code 加一堆插件)"要轻:Conmux 围着命令行本身转,没有编辑器那一大坨。

诚实提醒:带图形界面的 Conmux 用了系统自带的 WebView2,不是最极致的轻量方案(内存地板大约几百 MB)。它比一整套 IDE 省得多,但如果你追求"极致省内存",纯命令行的 conmux 工具(不带图形界面)才是最轻的那档。

接下来

装上它

这一页帮你把 Conmux 装到自己的 Windows 上。先说清楚:现在还没有现成的安装包可下,所以要么用 cargo 装命令行版,要么从源码自己构建图形界面版。听起来有点手动,但每一步都写全了,跟着走就行。

先看看你的机器够不够

  • 系统:Windows 10(1809 及以上版本)或 Windows 11。Conmux 只跑 Windows——它整个存在的理由就是走 Windows 原生这条路,所以没有 macOS / Linux 版。
  • 想装命令行版(crate):需要 Rust 工具链,且带 MSVC 组件(装 Rust 时选默认的 x86_64-pc-windows-msvc 就对了)。
  • 想从源码构建图形界面版:在上面 Rust 的基础上,再加 Node.js 18 或更高(自带 npm),以及 Git

小提示:如果你只想先尝个鲜、看看 Conmux 能干嘛,先装下面第 ① 条的命令行版最省事——不用装 Node,几分钟搞定。图形界面留到你确定要用再构建。

三条路,挑一条走

① 命令行版(conmux crate)——最轻、最快

一条命令的事:

cargo install --locked conmux

装完你就有了 conmux 命令,可以直接开会话、分屏、detach / attach(怎么用见 你的第一个会话与分屏)。这一档不带图形界面,是内存占用最小的那档,也是 「这是什么」 里说的「追求极致省内存就选它」的那个。

如果你是想在自己的 Rust 项目里把 Conmux 当库用(而不是当命令行工具跑),那就不用 cargo install,改在你项目的 Cargo.toml 里加一行:

[dependencies]
conmux = "0.1"

② 从源码构建图形界面版(conmux-app)

图形界面版目前只能自己构建。四步:

git clone https://github.com/Verson1daddy/Conmux.git
cd Conmux\conmux-app
npm install
npm run tauri:build

构建成功后,**Windows 安装包(NSIS 格式)**在这个目录下:

conmux-app\src-tauri\target\release\bundle\nsis\

里面会有一个 .exe 安装程序,双击它就能把 Conmux 装进系统。

可能踩的坑

  • npm run tauri:build 第一次跑会比较久——它要编译整个 Rust 后端,头一回几分钟到十几分钟都正常,别以为卡死了。
  • 如果 Rust 或 Node 没装好,这一步会在中途报错;回到上面「先看看你的机器够不够」补齐再重来。
  • 安装包是未签名的(原因见下),双击安装时 Windows 会弹 SmartScreen 提示——处理办法同样见下。

③ 以后从 Releases 直接下安装包 · 🚧 路线图(还没做)

将来会在 GitHub Releases 页放预编译好的安装包,到时候就不用自己构建了。现在还没有,所以图形界面版目前只能走上面第 ② 条从源码构建。这条路等安装包发布后再回来看。

关于「未签名」和那个吓人的蓝框

不管是自己构建的安装包,还是以后 Releases 上的安装包,都会是未签名的——因为这是一个学生的开源项目,没有预算买代码签名证书。

后果是:双击安装时,Windows SmartScreen 会弹一个蓝色框,说「Windows 已保护你的电脑」「发布者:未知」。这不代表软件有问题,只是 Windows 对没花钱买证书的程序一律这么提示。想继续装,点:

更多信息 → 仍要运行

框就过去了,正常安装。如果你实在不放心这个提示,那就走第 ① 条命令行版——cargo install 走的是 Rust 官方的包分发,完全不涉及 SmartScreen 这一套。

装好了,然后呢?

你的第一个会话与分屏

装好了?好,我们把它跑起来,五分钟摸清最常用的几下操作。

(这一页讲带图形界面的 Conmux。纯命令行的 conmux 工具走另一条路,见 进阶 · 控制面。)

起来长什么样

打开 Conmux 图形界面,你会看到一个窗口,里面跑着一个命令行会话——就跟平时开一个终端一样,先在里面敲你想跑的东西(一个 agent CLI、一次编译、一个本地服务器,随你)。

窗口底部有一条缩点条:每开一个会话,就多一个小圆点。点一下某个圆点,就跳到那个会话。 会话一多,这排点就是你的「我现在在哪、还有哪些在跑」的一览。

这排小圆点就是前一页说的「一排小圆点,点一下跳过去」。会话之间互不打扰,各跑各的。

Leader 前缀:Conmux 的「暗号键」

分屏、切焦点这些动作,Conmux 用一个 leader 前缀键来触发,默认是 Ctrl+B(tmux 老手会觉得眼熟)。

它是两步手势,不是一次按下四个键:

  1. 先按一下 Ctrl+B,松开——这一下不会打到你的程序里,Conmux 进入「待命」状态(界面上会有个小提示告诉你它在等下一个键)。
  2. 再单独按一个命令键——比如 \-z——Conmux 就执行对应动作。

为什么要两步? 这样 Conmux 平时完全不碰你的键盘:除了 Ctrl+B 这一个键,其它所有按键都原样透传给你的程序。只有你按了 Ctrl+B 之后的那一下,才被当成 Conmux 命令。这是内核给的硬保证——永远不会把你的 CLI 弄坏

顺带两个贴心细节:按下 Ctrl+B1.5 秒内不按命令键,它会自动退回透传(你没按错,就当无事发生);如果你其实是想把 Ctrl+B 这个键本身发给程序,那就 Ctrl+BCtrl+B,字面前缀会送进当前会话。

分屏:一个窗口切几格

先按 Ctrl+B 松开,再按:

再按这个键效果
\(反斜杠)竖切——当前面板左右一分为二,右边开一个新会话
-(减号)横切——当前面板上下一分为二,下面开一个新会话

想切成田字格?多切几次就行——竖切一次、再横切一次,随你怎么排。每一格叫一个「pane / 面板」,各跑各的会话。

在几格之间切焦点

分了屏,键盘输入总得有个「当前在哪一格」。先按 Ctrl+B 松开,再按方向键,焦点就往那个方向的面板跳:

  • Ctrl+B 然后 / / / —— 把焦点移到左 / 右 / 上 / 下那一格。

焦点在哪一格,你敲的字就进哪一格。

看一格看仔细:缩放

分屏之后每格都变小了,想临时把某一格铺满整个窗口看清楚?

  • Ctrl+B 然后 z —— 当前焦点这一格全屏放大;再来一次 Ctrl+B z还原回原来的格子布局。

z = zoom,放大。别的格子没关掉,只是暂时藏起来了,还原就回来。)

离开再回来:detach / attach

这是 Conmux 最想让你安心的一点:

你直接关掉 Conmux 窗口,里面的会话不会死。 它们在后台继续跑着。下次再打开 Conmux,画面原样接回来——之前跑到哪、屏幕上是什么样,都在(滚动历史、光标、乃至全屏程序的界面状态都会照原样重放)。

这叫 detach(离开)/ attach(接回来)。跑长任务的时候特别有用:关掉窗口去干别的,回来接着看,什么都没丢。

一句诚实话:「关掉窗口会话不死」指的是关掉客户端窗口。会话真正的宿主是后台的守护进程(daemon);如果你显式停掉守护进程(conmux kill-server)或者它自己崩了,那所有会话才会一起结束——这是「绝不留孤儿进程」的另一面,是故意这么设计的,不是 bug。

还没做到的(roadmap)

诚实标一下边界,免得你按了没反应还以为坏了:

  • 图形界面本身还在建设中——上面这些分屏、切焦点、缩放的键位,规格和交互已经冻结、代码已落地,但整套原生 GUI 外壳仍是当前主线开发项,细节可能还在打磨。
  • 跨守护进程重启的持久化——目前 detach/attach 是在守护进程活着的前提下断线重连;「关机重启后会话还在」这种跨进程持久化不在当前范围(属于设计上明确的取舍)。
  • 远程 attach / 跨 WSL 统一——都还在路线图上,见 从 tmux 过来 与 conmux 仓库的 Roadmap。

接下来

差异速查表

(这一页写给已经用惯 tmux 的人。假设你知道什么是 prefix、pane、split,只想知道在 Windows 上、在 Conmux 里,哪里一样、哪里不一样。)

一句话先讲清楚

Conmux 借了 tmux 的手感——一个前缀键(leader),然后按一下命令键去分屏、切焦点、缩放——但它不是 tmux 的替代品(drop-in replacement)。它不读你的 .tmux.conf,命令集是 tmux 的一个小子集,跑在 Windows 原生底层上。所以:肌肉记忆大体能用,但别指望把 tmux 配置搬过来就直接跑。

Windows 原生,不用 WSL

这是 Conmux 存在的头号理由。tmux 从设计上就不为 Windows 而生——在 Windows 上用它,你得先装一层 WSL(Linux 子系统),然后你的 tmux 其实活在那个 Linux 里,管不了 Windows 这边的进程。

Conmux 直接跑在 Windows 自己的伪终端(ConPTY)上,不需要 WSL。PowerShell、原生程序、AI agent CLI,都是被同一套整树监管的 pane。(想把 WSL 里的工具也接进来?可以——但那属于路线图上的「跨 WSL 统一」(M4),目前还没做,另有单独一节讲当前能做到哪一步。)

前缀键:默认 Ctrl+B,为什么不是 Ctrl+Space

好消息:Conmux 的默认前缀就是 Ctrl+B,和 tmux 自身默认一模一样,老手上手零成本。

我们其实先试过 Ctrl+Space(不少人爱用的 tmux 自定义前缀)。踩了个坑:中文 Windows 上,Ctrl+Space 是输入法「中/英切换」的全局热键,会被输入法先吞掉,Conmux 根本收不到——leader 在中文环境里等于失效。所以默认改回 Ctrl+B 避开它。

前缀键可以改(在应用里配置,本地持久保存)。唯一的硬约束:前缀必须带 CtrlAlt——裸键当前缀会把每一次普通按键都吞成命令、直接弄坏你的 CLI,这条不允许。

和 tmux 一样的小细节:连按两次前缀(Ctrl+BCtrl+B)会把一个字面前缀字符送进当前终端——对应 tmux 的 send-prefix

键位对照表

按下前缀(默认 Ctrl+B)后,再按命令键。下面是 tmux 与 Conmux 的对照:

想干什么tmux(默认)Conmux说明
竖切(左右并排)prefix %prefix \Conmux 用 \,不是 %
横切(上下堆叠)prefix "prefix -Conmux 用 -,不是 "
在 pane 间移动焦点prefix ←↑↓→prefix ←↑↓→一致
调 pane 大小prefix Ctrl+←↑↓→prefix Shift+←↑↓→Conmux 用 Shift+方向
缩放 pane 到全屏(切换)prefix zprefix z一致
跳到第 N 个会话prefix 0..9(切窗口)prefix 1..9Conmux 跳的是「会话」,从 1 起
下一个 / 上一个会话prefix n / prefix pprefix n / prefix p一致
打开命令面板prefix :(命令行)prefix :Conmux 开的是应用内命令面板

诚实提醒:这张表覆盖的就是 Conmux 目前实现的 leader 命令,没有更多了。tmux 里的会话/窗口/pane 三级模型、复制模式(copy-mode)、prefix [ 翻页选择、prefix d 快捷键 detach(Conmux 的 detach 走关闭客户端窗口,不是快捷键)、命令别名、bind-key 自定义绑定——这些都还没有。别照着 tmux cheatsheet 逐条试。

免前缀直接快捷键(opt-in,默认关)

嫌两步前缀麻烦?Conmux 有一档免前缀的直接快捷键,一步到位:

  • Ctrl+Alt+H/J/K/L → 按 vim 方向跳焦点 pane
  • Ctrl+Alt+\ → 竖切 · Ctrl+Alt+- → 横切 · Ctrl+Alt+Z → 缩放

但它默认是关的,得你在设置里显式打开。为什么默认关:Conmux 的核心承诺是「永不弄坏你的 CLI」——默认状态下它只截走前缀键那一个键,其余全部原样透传给终端。开了直接快捷键,就等于多让它截走一小撮 Ctrl+Alt 组合;这是拿一点便利换透传的纯粹性,所以交给你自己决定要不要开。(另:真按 AltGr 组字符时不拦,非 US 键盘布局也不会被弄坏输入。)

明确说清楚的差异(别踩坑)

  • 不读 .tmux.conf Conmux 没有任何 tmux 配置文件解析。前缀键和直接快捷键在应用内配置、存本地,和 tmux 的配置文件互不相通。
  • 命令集是子集,不是全集。 上面对照表之外的 tmux 功能大多没有;把它当「一个借了 tmux 手感的 Windows 原生复用器」,而不是「Windows 版 tmux」。
  • detach / attach 语义不同。 tmux 的会话活在服务端、prefix d 解绑;Conmux 是关掉客户端窗口,pane 在后台继续跑,下次 attach 画面原样重放(滚动历史 + 终端模式状态都在)。但注意:pane 活过的是客户端,不活过守护进程——conmux kill-server 或守护进程崩溃会连根带走所有 pane(这是「零孤儿进程」保证的另一面,控制面一章有细说)。
  • 还年轻。 Conmux 是 v0.1.x。上面标了「一致」的键位是当前实现里可用的;标了「还没有」的就是真没有。手册会一处处如实标出,不把路线图当现状讲。

接下来

接 WSL 里的工具

你已经知道 Conmux 直接跑在 Windows 自己的终端底层(ConPTY)上,不需要 WSL。但很多人手边真有 WSL——里面装着 Linux 那套工具链,某个 CLI 只在 Ubuntu 里跑得顺。这一节讲:怎么把 WSL 里的一个命令,当成 Conmux 的一块受监管 pane 起起来。

已经能用:一键起一个 WSL pane

在 GUI 的 Home 加项 里,有一个 WSL picker。打开它,Conmux 会去问一句你系统里装了哪些发行版,把它们列成一排给你选:

  • 后台跑的是 wsl.exe --list --quiet,只是探一下装了什么,不会启动任何东西。
  • 装了 UbuntuDebianUbuntu-22.04… 就都列出来;
  • 没装 WSL、或者一个发行版都没有?picker 就空着,你直接退回普通的「纯文本加项」手动敲命令即可——不会报错、不会卡住。

选好发行版、填上要跑的 CLI(比如 bashhtop、或某个 Linux 侧的 agent 命令),Conmux 就替你拼出这么一条命令,把它当一块新 pane 起起来:

wsl -d <发行版> -- <你的 CLI>

比如选了 Ubuntu、CLI 填 htop,起的就是 wsl -d Ubuntu -- htop。这块 pane 和一块 PowerShell pane 一视同仁:同样能分屏、切换、detach 关掉客户端后在后台继续跑、下次 attach 画面原样接回来。整树监管(Job Object)也照管——关掉这块 pane,它在外层拉起的进程一起收走。

小提示:picker 只是帮你把 wsl -d ... -- ... 这行命令填好。你要是喜欢自己敲,在普通加项里手写这一整行、当成一条带参数的命令起,效果完全一样。picker 是省事,不是唯一入口。

还没做(M4 路线图):跨 Win/WSL 的「统一会话」

这里要把话说清楚,别让你误会 Conmux 现在能做到多少。

现在能做的:把 WSL 里的一个 CLI,当一块独立的受监管 pane 起起来、看得住、能断线重连。就这么多——它本质上是「Conmux 起了 wsl.exewsl.exe 自己再进 WSL 跑你的命令」。

现在还做不到(都在 M4 路线图上,未实现)

  • 跨边界的优雅终止(信号代理)——让 Windows 侧的守护进程能干净地给 WSL 里的进程发信号收尾,而不只是硬拔掉外层的 wsl.exe
  • 路径转换——Windows 路径(D:\foo)和 WSL 路径(/mnt/d/foo)之间自动翻译;
  • 一棵统一的 Win/WSL 会话树——让一块 PowerShell pane 和一块 WSL pane 在同一套监管语义下被同等地寻址、终止、管理。

这三件事合起来,才是 Conmux 真正想做的那件「拥有 Win 与 WSL 的边界」的事。今天它还没实现,是 M4 的目标。所以现阶段请这样用它:把 WSL CLI 当一块方便起起来的 pane,而不是指望它做深度的跨边界统一——那还在路上。

接下来

进阶 · 控制面:从代码驱动 Conmux

(这一页面向想做自动化、写工具、或把 Conmux 接进自己脚本/程序的人。假设你会一点 Rust;不写代码只想按键操作的话,这页可以跳过。)

一句话先说清楚

大多数复用器你只能用键盘去戳它。Conmux 不一样:它背后有一个你可以从代码里直接驱动的控制面——开会话、发按键、抓屏幕、跟着某个 pane 的输出一路看下去,全都能程序化地做,而不只是靠快捷键。

骨架长这样:一个 daemon + 一堆瘦客户端

Conmux 是 tmux 那种「服务端」模型:

  • daemon(守护进程)持有所有真实的 ConPTY。你开的每一个 pane,进程、它的整棵子进程树、滚屏历史、终端模式状态,都活在这一个 daemon 里。
  • 客户端是「瘦」的——不管是命令行 conmux、图形壳,还是你自己写的程序,都只是通过一条**命名管道(named pipe)**连上 daemon 跟它说话。客户端本身不持有 pane。

这就是为什么「关掉客户端窗口,里面的东西不会死」:死的只是那根连线,pane 还在 daemon 里跑。(反过来:daemon 自己没了——kill-server 或崩溃——所有 pane 才会一起走,这是「零孤儿进程」保证的另一面,见 机制 vs 语义:边界在哪。)

信任边界:这根管道只对当前用户开放(DACL 只授权你自己的 SID,并拒绝远程客户端)。控制面是本地的——没有账号、没有遥测,不出这台机器。

控制面给你三样东西

回给源码核实过,conmux crate 对外暴露的这三件是控制面的核心:

  1. 请求 / 应答——一问一答的成帧协议。你发一个操作(MuxOp),daemon 回一个应答(MuxReply::Ok { payload }Err),带 correlation_id 对得上号。
  2. 每 pane 的事件流——订阅某个 pane,就能收到它的 PaneOutput(带逐 pane 严格单调、从 1 起seq 序号)和 PaneExited(带确切退出码)。
  3. 稳定的 pane id——每个 pane 有一个稳定的 PaneId,你用它来寻址:发送、抓取、resize、attach 都认这个 id。

有了这三样,你就能从代码驱动它,而不是只靠按键

从 Rust 里驱动它

客户端入口在 conmux::client::Client(下面的方法名都回 client.rs 核实过):

#![allow(unused)]
fn main() {
use conmux::client::Client;
use conmux::protocol::MuxOp;

// 连上当前用户的 daemon;没有 daemon 就自动帮你拉起一个(tmux 那种心智)。
let mut client = Client::connect_or_spawn()?;

// 一问一答:列出当前所有 pane。
let panes = client.request(MuxOp::ListPanes)?;

// 往某个 pane 注入按键(原始字节,走 daemon 内唯一那条受审计的写链)。
client.request(MuxOp::Send { pane_id: id.clone(), data: b"ls\r".to_vec() })?;

// 抓一个 pane 当前的屏幕(可选带/不带 ANSI)。
let snap = client.request(MuxOp::Capture(cap_req))?;
}

跟着一个 pane 一路看下去(而不只是抓一张快照),用 attach——它先给你一份原子快照(终端模式前导 + 滚屏历史 + 序号高水位),再转成一个流式会话,之后循环收 live 输出、也能回注 stdin:

#![allow(unused)]
fn main() {
let attached = client.attach(&id)?;
// 先按 mode_preamble → history → buffered 的顺序喂给渲染器重建画面
let mut session = attached.session;
while let Some(ev) = session.recv_output() {
    // AttachEvent::Output { seq, data } / Exited { exit_code }
}
}

其它常用操作(都是 MuxOp 的变体,回 protocol.rs 核实):Spawn / Respawn(原子同 id 重起)/ Resize / KillTree / Subscribe · Unsubscribe / ListThemes · SetTheme / KillServer。完整清单和每个 payload 的形状见 下一页 · 协议

诚实边界:哪些稳、哪些会变

Conmux 的稳定性承诺分成两档,这点很重要——回 crate::lib 的文档核实:

  • ✅ 协议层是冻结契约(committed)。wire 协议类型(MuxRequest / MuxOp / MuxPayload / MuxReply / MuxNotify,及其携带类型闭包,如 PaneId / PaneSize / PaneState)、PaneHost 门面、事件面、注入扩展点、主题面——这些在 0.x 期间变更必须走 minor 版本号 + CHANGELOG,patch 版本不会破坏你。序号语义(PaneOutput.seq 逐 pane 从 1 起严格单调)也随附冻结。按协议层建自动化是可以放心的。
  • ⚠️ 协议层以外的 API 是 unstable,1.0 前可能不打招呼就变。具体说:daemonclientpipewire 这几个模块在源码里都明确标了「Stability: unstable — may change without notice」。也就是说,上面那段 Client::connect_or_spawn() / attach()具体 Rust 方法形态属于会变的一档——能用、Conflux 自己也在真实用它,但别把它当冻结契约。想要长期稳的锚,锚在协议本身(wire 类型),而不是某个客户端方法签名。

一句话拿捏:协议是合同,客户端 API 是当前实现。做自动化时,越贴近 wire 协议,越不容易被未来版本掀翻。

接下来

Wire 协议参考

(这一页是给要从代码接进 daemon、或者想搞明白管道上到底跑什么字节的人看的。只用图形界面或 CLI,可以跳过。)

Conmux 的 daemon 和它的客户端(CLI、GUI、你自己写的程序)之间,在一条命名管道上收发一串 JSON 帧。这一页把这串帧的形状写清楚——每一条都对应 crates/conmux/src/protocol.rs#[derive(Serialize, Deserialize)] 的真实类型,不是另起炉灶的文档。

稳定性提醒:协议层是 conmux 唯一承诺稳定的公开面(serde 形状即契约,破坏性改动走 minor + CHANGELOG)。协议层之外的 API 都还不稳定。下面每个类型名都能在 protocol.rs / event.rs 里一一对上。

一条连接上跑什么:帧信封 WireFrame

所有帧都裹在一个信封枚举 WireFrame 里,externally tagged(JSON 里是 {"变体名": {...}})。一共五种:

方向载荷
Hello客户端 → daemonprotocol_version: u32client_kind: String
HelloAckdaemon → 客户端protocol_version: u32daemon_version: String
Request客户端 → daemon一个 MuxRequest
Replydaemon → 客户端一个 MuxReply
Notifydaemon → 客户端一个 MuxNotify(异步事件,无相关号)

方向是有约束的,不是随便发:daemon 只接受 Hello(且仅在握手期)和 Request;客户端只接受 HelloAck / Reply / Notify。发反了 = 协议错误,直接断连。

握手先行:客户端连上后首帧必须是 Helloclient_kind 只是个自由标签,进审计日志,不参与任何授权判定——别指望靠它提权。

版本必须严格相等

PROTOCOL_VERSION 当前是 1,它独立于 crate 版本。握手时 daemon 拿自己的版本和 Hello 里的 protocol_version严格相等校验——不是"大于等于",是"必须一样"。对不上就不给你 HelloAck。任何 wire 形状的破坏性变更都要 bump 这个常量。

拒收未知字段(deny_unknown_fields

WireFrame 信封、Hello / HelloAck、以及 MuxOp 都开了 deny_unknown_fields:报文里多一个没定义的键,反序列化直接失败,而不是静默忽略。这条最要紧的用途是把注入源挡在门外——见下面 Send

请求:MuxRequest 与操作枚举 MuxOp

一个请求 = 相关号 + 一个操作:

MuxRequest { correlation_id: u64, op: MuxOp }

correlation_id 用来把应答和请求配对(应答会带回同一个号)。MuxOp 是全部能下达的操作,逐一列在下面。右列是这个操作成功时对应的 MuxReply::Ok 载荷(MuxPayload 的变体)。

MuxOp 变体字段成功载荷(MuxPayload备注
SpawnSpawnRequestSpawned(PaneId)起一个新 pane
RespawnSpawnRequestSpawned(PaneId)原子同 ID 重起(消掉 KillTree+Spawn 之间的 ID 复用窗口)
Sendpane_iddata(见下)Sent向 pane 注入输入
CaptureCaptureRequestCaptured(CaptureResult)抓 scrollback
Resizepane_idsize: PaneSizeResized改 pane 行列
KillTreepane_idKilled杀掉整棵进程树
ListPanesPanes(Vec<PaneState>)列出所有 pane
Subscribepane_idSubscribed订阅该 pane 的事件流
Unsubscribepane_idUnsubscribed取消订阅
Attachpane_idAttachSnapshot { ... }原子「订阅 + 快照」
ListThemesThemes(Vec<TerminalTheme>)列出主题预置
SetThemeid: StringThemeSet热切换主题;另广播 ThemeChanged
KillServerServerKillScheduled终结 daemon 及全部会话
PinExecutablepath: StringPinnedpin 可执行文件到信任库
UnpinExecutablepath: StringUnpinned移除 pin

这就是全部 15 个变体,一个不多一个不少。MuxOp 故意不加 #[non_exhaustive]:新增变体是一次显式的 minor 决策,daemon 的 dispatcher 对它穷尽 match,未来加变体会在 daemon 侧编译报错,逼你显式处理,而不是静默漏掉。

关于 Send:注入源不过 wire

Senddata原始字节,上 wire 时经 base64 编码(方向键、Alt 组合、二进制粘贴这些非 UTF-8 内容用普通字符串无法无损携带)。

注意 Send 上没有 source 字段,这是故意的:注入来源由接收端边界按信道身份赋值,不允许客户端在 wire 上自报。配合 deny_unknown_fields,一条带 source 键的 Send 报文会反序列化即失败——拒收,而不是接受后再丢弃。

关于订阅类操作

Subscribe / Unsubscribe 维护「这条连接关心哪些 pane」的集合,daemon 的 fan-out(FanoutSink)据此把 PaneOutput / PaneExited 事件只投给订阅了的连接Attach 是一步到位的「订阅 + 原子快照」:先注册订阅、再取当前 scrollback 快照,之后按 seq > last_seq 连续喂 live 流——中间不丢帧不重帧(限速 + per-pane 并发=1 防快照放大)。这套「fan-out 分发 + 无缝快照拼接」在当前 daemon 里已经实现并有测试(M2 里程碑已落地,见 daemon.rs::attach_with_limits / FanoutSink)。(SetTheme 明确不持久化主题偏好——持久化归上层消费者 / GUI 壳,不归 daemon。)

应答:MuxReplyMuxPayload

应答两条路,都带 correlation_id 供配对:

MuxReply::Ok  { correlation_id: u64, payload: MuxPayload }
MuxReply::Err { correlation_id: u64, error:   ConmuxError }

出错走 Err,携带的是 ConmuxError机制层错误,比如 PaneNotFound)——conmux 只报它机制层的错,不认 conflux 的语义错误。

成功载荷 MuxPayload 的变体已在上表右列逐一对应。多数是无字段的确认(Sent / Resized / Killed / Subscribed / …)。两个带真数据的值得单独看:

  • AttachSnapshot——Attach 的原子快照,字段:mode_preamble_b64(终端模式前导,如 alt-screen)、history_b64(scrollback 历史)、last_seq(末序号)、pane_state。客户端重建 = 喂 preamble → 喂 history → 之后按 seq > last_seq 连续喂 live 流,不丢不重。
  • Captured(CaptureResult)——Capture 的结果,含 base64 数据、首/末绝对行号、是否截断、是否"实际已满"。

MuxPayload 加了 #[non_exhaustive](与 MuxOp 相反),消费者 match 时要留 _ => 分支。

异步事件:MuxNotify

daemon 主动往客户端推的事件,裹在 WireFrame::Notify 里,没有相关号(它不回应任何请求)。定义在 event.rs,三种:

MuxNotify 变体字段含义
PaneOutputpane_idseq: u64data(base64)pane 的原始输出;seq 是 per-pane 单调序号,供重放对账
PaneExitedpane_idexit_code: Option<i32>进程退出;拿不到退出码时是 None不伪装成 0
ThemeChangedid: String主题被 SetTheme 切换后广播,供实时换肤

SendPaneOutput.data 上 wire 也走 base64(原始字节可能含不可打印 / 非 UTF-8 内容)。in-proc 直连的消费者(sink 实现)仍收原始 Vec<u8>,base64 只在管道边界生效。

seq 的单调性有个硬纪律:如果消费方或 conmux 对 PaneOutput 做合帧,只能拼接、不得丢字节、seq 必须连续——丢帧会让消费方据残缺输出做错误决策。

诚实边界MuxNotify 只发 conmux 机制层确知的事件——字节、退出、换肤。它不发"这个 agent 在思考 / 在等权限"这类语义状态:那是上层(比如 Conflux)对 PTY 内容的解读,不属于协议层。别指望在 wire 上直接读到 agent 状态。

一眼核对

想快速验证上面这些,protocol.rs 底部的 #[cfg(test)] 模块就是活文档:all_ops_round_trip 列全了 15 个 op,wire_frame_all_directions_round_trip 跑五种信封帧,send_with_source_field_is_rejected_on_wirehello_rejects_unknown_fields 演示了拒收未知字段的两处。改协议前,先让这些测试红,是最稳的对账方式。

把你的 CLI 接上 conmux

(这一页写给做 agent 框架 / CLI 工具的人。你有一个自己的命令行程序,想让它在 conmux 里跑起来、被看住。)

先说一句实在话

「一键接入任意框架、自动识别、自动适配」这套脚手架,conmux 现在还没有。 别被别的话术带偏——今天 conmux 能给你的是两档,界限很清楚:

  1. 任何 CLI 都能起成一个受监管的 pane —— 这是已经能用的地基。你的程序、别人的程序、PowerShell、WSL 里的工具,都一样。
  2. 对 Claude Code 会话有一层深观测 —— 这是目前唯一做了专属适配的框架。

至于「你随便丢个 agent 框架进来,conmux 就自动认出它是什么、自动把它的模型 / token / 子任务解析出来」——那是方向,不是现状。想要那一层,得像 Claude 那样为每个框架单独写适配。所以下面分两段讲:先讲对谁都成立的那档,再讲 Claude 专属那档。

第一档:把任意 CLI 起成受监管 pane(人人可用)

conmux 不在乎你的程序「是不是 agent」——它只认进程、字节、pane。要把你的 CLI 接进来,你需要的信息就一条命令这么多:

  • program —— 要启动的可执行文件(比如 my-agent.exenodepython)。
  • args —— 启动参数(比如 --serverun main.py)。
  • cwd —— 工作目录(可选;不给就继承当前目录)。

在 GUI 里,这三样合起来就是一条启动项(launch entry):一个显示名 + 一行命令原文(含参数)+ 可选工作目录。内置就种了两条能直接跑的——Shellpowershell.exe)和 WSLwsl),你可以照着加自己的、改它、删它。命令原文按「空格分词 + 双引号包含空格段」拆成 program 和 args(不支持管道、重定向、变量展开——那些超出启动项的范围)。

按下去之后发生的事:conmux 把你这条命令当成一个真的 ConPTY 进程直接启动(不是往某个 shell 的 stdin 里灌字符),于是你的 CLI 就成了一块受监管的 pane。从这一刻起,前面几章讲的能力对它全都成立

  • 和别的 pane 分屏并排、一排小圆点之间切换
  • 关掉客户端窗口,你的进程不会死,重新 attach 画面原样接回(detach / attach);
  • 它连同它 fork 出去的子孙进程一起被 Job Object 兜住——要杀就整树杀干净,不留孤儿
  • 所有往它写的输入都过同一条受审计的通道。

这一档不挑框架。 你的 agent CLI 只要能在 Windows 命令行里跑起来,它就能变成 conmux 的一块 pane,享受上面这一整套。这是 conmux 今天能给「任意框架」的、最扎实的那部分。

要更精确的接入契约? program / args / cwd 怎么进到 spawn、启动项怎么持久化、命令怎么解析——GUI 侧真源在 conmux-app/src/lib/launch-registry.tssessions.ts。从代码驱动内核(framed 协议、稳定 pane id、事件流)见 从代码驱动 conmux

第二档:Claude Code 的深观测(目前唯一的专属适配)

除了「起成 pane」,conmux 对 Claude Code 会话还多做了一层——能看出这个会话在跑什么模型、上下文用了多少、派了哪些子任务、什么时候在等你。这一层是给 Claude 专门写的,不是通用能力。

当你用一条claude 命令(就是 claude,你自己没带任何参数)通过 conmux 启动时,conmux 会自动做三件事:

  1. 注入 --session-id <uuid> —— 给这个会话钉一个唯一 id,观测端据此精确锚定它的日志文件,不会和同一目录下别的历史会话串台。
  2. 顺着 JSONL 读 —— Claude Code 会把会话内容写成 JSONL 日志;conmux 增量读它,从中解析出模型名、token、上下文占比、子 agent 树这些结构化信息。
  3. 挂一个 Notification hook —— 通过临时写一份 --settings 文件(只对这个会话生效,且和你自己的全局 hooks 合并、不覆盖),拿到「等你批准权限 / 闲着等你输入」这类结构化的「需要你」信号,补上终端响铃(BEL)抓不到的那些 TUI 权限框场景。

有几条边界要讲在明处:

  • 只对裸 claude 生效。 你只要给了任何显式参数(哪怕是自带 --session-id-c-p),conmux 就一个字都不动你的命令——不注入、不加 hook。这是刻意的纪律:不越俎代庖改你的启动。
  • 依赖 claude CLI 支持 --session-id(2026-07 当时的版本已验证支持);太老的版本不认这个 flag 会在 pane 里可见地报错退出,不是静默失败。
  • 任一步失败都会诚实降级。 写不了 settings 文件 / 拿不到目录,就退回「只注 session-id」甚至只靠 BEL + 退出信号,绝不假装观测正常。
  • 观测只读真打印 / 真日志的内容,拿不到就留空(UI 显 ),绝不猜、绝不编造模型或活跃状态。

深观测具体解析出哪些字段、怎么判活跃 / 陈旧、子 agent 树怎么来——见 conmux 观测什么。想接入代码真源在 conmux-app/src/observe/(session-observer 与 parsers/claude.ts)、hook 构造在 src/lib/claude-hooks.ts

那「通用适配层」呢——差在哪、会往哪走

坦白说清楚这道缺口:

  • 今天有的:任意 CLI → 起成受监管 pane(通用);Claude Code → 深观测(专属)。
  • 今天没有的:一个「你丢进来就自动识别框架类型、自动套上对应观测 / 适配」的通用脚手架。观测那一层现在是一个框架一份手写适配——Claude 有,别的框架暂时没有。
  • 想让 conmux 认识你的框架,路子就是照 Claude 这条走:为它写一个 parser / 观测适配。这块的可扩展点、以及「机制该管到哪、语义该留给谁」的分界,见 机制 vs 语义:边界在哪

如果你正在做自己的 agent CLI、想让它在 Windows 上被 conmux 好好看住——第一档现在就能用,欢迎直接接;想推动通用适配层、或想把你的框架加进深观测,特别欢迎开 issue / discussion,或直接发 PR。让「把你的 agent CLI 轻松接上 Windows」这件事真正成立,正是这个项目最想和你一起做的事。

conmux 观测什么

如果你在做自己的 agent 框架,最关心的多半是这句:接进 conmux 之后,它能替我看住多少? 这一页老实讲清楚——哪些信号对任何 CLI 都有,哪些只有 Claude Code 能拿到,以及一条不变的底线:conmux 只观测程序真打印、真落盘的东西,拿不到就显「—」,绝不为了好看编一个数出来。

先说一个大前提。conmux 的观测跑在终端窗口的图形壳(conmux-app)里,和终端渲染并行挂一个「观测者」订阅同一份 PTY 输出——它不改你的程序、不注入代码、不猜你在干嘛,只是

对任何 CLI 都有:PTY 级信号

不管你接的是 PowerShell、一个编译脚本、还是别家的 agent CLI,只要它是个正常的命令行进程,conmux 就能给你这几样:

  • 活跃度(running / idle)——最近一小段时间有输出就是 running,静默超过约 2.5 秒翻成 idle。这不是"它在思考"这种判断,就是字面的"最近有没有在往终端吐字"。
  • 进程退出 + 退出码——进程结束时 conmux 拿到确切的退出码(内核层 PaneExited 事件带的是真实 exit code,不是估的)。窗口里那个会话的状态随之变成 exited
  • 终端响铃 → 需注意(attention)——程序往终端打了一个响铃字符(BEL,\x07)、或者进程退出了,conmux 就把这个会话标成"需要你看一眼"。这两个都是真信号(响铃是 tmux monitor-bell 那套语义),不是启发式瞎猜。你切到那个会话看了,标记就清掉——看过了就不再打扰你。

就这些。对非 Claude 的 CLI,conmux 只提供这一层。它不会去解析你程序的模型名、token 数、内部状态——因为它没法诚实地知道,所以干脆不编。观测状态里那些字段(model / tokens / 活动)对普通 CLI 一律是 null,界面上显「—」。

只有 Claude Code 有:深度观测

Claude Code 除了往终端打字,还会把整场会话结构化地落盘成一份 JSONL 文件,另外可以配一个通知 hook。conmux 认得这两样,于是能对 Claude 会话看得深得多。这是 Claude 专属的——靠的是 Claude Code 自己写出来的真数据,别的 CLI 没有这些落盘物,也就没有这一层。

conmux 怎么认出这是个 Claude 会话?要么你在启动时就告诉它(启动命令里带 claude),要么它从终端输出里嗅到 Claude 的稳定标志(比如终端标题恒为 ✳ Claude Code、或那行 Using Opus 4.8 (1M context))。认定之后,才会懒启下面这些源——非 Claude 会话永远不碰它们。

读 JSONL,拿到这些:

  • 模型名(model)——直接取会话记录里的 message.model(如 claude-opus-4-8),是字面真值,不是从终端刮的。
  • 上下文占用(contextPct)——从最后一条真实 assistant 消息的 usage 算:input + cache_read + cache_creation 除以该模型的上下文窗口(Opus / Sonnet 按 1M,Haiku 按 200K;模型不认识就不猜,显「—」)。
  • 会话累计 token(Σ 输入 / 输出)——把整场真实消息的 usage 累加起来。
  • subagent 树——Claude 派子 agent 时会在记录里留 tool_useAgent / 旧版 Task),带 subagent_type 和描述;对应的 tool_result 回来就标记这个子 agent 已完成。conmux 把它渲染成一层扁平的子 agent 列表(Claude 主 agent → 子 agent 就一层,不臆造更深的嵌套)。
  • 正在跑的 workflow / 最近调用的 skill——从 tool_use 里读出来,如实标出。

诚实边界(几个都写在源码注释里的坑):① 只有当会话里出现过真实的 assistant 消息(有真 usage、不是中断产生的 <synthetic> 空消息)才写这些字段,否则保持「—」,不拿假 0 糊弄。② 这些字段来自 JSONL——多数 Claude 版本把 token 计数打到终端上,所以别指望从终端文本刮到它们;深度数据的来源是那份落盘文件。③ 要读 JSONL 得先知道会话的工作目录(cwd);cwd 还没拿到时,深度观测会明确告诉界面"暂时读不到",而不是静默地一直显「—」让你一头雾水。

Notification hook,驱动更准的 attention:

Claude Code 的 Notification hook 能在两种时刻发信号——弹出权限请求框permission_prompt)和空闲时向你提问idle_prompt)。conmux 把这类事件收下来,命中在册类型就把会话标成"需注意"。这比单纯的终端响铃更贴——它精确对上了"Claude 正卡在等你批准 / 等你回答"这个真实时刻。

关于 hook 还有一个诚实标注:conmux 只会在真收到过 hook 事件、确证这条链路通了之后,才亮出"深度感知已生效"。它反过来断言"没亮就是 hook 没工作"——也许只是这会话还没触发过权限框或空闲提问而已。只做正向标注,不做否定推断。

一句话收口

  • 任何 CLI:活跃度、退出码、响铃 / 退出触发的 attention。够你把一排会话"看住"。
  • Claude Code:在上面再叠一层结构化深观测——模型、上下文、token、subagent 树,加上 hook 驱动的精准 attention。
  • 底线不变:观测 = 读它真打印、真落盘的东西。读不到就诚实显「—」,从不臆测、从不填假数。

要把你自己的 agent CLI 接进来、让它至少吃到 PTY 级观测,甚至留出让 conmux 认出它的钩子,见 把你的 CLI 接上 conmux

机制 vs 语义:边界在哪

如果你正在做自己的 agent 框架、想把 conmux 接进来,这一页是最该先读的一页。它讲的不是某个 API,而是一条刻意划死的边界——理解它,你就明白为什么 conmux 值得当地基,以及哪些活它故意不替你干

一句话

conmux 只知道 pane(面板)、进程和字节。它不知道「agent」是什么。

你在别处听到的那些概念——哪个 agent 在等你、谁该排到注意力队列前面、几个 agent 怎么协作、灵动岛上该亮哪盏灯——conmux 一个都不认识。对它来说,一个 pane 里跑的是 Claude Code、是 cargo build、还是一个普通的 PowerShell,没有任何区别:都是一棵被监管的进程树,往外吐字节、偶尔要你喂输入。

这不是还没做,是故意不做

为什么故意划这条线

一个东西想当「地基」而不是「框架」,关键就在这里。

框架替你把语义定死:它假设你在做 agent、假设 agent 该怎么排队、该怎么互相说话——你顺着它的假设走很省事,可一旦你的想法和它不一样,就得跟它对着干。

地基只提供机制,把语义留白:它给你稳的地面(进程不跑飞、输入有人审、断线能接回来),至于地上盖什么——一个 agent 工作台、一个 CI 面板、一个多会话运维工具——它不替你决定,也就不会挡你的路。

conmux 选的是后者。它把「一个 CLI 会话怎么被可靠地托管、监管、断连重连」这件又脏又难的事做到底,然后在语义开始的地方停手。「这个 pane 是个正在等权限的 agent」这句话,conmux 说不出来——因为「agent」「等权限」都是上层的词,得由消费方自己定义。

这也是为什么 conmux 的公开 API 里,你翻遍了也找不到一个带 Agent 的类型。对外能看到的就是 PaneHost、协议消息(MuxRequest / MuxReply / …)、事件出口(PaneOutput / PaneExited)、注入钩子、主题——全是「面板 / 进程 / 字节」这一层的词汇。(要落到具体类型名,见 控制面。)

那语义谁来做——你

边界的另一半,就是留给你的空间。「agent 是什么、几个 agent 怎么协作」这层语义,conmux 一概不碰,全交给上层。 Conflux 就是这样一个上层:它在 conmux 提供的 pane / 事件 / 注入之上,自己定义了「这个 pane 是一个 agent」「这个 agent 弹了权限请求、该提醒用户」「把用户的一条消息广播给这几个 agent」这些概念。

换成你,边界的两侧大致这么分工:

conmux 负责(机制)你负责(语义)
起进程、监管整棵进程树、进程死了给你个准确退出码判断「这个 pane 代表一个 agent / 一次构建 / 一个服务」
把 pane 吐的字节按序交给你(PaneOutput从字节里读出「它在等我输入」「它报错了」「它跑完了」
提供唯一、可审计的输入通道,把你要喂的字节送进 PTY决定「什么时候、喂什么、要不要先过一道策略」
断线重连时把屏幕原样接回来决定「界面长什么样、谁排在注意力队列前面」

一句话:conmux 保证下面那层地面是平的、稳的、看得住的;地上盖什么由你说了算。

依赖是单向的(也请你保持单向)

这条边界在代码里是硬约束,不是口头约定:依赖方向永远是 你的框架 → conmux,反过来不成立。conmux 不依赖任何上层——不依赖 Tauri,不感知 UI,更不认识任何具体框架的业务概念。

对你的好处很直接:

  • 你换掉整个上层(换 UI、换交互模型、甚至换一门语言来驱动它),conmux 那层地面不用动。
  • conmux 出新版本时,它承诺稳定的只是那个小而明确的公开面(协议类型 + 几个核心 trait),其余内部实现明确标了「不稳定、可能随时变」。你贴着公开面写,升级就不容易被内部重构波及。这条原则 conmux 自己叫「库即产品」——公开面小、稳,好过大而全。

记住这一条就够

conmux 给你平、稳、看得住的机制;「这是不是一个 agent、该怎么协作」的语义,是你的活,也是你的自由。

这条边界不是缺功能,是设计上的克制——正因为它在语义开始的地方停手,你才有地方盖自己的楼。


接下来

常见问题(FAQ)

这里收了大家最常问的几个问题,一问一答,说人话。凡是「还没做」的地方都标出来,绝不含糊。

我得先装 WSL 吗?

不用。 Conmux 直接跑在 Windows 自己的终端底层(ConPTY)上,每个面板都是一个真正的 Windows 伪终端。装上就能跑 PowerShell、cmd、任何原生命令行程序,不需要在底下垫一层 Linux 子系统。

那 WSL 里的工具怎么办?能接——你在一个面板里直接跑 wsl.exe <你的工具>,它就跟普通命令一样被 Conmux 接管、监管、断线重连。这条路今天就能用。

老实说:把 Windows 侧和 WSL 侧的进程统一在一棵会话树里监管(比如跨边界优雅地关掉 WSL 进程、路径自动翻译)——这一层还没做,是路线图上的 M4。今天你能在面板里跑 WSL 工具,但那种「一视同仁的统一监管」还在路上。

它和 tmux 是什么关系?

Conmux 是类 tmux 的东西——一个终端复用器(把多个会话装进一个窗口、分屏、断线重连)。但它不是 tmux 的移植版,也不是 drop-in 替换

  • tmux 不为 Windows 而生,官方上游也明确不会去做原生 Windows;在 Windows 上用 tmux,你得先钻进 WSL。
  • Conmux 从头就是 Windows 原生的——跑在 ConPTY 上,不走 Unix 模拟层。

所以 tmux 的配置文件、.tmux.conf、那套快捷键,照搬不过来。Conmux 有自己的一套(GUI 里默认前缀键是 Ctrl+B——特意和 tmux 一致,方便老手,但底下是两套实现)。如果你是 tmux 老手,看 从 tmux 过来 · 差异速查表 最省事。

它很占内存吗?

看你用哪一层,答案不一样,这里给实话:

  • 带图形界面的 Conmux(conmux-app):用的是 Tauri + 系统自带的 WebView2。这不是最极致的轻量方案,内存地板大约几百 MB。它比「把 agent 塞进一整套 IDE(VS Code 加一堆插件)」省得多,但要说「极致省内存」,它不是。
  • 纯命令行的 conmux(不带 GUI 的那个 crate / CLI)很轻。它是纯 Rust 的机制层内核,依赖树刻意压得很小,没有编辑器、没有浏览器引擎那一大坨。

一句话:追求轻量、只在命令行里活动 → 用纯 CLI 的 conmux;想要可视化分屏、会话点阵、深度观测面板 → 用 GUI 壳,接受几百 MB 的地板。

支持 macOS / Linux 吗?

不支持,这是设计使然,不是没来得及做。 Conmux 是 Windows only:它的整个立身之本就是「把 tmux 那类能力做到 Windows 原生」——ConPTY 伪终端、Job Object 整树监管、命名管道 IPC,全是 Windows 平台的东西。在别的系统上,你本来就有 tmux / Zellij。

技术上:GUI 壳只在 Windows(Win10 1809+ / Win11)编译运行。conmux crate 的纯逻辑层跨平台可编译测试(方便开发),但真正干活的 ConPTY / Job Object 后端只在 Windows 上编。

它和 Conflux 是什么关系?

简单说:Conmux 是地基,Conflux 是盖在上面的楼。

  • Conmux 只懂三样东西——面板、进程、字节。它不知道什么叫「agent」,也不想知道。这条「只做机制、不碰语义」的边界,正是它能当地基的原因。它是独立产品:单独下载 Conmux,你拿到的就是一个终端复用器,仅此而已。
  • Conflux 建在 Conmux 之上,是一个多 agent 的 CLI 监管台(GUI)——把「agent」这层语义加回来:谁在等你、谁弹了权限、多个 agent 怎么摆布。

所以:想要一个 Windows 原生的终端复用地基 → Conmux;想要在它之上做多 agent 的可视化监管 → Conflux。

没签名,装它安全吗?

先说清楚现状:GUI 安装包会是未签名的——学生开源项目,没预算买代码签名证书。Windows SmartScreen 会提示「发布者未知」。可以点「更多信息 → 仍要运行」,或者干脆自己从源码构建。(纯 CLI 的 conmuxcargo install --locked conmux 安装,完全不涉及这个提示。)

为什么可以放心:

  • 源码全开——MIT / Apache-2.0 双许可,代码你都能看、能审、能自己 cargo build / npm run tauri:build 出一份你亲手编的版本。
  • 本地无遥测——没有账号、没有分析上报,没有任何数据离开你的机器。连接级审计(谁连了 daemon)只写到本地一个滚动日志 %LOCALAPPDATA%\conmux\daemon.log,不外发。
  • 信任边界是「当前用户」——daemon 走命名管道,DACL 只放行你自己的账号(SID),并拒绝远程客户端。

诚实边界:命名管道那层的身份校验(客户端 fail-closed、客户端用 Authenticode 核对 daemon 的进程镜像)是为了「抬高门槛、保持可审计」,不是用来防同一账号下已经在跑的恶意代码——任何以你的身份运行的程序,本来就能读你的内存、杀你的进程。这是操作系统的边界,不是 Conmux 能替你补上的。这一点我们摊开讲,不假装。


还有没答上的问题?欢迎开 issue 或 discussion,也帮我们把这份 FAQ 补得更全。

排障

装不上、点了没反应、命令行报一串看不懂的错——这一页把 Conmux 上最常撞见的几个坑,按「症状 → 为什么 → 怎么办」列出来。挑你遇到的那条看就行。

如果这里没有你的问题,或者照着做还是不行,开个 issue 告诉我——附上你的 Windows 版本和报错原文,最好排查。


装 GUI 时弹「Windows 已保护你的电脑」(SmartScreen · 发布者未知)

症状:双击 Conmux 的 GUI 安装包,Windows 弹一个蓝框,写着「Windows 已保护你的电脑」「未知发布者」,只有一个「不运行」按钮。

为什么:这是未签名程序的正常待遇,不是病毒也不是装坏了。给程序做数字签名要买一张代码签名证书,而 Conmux 是一个学生的开源项目,没有这笔预算——所以 SmartScreen 认不出发布者,就先拦一下。详情见 FAQ 的「未签名」条目

怎么办

  1. 在蓝框里点 「更多信息」(More info)。
  2. 底下会多出一个 「仍要运行」(Run anyway)按钮,点它。

只用点这一次,之后就正常了。实在不放心这个弹框,也可以从源码自己构建——conmux 命令行工具走 cargo install 安装,完全不碰这个流程。


GUI 打不开 / 白屏(缺 WebView2)

症状:装完了,但 Conmux 的图形界面起不来,或者开出来是一片白。

为什么:Conmux 的 GUI 壳靠系统自带的 WebView2 来渲染界面(这也是它内存地板在几百 MB、不是「极致轻量」的原因,见 这是什么)。

  • Windows 11:WebView2 是系统内置的,一般不用管。
  • Windows 10部分老机器没预装,就会白屏或起不来。

怎么办:如果是 Win10,去微软官网下 「Evergreen WebView2 Runtime」 装上,再重开 Conmux。装好后 GUI 会自己接管,不用额外配置。

纯命令行的 conmux 工具不吃 WebView2——如果你只用 CLI,这条与你无关。


按了 Ctrl+B 没反应(前缀键是两步手势)

症状:想分屏或切 pane,按了 Ctrl+B,屏幕上什么都没发生。

为什么:这多半是正常的。Conmux 沿用 tmux 的两步前缀手势:Ctrl+B 只是「叫一下」,它本身不干活,得先松开、再按第二个键才触发动作。

Ctrl+B  然后按  \        # 竖着分屏
Ctrl+B  然后按  -        # 横着分屏
Ctrl+B  然后按  ← ↑ ↓ →  # 在 pane 之间切焦点
Ctrl+B  然后按  z        # 当前 pane 放大到全屏(再按一次还原)

按下 Ctrl+B 后,状态栏会亮一个 ⌨ LEADER 徽章提示「我在等你的第二个键」——看到它就说明前缀收到了。

如果连徽章都不亮

  • 确认焦点在 Conmux 窗口里(不是别的程序抢了键)。
  • 历史坑:前缀键默认曾是 Ctrl+Space,但在中文 Windows 上会被输入法的「中/英切换」全局热键吃掉,Conmux 根本收不到——所以从 v0.1 起默认已改成 Ctrl+B。如果你用的是很旧的构建、按 Ctrl+Space 没反应,升级到新版即可。
  • 想换成别的前缀键,可以在命令面板里「设置 leader 前缀」重配(前缀必须带 CtrlAlt,裸键会被拒绝,免得每次打字都被吞成前缀)。

嫌两步麻烦?有一个默认关闭的免前缀直接快捷键模式(Ctrl+Alt+\ / - / Z,以及 vim 风格的 Ctrl+Alt+H/J/K/L 切 pane)。默认不开,是为了在你亲手打开前不抢走任何按键。


WSL 里的工具起不来

症状:想把一个 WSL 里的命令接成 pane,结果 pane 一开就退,或者报「找不到 wsl」「没有该分发版」之类的错。

为什么:今天的 Conmux 是直接调用你系统上的 wsl.exe 来起这个 pane 的——它没有自带 WSL,也还没做「跨 WSL 的统一监管」(那是 M4 路线图,见 从 tmux 过来)。所以这类失败基本都是 wsl 本身的问题,跟 Conmux 的分屏/监管无关:

  • WSL 根本没装——命令行里单独敲 wsl -l -v,如果它自己也报错,说明得先装 WSL(微软官方的 wsl --install)。
  • 发行版名字对不上——你指定的分发版名(比如 Ubuntu-22.04)和实际安装的对不上。wsl -l -v 会列出你真正装了哪些,照着列出来的名字填。

怎么办:先在一个普通终端里把这条 wsl 命令跑通(该装 WSL 装 WSL、该对齐分发版名对齐),能单独跑起来后,再把同样的命令接进 Conmux 就顺了。


cargo build 报 schannel / CRL / 证书吊销错误

症状:从源码构建、或 cargo install --locked conmux 时,cargo 卡在拉依赖,报 schannelCRL、或 certificate revocation 相关的错。

为什么:这是 Windows 网络层的老毛病,不是 Conmux 的问题。Windows 的 schannel 对证书吊销列表(CRL)校验很严,在企业网 / 走代理的环境下常常拉不到 CRL,于是整个 TLS 握手就失败了。

怎么办:让 cargo 别做这一步吊销校验——设一个环境变量再重试:

$env:CARGO_HTTP_CHECK_REVOKE = "false"
cargo install --locked conmux

想永久生效,就把 CARGO_HTTP_CHECK_REVOKE=false 加进系统环境变量。换用国内镜像有时也能绕过,但那只是掩盖症状——上面这个开关才是对着根因。


看完还没解决?把报错原文 + 你的 Windows 版本(Win10/Win11)+ 是 GUI 还是命令行一起发到 issue,我尽快跟。

关于 Conflux

Conmux 是地基,Conflux 是盖在它上面的那栋楼

如果说 conmux 解决的是"多个命令行会话怎么在 Windows 上跑得起来、看得住",那 Conflux 想解决的是更上层、也更具体的一件事:你同时开了三四个 AI 编程 agent,怎么才能不被它们淹没。

一句话:conmux 管面板和进程,Conflux 管 agent。 conmux 不知道什么叫"agent"——它只认面板、进程、字节;"这是一个 Claude Code 会话、它在等你批权限"这种语义,是 Conflux 干的活。

它想解决的问题

你在 Windows 上跑 Claude Code、Codex、Aider、OpenCode 这类 agent CLI。一个跑得挺好,三四个一起跑就没法看了——你不可能同时盯四个终端,一个个翻谁卡住了、谁在等你点确认、谁已经跑完。

Conflux 的设计目标就是让你不必逐个盯守:把"现在需要你的那个"浮出来,让你一键跳回它当初发问的那一刻,并在你没看的时候把发生的一切记进审计。

不是又一个聊天窗。agent 的工作在这里被当成真实的会话、画布上的卡片、注意力信号、权限请求,以及一条可回放的事件时间线。

长什么样、能干什么

Conflux 是一个 Tauri 2 + React 的 Windows 桌面工作台,跑在 conmux 内核之上。核心是这么几件事:

  • 多 agent 一块画布——同时打开多个真实 CLI agent,每个都是活的 PTY 会话(ConPTY + xterm.js 渲染,完整 ANSI/配色),以卡片形式铺在同一块画布上,可排布、展开、收起。不是假卡片、不是只读样稿。
  • 会来找你的注意力面——灵动岛 + 侧栏 + 系统托盘,把每个 agent 的状态送到你眼前。对 Claude Code 会话,"这个在等你"来自一个权威 hook 信号,不是刮屏猜的——所以它是一个真实事件,而不是估计。
  • 一键跳回触发处(jump-back)——点通知,Conflux 直接把你带到那个分屏、带到触发它的那个位置,不用在一堆终端里翻找。
  • 不打断心流地介入——展开任意卡片进入双向终端,直接往那个 agent 的会话里打字,处理完再收回网格。
  • 闸门在你手里(权限审批)——agent 的权限请求以审批 UI 浮现,没经你放行的东西不会往下走。
  • 一切留痕——每个事件写入本地 SQLite,会话时间线可事后回放。

老实话:已经通的核心,和还在打磨的理念

这是这一页最该说清楚的地方。Conflux 里真正跑通、稳定可用的,是这么一条链:

多开真 CLI agent → 观测每个会话 → 注意力信号把该处理的浮出来 → 你审批 / 一键跳回 → 全程写进 SQLite 审计、可回放。

这条"监管台"的链子是实的。但agent 之间怎么协作、怎么编排,这套理念仍在打磨,现在没有做的部分,我们直说:

  • 讨论面板是单向广播,不是协作。 它只做一件事:把你的一条 prompt 一次性广播发给多个 agent。
  • agent 之间不互相对话。 它们不会把回复汇进一个共享聊天室,不会彼此交谈——广播出去之后,各跑各的。
  • 没有自动编排引擎。 没有一个"自己决定该派谁干什么活"的调度大脑。决策权始终在你手里——Conflux 监管 agent,但不替你编排它们。

换句话说:监管这一面是真打通的,协作 / 编排这一面还是个方向、不是现状。 别把路线图当成已经能用的功能。

注意力面由两态灵动岛 + 侧栏 + 系统托盘构成;早期设计里的悬浮球(Float Ball)已经删掉,不在其中。

当前状态

  • V1 · 仅 Windows · early。 它是一个能用的工作台,不是成品。
  • 直接跑在 ConPTY 上,不依赖 WSL、不装类 Unix 兼容层。
  • 暂无预编译 / 签名安装包,目前请从源码构建git cloneconflux-appnpm install + npm run tauri:build)。上线的安装包会是未签名的——没预算买代码签名证书,SmartScreen 会提示"发布者未知",点「更多信息 → 仍要运行」即可。

路线上还在往前走的(roadmap,非现状):预编译 / 签名安装包、更广的适配器覆盖与更简单的配置、注意力层与权限层的硬化、时间线 / 审计体验打磨。

Conflux 和 Conmux 是什么关系

  • conmux——独立的 Rust crate:Windows 终端多路复用 + agent 隔离运行时(ConPTY、进程整树监管、单一受审输入路径……),不依赖 Tauri。它是本手册的主角,也能单独用。
  • conflux-app——盖在 conmux 之上的 Tauri 2 + React 产品层(画布、注意力面、权限 UI、审计 / 时间线)。

所以你现在读的这份手册,讲的是地基 conmux;Conflux 是这块地基目前最大的一个消费者,也是"mechanism(机制)vs semantics(语义)"这条边界为什么值得存在的活例子。想深入了解 Conflux 本身,去看它的项目仓库。


Conflux 和 Conmux 都是华南师范大学一名学生做的开源项目,在开放中开发。有想法、遇到 bug,欢迎去对应仓库提 issue / 开 discussion / 发 PR——一起把 Windows 上的 agent 生态做得更好。