
我原本想给 Codex 做一台外接设备,最后留下的是手机。
最早想做 AgentDeck 时,我想的真是一台设备。7 月去上海参加 WAIC 时,我还看到有人在做类似的产品,甚至已经拿它创业了。
我当时的设想很直接:桌上放一块触控屏,专门看 Codex 正在做什么。哪个会话停下来等我回答,哪条命令需要审批,我瞄一眼就能处理。方向跑偏了,伸手按一下中断。离开书桌,这块屏也能带走。
我看过树莓派触控屏,也考虑过平板和闲置手机。名字倒是很早就定了:AgentDeck,给 Agent 用的控制甲板。
真动手时,硬件反而被我放到了一边。我得先让 Windows 电脑上的 Codex 出现在另一块屏幕上,会话还要接得上,权限也不能乱。这件事没跑通,造个外壳没有多少意义。
手边原本有一台闲置的安卓手机,准备拿来做原型。可真要每天多带一台手机,事情又绕回去了。最后我用的是日常那台 iPhone。屏幕、网络、电池和身份验证都已经在手里,先用它把整条路跑一遍。
这台临时拿来测试的手机,后来留了下来。
我不想把 Windows 桌面塞进手机
Codex App 有配套的手机端。我平时用得最多的却是 Codex CLI,而且经常同时开几个会话。
电脑上的 Codex CLI 多会话工作区

Codex CLI 很适合连续工作。它读代码、改文件,测试跑到一半需要做决定时才停下来找我。人在电脑前,这个停顿可能只有几秒。我下楼、开会或者在路上,它就可能白等半小时。
远程桌面当然能让我回到那个终端,但在手机上用起来很累:字小,点击目标也小,软键盘一弹出来,终端只剩半块。权限处理起来更别扭。我只想批准一条命令,手机却顺便拿到了整台电脑的操作能力。
所以 AgentDeck 从第一版开始,就只向手机传递 Codex 会话。手机可以看消息、命令、文件变更和运行状态,也能回答问题或中断任务。它看不到 Windows 桌面,也没有一个可以随便发送按键的 PTY 入口。
这个选择定下来以后,工作量才真正露出来。终端画面可以直接传,结构化会话却要弄清每条消息、每个审批到底属于谁。
我当时列的需求很贪心。真实终端、多会话、完整历史和手机回复都想要,命令与文件审批也不能少。Windows 锁屏后要继续运行,用户登录后要自动启动,离开局域网照样能访问。日常命令还得保持原样。我不想为了让手机看到会话,每天背一串新参数。
"本机优先"也是那时定下的。手机只是扩展,不是电脑端的上级。AgentDeck 出问题,Codex 仍然应该能在本机工作;手机看不懂新的权限请求,就把它留给本地终端。这些原则当时只有几句话,后来几乎每一句都返工过代码。
手机打开页面只是第一步
第一个 Windows 原型有四个主要部分:Codex App Server 管理线程和实时状态;Bridge 把协议收窄成手机需要的接口;PWA 负责展示和输入;cloudflared 则把只监听本机回环地址的 Bridge 接到 Cloudflare Tunnel。
项目文件和 Codex 历史都留在 Windows 用户环境里。我没有引入 WSL,也没有把代码搬到云上。App Server 和 Bridge 的端口只监听 127.0.0.1。外部请求先过 Cloudflare Access,再通过 Tunnel 进入 Bridge。路由器不开入站端口,Windows 防火墙也不增加公网入站规则。

手机很快就能打开页面,但那只能证明链路通了。真要拿它处理 Codex 会话,后面还有一堆问题。
早期的认证流程很繁琐。先启动几个组件,手机登录 Cloudflare,然后还要回电脑找配对码。处理一次 Codex 审批可能只要几秒,进入 AgentDeck 花的时间反而更长。
后来我改成了长会话。配对过的设备平时可以直接进入,内部会话过期后用 Passkey 恢复。只有设备凭据丢失、被撤销,或者浏览器状态被清除时,才需要重新配对。
Cloudflare 还给我上过一堂很具体的课。Tunnel 上游不可用时,它会返回一整页 502 HTML。前端把这页 HTML 当成普通错误文本,手机上于是冒出一大块红色内容,开头是 <!DOCTYPE html>,下面拖着长长的 Cloudflare 错误页。
后来前端会先检查内容类型,这类返回只显示一句简短错误。状态检查也拆开报告 Bridge 和 Tunnel。这个区分之后反复派上用场:本地端口在监听、/readyz 正常、Tunnel 已经注册,只能说明这几层各自在工作,代替不了 Access 登录和手机验收。
我曾经卡在 No authentication methods set up 上。Tunnel 明明连着,AgentDeck 也没有拒绝请求,问题其实是 Cloudflare Access 的登录方式没配完。这几类故障在手机上看起来都是"进不去",处理方法却完全不同。
配对命令本身也踩过 Windows 的坑。有些机器的 PowerShell 执行策略会拒绝未签名的公共脚本;唯一用户身份没有写进环境时,配对命令又会直接报错。后来的运行手册把临时执行策略、环境加载和配对命令整理成一条明确路径。靠我记得某个环境变量放在哪里,这套东西显然交不到别人手里。
我曾经认真做过一版远程终端
第一个原型里已经有 App Server,但我当时还没确定它会不会成为最终入口。为了保留自己熟悉的 CLI 体验,我中间认真做过一套 Windows 原生终端代理,想用它替代结构化接入。
那版设计由 .NET Terminal Host 管理 ConPTY,负责启动 cmd.exe 和 Codex CLI。Node Bridge 处理外部身份、会话和日志。本机键盘与手机输入进入同一个 FIFO,中断时向目标 ConPTY 发送 Ctrl+C。会话上限、日志保留、断线后怎样补输出,我都写进了规格,随后照着规格写计划、写代码。
真实终端保留了 ANSI 输出、窗口尺寸和原有 CLI 行为,很接近我平时看到的 Codex。代价是结构没了。审批、问题和运行状态都得从终端文本里重新识别。Codex 明明已经通过 App Server 给出了结构化事件,终端方案却先把它们压成字符,再从字符里猜回来。
2026 年 8 月 11 日,仓库先提交了 Windows 原生终端模式。实际跑起来后,结构丢失带来的麻烦很快盖过了终端画面的便利。当天后续的提交开始恢复结构化 Bridge 入口,并把本地 TUI 和手机都接到统一 App Server。ConPTY 最终成了一次真正写到代码里的替代方案实验,App Server 则回到了主路径。
这一天看起来有点反复。可我确实需要把两条路都推到足够具体,才能知道自己想留下什么。AgentDeck 最终保留的是 Codex 的会话结构,不是一张远程终端画面。
Zellij 也在这一阶段进入过讨论。它很适合在本机组织窗格,也能抓取屏幕、发送按键。但这些能力无法说明某个审批属于哪个 threadId,更不能证明屏幕上的一段命令就是当前等待处理的请求。Zellij 后来仍可作为本机界面,会话归属则交给 App Server。
两块屏得连着同一个现场
Codex 会把历史写入磁盘。新客户端之后可以读取这份历史,也可以从那里继续一轮。可正在独立 TUI 进程里运行的 turn、实时输出和审批请求,仍属于那个活着的进程。新客户端读到同一份历史文件,不等于进入了当前连接。
我早期遇到的几个怪现象都源于这里。手机创建了会话,电脑终端没有跟过来。电脑直接启动的 Codex 退出后能被手机读到,运行时却接不上。codex resume 走另一个入口,还可能加载成独立副本。
后来,电脑终端和手机都改为连接同一个受管 App Server。本地 TUI 经过回环地址上的路由代理,Bridge 也连过去。两边的消息、输出和审批都带着同一个 threadId。
这样做也带来一条明确限制:AgentDeck 启动前已经独立运行的 Codex,无法在 turn 中途安全迁入。我可以等它完成,也可以先中断。会话保存以后,再从受管入口恢复。
这条限制不能只藏在架构文档里。早期手机会在普通 Codex 退出后看到它的历史,用户很容易误以为运行时也能接管。后来 AgentDeck 会检测这类未受管终端,在手机上明确标成"未同步"。它可以告诉我这个终端存在,但不会猜它正在使用哪个线程。
与此同时,AgentDeck 增加了明确的受管启动入口。普通终端里照旧输入 codex、codex resume 或 codex fork,PATH shim 负责把命令带到受管 App Server。我想明确进入 AgentDeck 时,也有专用命令可用。已经独立启动的终端继续工作,不会突然被绑定。
单会话跑通后,多会话很快又出了问题。电脑明明开着四个 Codex 窗口,手机上只有一两个;有时页签没标题,只剩一串标识。最严重的一次,某个会话的审批卡跑到了另一个会话里。
手机需要分别知道三件事:App Server 里有没有这个线程,当前有没有电脑终端连着它,以及线程的工作目录能不能通过手机访问。
早期的 presence 文件只能证明某个进程写过心跳。Codex hook 没补上 threadId 时,Bridge 只知道有终端活着,却不知道该把它放进哪个会话。版本一变,hook 还可能直接失效。
路由代理后来直接观察 thread/start、thread/resume 和 turn/start 这些请求,再把连接绑到线程。连接关闭,绑定随之解除。presence 文件留作诊断,不再单独决定在线状态。
这段逻辑实际分了几次才做完。第一次只记录活着的终端连接;后来才把每个 TUI socket 绑到当前线程,再让路由通路的信号覆盖旧的 presence 判断;最后还要结合目录授权,才能把会话发布到手机。缺了其中任何一步,都只能解释一部分现象。
同一线程还可能同时开着两个电脑终端。这时线程只能出现一次,界面却要另外记录实例数。关掉其中一个终端,只要另一个还连着,手机上的会话就不能从在线区消失。一个布尔值说不清这些状态。
目录授权也得单独检查。线程目录经过规范化和链接解析后,才能与允许根目录比较。四个窗口里有两个位于未授权路径,手机显示两个就是正确结果。我只需加入实际使用的项目路径,没必要放开整个用户目录。
会话列表在这期间改过几次。电脑上正开着、正在运行或者等我处理的会话放在顶部,退出后的历史会话收进单独入口。顶部页签改成横向滚动,标题缺失时也不再直接塞一个完整 UUID。
这里还有个容易忽略的细节:等待审批和等待回答的会话要往前排,普通新会话不能随便抢焦点。我打开手机往往只有几十秒,顶部状态决定了我先点进哪个会话。状态没写清楚,多会话页面就只是一排 UUID。
iPhone 上的"按钮坏了"
我最初坚持用横屏。命令和代码需要宽度,顶部切换会话,中间看内容,底部放连接状态。不输入时,这个布局很好用。我还设想过用左右滑动直接切换会话。
一点击输入框,iPhone 软键盘就占掉大半屏幕。Codex 刚才问了什么,输入时已经看不清了。
界面后来分成两种用法。横屏用于监控和快速回复,只显示最近内容与紧凑输入框;竖屏保留更多上下文,还有多行编辑器。每个会话保存自己的草稿,旋转屏幕或切换会话都不会把内容带错地方。
iOS Safari 的地址栏、软键盘和横竖屏切换会反复改变可视高度。页面后来监听 visualViewport,按真实视口摆放内容和输入区。
有一次,"收起"按钮看起来完全没反应。click 事件其实已经触发,界面还是像卡住了一样。这个动作要同时释放焦点、关闭键盘、恢复布局,还得留下草稿。漏掉任何一件,手机上的结果都是"按钮坏了"。
这些交互几乎都是拿着 iPhone 一项项试出来的。草稿会不会串到别的会话,Passkey 取消后登录状态还在不在,都单独修过。长审批把按钮挤出屏幕后,操作区又改了一轮。桌面浏览器里显示正常,从来不等于 iPhone 上已经能用。
终端输出也慢慢变成了对话。Codex 在左,用户在右。命令、文件变更和系统状态使用全宽事件卡。Markdown 要安全解析,代码和表格可以单独横向滚动。流式输出始终更新同一条 Codex 消息,不会每收到一段增量就新建一个气泡。
时间线有一阵会在审批出现后停住。Codex 还在输出,手机上却只剩会话开头的第一句话。问题出在前端或 Bridge 把审批状态与消息更新绑在了一起。审批可以阻塞任务,但不该顺手截断消息订阅。修好以后,时间线继续更新,审批卡留在底部等人处理。
AgentDeck 的横屏界面

AgentDeck 的竖屏会话视图

审批卡比我预想的难做
最早的审批还要输入文字。手机明明有触控屏,批准和拒绝当然应该直接点。可按钮做出来后,我很快发现,最难的根本不是点击。
命令一长,按钮会被挤到屏幕外。有些请求带着持久权限,手机页面未必能把风险说完整。文件变更还有时序问题:审批请求和完整补丁可能分别到达。
最后定下的规则是,手机只允许批准能够完整核对的请求。命令全文、工作目录和网络目标都要展示完整;文件补丁必须按线程、turn 和 item 对上审批请求。手机说不清一项权限,页面就不提供批准按钮,用户得回到本地终端。
Bridge 会暂存提前到达的补丁或请求,等另一边到达后合成同一张卡。审批队列按会话隔离,路由代理只把线程级请求发给已经绑定的客户端。审批串台的问题到这里才算处理掉。
Bridge 重启后,文件审批还得恢复。只恢复请求,却找不到对应补丁,那张卡仍然不能批。我后来专门补了恢复路径。关联证据不足时,手机退回只读状态。
正向审批起初每次都要用 Passkey 和 Face ID,一次性授权与 request ID 绑定。很稳妥,也很慢。后来审批策略变成可配置:新安装默认使用 passkey,单用户实例可以明确切换到 confirm。
confirm 模式会把完整内容再显示一遍,等我点击"确认批准"才提交。Cloudflare 身份、设备配对、Origin、CSRF 和目录授权都还在,确认框只负责防误触。只要手机已解锁、AgentDeck 会话仍有效,拿到手机的人也能批准。选择这个模式,就得接受这项风险。
问题回答后来也走同样的确认流程。回答一句"继续",可能让 Codex 开始下一批修改,影响未必比批准命令小。手机会在提交前保留完整回答,让我再看一遍;发送失败,草稿也不能消失。
AgentDeck的审批卡页面


十来天里的五十三次提交
整理这段经历时,我又翻了一遍提交记录。2026 年 8 月 11 日到 21 日,仓库里一共有 53 次提交。
它们没有沿着预先画好的路线图前进。11 日做完终端模式,代码当天就开始转回统一 App Server。12 日刚接上会话导航和双向同步,未受管终端的状态又冒了出来。13 日有 12 次提交,很多时间花在 iPhone 输入、Markdown 和审批卡上。到了 21 日,仓库又多了 16 次提交。真实终端在线状态、移动端确认策略和文件审批恢复,都在那一天反复调整。
提交数当然不能直接换算成工作量。真正耗时间的是每次提交后冒出来的那个小问题,以及修完后必须重走一遍的使用过程。第三个页签滑不到,取消 Passkey 后登录状态丢了,补丁先于请求到达时和审批卡对不上。每件事单看都不大,只要有一件留在手机上,AgentDeck 在那个时刻就不可靠。
这段时间还留下十多份设计和实施计划。我很少能把一个问题一次写对。通常先重现,再补测试和代码,然后回到电脑与手机上重跑。页面恢复正常后,还要断一次连接、重启一次 Bridge,或者再开几个会话,看看刚修好的状态会不会串到别处。某个问题当天消失,第二天可能借着重启、断线或新版 Codex 换个样子回来。
测试的写法也跟着变。最早只确认某个入口有没有运行,后来开始核对完整参数、线程归属和退出码。在线状态的集成验收会同时打开至少三个受管 Codex 页面,关掉其中一个,手机上只能有对应会话离线。两个窗口打开同一线程时,实例数也得对。到这个阶段,"页面能打开"早就算不上验收结果了。
Codex 的版本变化尤其麻烦。从 0.146.1 迁移到 0.147.0 时,我原本只打算替换可执行文件。真正动手后,启动参数、schema、presence hook 和审批字段全要重查。0.148 又带来了桌面审批隔离问题。
AgentDeck 后来单独管理协议适配,针对精确版本生成 schema,再重跑多客户端、审批和恢复测试。正式安装只使用已经验证的版本。截至 2026 年 8 月 26 日,0.149.1 的 schema 和适配仍在工作树里收尾,这次升级还没完成。
适配层最敏感的仍是审批。新版本多出一个权限字段,手机端的旧展示器就可能漏掉风险。AgentDeck 遇到未知字段时,不会继续显示可批准卡片。精确版本的 schema 先进入仓库,adapter 再明确列出已经支持的请求。手机发回的决定也只能使用上游本次给出的选项,不能擅自把"本次允许"改成"本会话一直允许"。
每次升级都要重新走一遍启动、双客户端同步、问题回答和审批。文件变更要核对完整补丁,命令要看工作目录与网络目标。应用能启动,只是清单的第一项。
AgentDeck 坏了也不能拖住 Codex
统一 App Server 把电脑和手机连到了一起,也让 AgentDeck 进入 codex 的启动路径。这样一来,Bridge 或初始化脚本出问题,原本可以单独使用的 Codex 也可能启动不了。这个代价我不能接受。
普通终端后来增加了用户级 PATH shim。输入 codex、codex resume 或 codex fork 时,shim 先尝试连接本机路由代理。如果 AgentDeck 在 Codex 启动前就初始化失败,shim 会显示警告,再带着原始参数启动本地 Codex。这个会话不会出现在手机上,但人还能继续工作。
回退功能曾经坑过我一次。我输入 codex resume <session-id>,AgentDeck 初始化失败后确实启动了原生 Codex,界面里却是一个新会话。
我顺着 shim、wrapper 和启动脚本查下去,最后找到 PowerShell 函数里的 $args。这个自动变量遮蔽了外层参数。fallback 实际只执行了 codex,resume、会话 ID 和附加参数一个都没带过去。
原来的测试只检查 fallback 进程有没有启动。我先加上 resume thread-123 --model gpt-test 的透传断言,修复前它果然失败。接着把变量换成 $nativeArguments,受管和原生两条路径都使用这组参数。现在,这类测试还会核对子命令、会话 ID、附加参数和退出码。
回退只发生在受管 Codex 启动之前。受管进程已经运行后,后续的非零退出不能再启动一个本地副本。同一条命令产生两个会话,比直接失败更难收拾。
调试后期,我还碰到过 helper_unknown_error: setup refresh had errors。它发生在本地执行器的准备阶段。Codex 还能对话,却读不了文件,也不能测试和修改。Bridge 的 HTTP 健康检查一切正常,对这个故障毫无帮助。
当时连 echo ok 都会在命令真正启动前失败。我最后查到,某个项目里的 .codex 目录所有者与父目录不一致,sandbox refresh 写 ACL 时收到 Windows 错误 5。碰到这种故障,应该先查 owner、继承和 ACL,别上来就删 .codex 目录重建。
Windows 的电源状态也不会因为 AgentDeck 改变。锁屏通常不会停止用户会话,手机仍能继续访问;休眠会暂停 CPU 和网络,AgentDeck 没法自己唤醒主机。
Windows 登录后,可以由用户级 Supervisor 启动 Bridge 和 App Server。cloudflared 更适合交给 Windows 服务单独托管。截至 2026 年 8 月 26 日,这部分已经有设计和脚本,但重启恢复与真实登录场景还要再验一遍。
要把 AgentDeck 交给别人,仍有不少事情要做。安装器要检查 Node.js、Codex 精确版本、PowerShell 环境和 cloudflared,生成本机 token,再引导用户配置 Access、Tunnel 和允许目录。升级失败后,原始 Codex 也必须能用。不同 Windows 账户、企业执行策略和目录 ACL,让"在我电脑上能跑"和"别人能装"之间隔着很多步。现在这套东西已经能支撑我的日常使用,但要交给不熟悉内部结构的人,还不能只靠一份运行手册。
手机留了下来
现在这套 Windows 原生方案已经能覆盖我的日常使用。我在受管终端里启动或恢复 Codex,手机上能看实时输出,也能切换会话。Codex 停下来提问,我可以回答;turn 走偏了,我可以中断;内容能完整核对的审批,也可以直接在手机上处理。
它还没到所有机器都能开箱即用的程度。跨平台进程托管、密钥存储和安装器仍要继续做,每次 Codex 升级也得重新验收。
macOS 和 Linux 从架构上可以接入,实际落地时要各自解决进程托管、密钥存储和休眠恢复。我现在更愿意让每台电脑保留独立的 App Server、Bridge 和允许目录,由手机选择主机。几台机器共用一套文件权限,出问题时很难查清究竟是哪台主机动了手。
专用硬件仍然有用。桌面常亮屏、机房值守面板、团队控制台,都有各自的场景。但我现在只有一个人用,手机已经够了。它一直在口袋里,联网、触控和身份验证都是现成的。
开始时,我想造一块放在桌上的屏幕。现在 Codex 留在电脑上工作;需要我时,我拿出手机,看清上下文,做完决定,再放回口袋。
AgentDeck 的名字没有变。现在,这块 Deck 就是我的手机。
目前AgentDeck已经投入使用了,但页面显示效果我觉得不够美观,并且后续我也可能会有新的需求,我会持续迭代。