分析对象:CodexMonitor(
github.com/Dimillian/CodexMonitor),MIT,本分析基于 main 分支 2026-03-26(commit dd61b9a,PR #584 "Refactor tray thread menu")。作者 Dimillian(Thomas Ricouard)——SwiftUI 开源圈知名开发者(ACarousel、MovieSwiftUI)。 代码规模:860 文件 / 28MB。Rust 后端 92 文件 / 31,171 行(src-tauri/),TS 前端src/557 个 ts/tsx 文件 / 112,428 行(连 css 计 603 文件 / 127,161 行),另有 docs/、scripts/(iOS 构建链)。运行时依赖官方codexCLI(codex app-server协议)。 一句话定位:给 OpenAI Codex CLI 配的桌面指挥台——Tauri 2 应用,每个工作区 spawn 一个codex app-server子进程,通过官方 stdio JSON-RPC 协议(而非 PTY 抓屏)驱动多个 Codex agent thread;提供工作区/worktree 管理、流式对话、diff/PR 审查、用量统计、听写、iOS 远程 daemon 全套。 读者对象:已经读过本系列至少一份分析的读者(尤其 OpenAI Codex 源码分析 与 Kanban 源码分析——本文是 Codex 分析"加深版"的姊妹篇,也是与 kanban"PTY+hooks 方案"的对照样本)。全文标注文件:行号,可回源码核对。
目录
- 项目概览:给 Codex CLI 配一个指挥台
- 全景架构:Tauri 双进程 + app-server 协议 + 事件流
- app-server 接入:一个工作区一个 codex 进程,LSP 式握手
- 协议交叉印证:CodexMonitor × codex 官方 app-server 协议
- thread 模型与事件驱动状态机
- Rust shared 层:app 与 daemon 共享的领域核心
- worktree 与工作区:一等对象
- git / GitHub:libgit2 读 + CLI 写 + gh 集成
- 远程 daemon 与 iOS:一套 core 两份形态
- 前端架构:feature-sliced + 零状态库
- 前端功能特性:Composer / 听写 / diff / 用量
- 配置三权分立:settings.json / workspaces.json / config.toml
- 工程化与测试:AGENTS.md 分层纪律 + 单测密度
- 开发思路与品味:Dimillian 的十个选择
- 总结:三个最独特的设计与取舍
第 1 章 项目概览:给 Codex CLI 配一个指挥台
CodexMonitor 是什么? README 第一句:A Tauri app for orchestrating multiple Codex agents across local workspaces。它不是又一个编码 Agent——Agent 是 codex,它是 Agent 的指挥台:
- 多工作区:侧栏管理多个项目,每个工作区 spawn 一个
codex app-server,独立CODEX_HOME。 - 多线程:一个工作区里多个 thread(对话会话),支持 resume/fork/archive/copy、pin/rename、每线程草稿、打断/steer 进行中的回合。
- worktree 隔离:给 agent 建独立 git worktree(独立分支 + 独立 codex home),多 agent 并行零冲突——和 kanban 一卡一 worktree 同一精神。
- Composer:图片附件、Queue vs Steer 双跟随模式、
$skills //prompts://review/@文件自动补全、模型选择、context ring。 - git/GitHub 全流程:diff 面板、stage/revert、commit log、分支、GitHub issues/PR(列表/diff/评论),"Ask PR" 把 PR 上下文送进新 agent 线程。
- 用量仪表:home 用量快照(解析 codex 的 rollout JSONL)+ 侧栏 credits 计量表。
- 听写:本地 Whisper(whisper-rs),hold-to-talk + 波形。
- iOS 远程模式:桌面起 daemon(TCP JSON-RPC),iPhone 连 Tailscale 跑瘦客户端。
它和 codex / kanban 的关系(本系列的两个邻居)
| codex(官方 CLI) | kanban(编排看板) | CodexMonitor(桌面指挥台) | |
|---|---|---|---|
| 谁是 Agent | 自己就是 | 7 种 CLI Agent | 只有 codex |
| 集成方式 | — | PTY 进程 + hooks 反向注入 | 官方 app-server 协议(stdio JSON-RPC) |
| 形态 | 终端 CLI | 浏览器看板 | Tauri 桌面 + iOS |
| 编排粒度 | 单会话 | 卡片(任务) | workspace × thread |
| 与 codex 关系 | 本体 | codex 是 7 种之一(PTY) | codex 是唯一核心,走官方插座 |
最有意思的对照:kanban 和 CodexMonitor 做同一件事(多 agent 编排),但走了两条截然不同的技术路线——黑盒适配(PTY 抓屏 + hooks)vs 官方协议(app-server JSON-RPC)。这是贯穿全文的主线。
数字化的项目形状
| 维度 | 数字 | 说明 |
|---|---|---|
| 后端 | src-tauri/ 92 文件 / 31,171 行 | Tauri 命令 + shared 领域核心 + daemon |
| 前端 | src/ 557 个 ts/tsx / 112,428 行(含 css 计 603 文件 / 127,161 行) | React + Vite + feature-sliced,25 个 feature |
| Tauri 命令 | generate_handler! 注册 125 条(源码共 138 处 #[tauri::command],差额是 dictation/terminal/menu 的 cfg 双实现) |
lib.rs invoke_handler 注册,每条命令"本地 or 转发 daemon"双轨 |
| app-server 方法 | 约 22 个 | codex_core.rs 调用,全部官方协议方法(无私有/实验) |
| 依赖(前端) | 19 条运行时依赖,UI 库只有 6 类 | lucide / react-markdown(+remark-gfm) / prismjs / xterm / react-virtual / Sentry,另加 @pierre/diffs、vscode-material-icons 与 6 个 @tauri-apps 插件——无 Tailwind/Radix/状态库 |
| 平台 | macOS 主 + Windows/iOS | 托盘仅 macOS;Windows opt-in;iOS 远程 daemon 模式 |
| 许可 | MIT | 极宽松 |
第 2 章 全景架构:Tauri 双进程 + app-server 协议 + 事件流
分层约定(AGENTS.md 硬性规则)
┌──────────────────────────────────────────────────────────────┐
│ React 前端(feature-sliced,src/features/ 25 个 feature) │
│ 编排在 hooks/* 与 bootstrap/*,事件 fanout 只在 services/ │
└───────────────┬──────────────────────────────────────────────┘
│ Tauri IPC(注册 125 条 #[tauri::command])
┌───────────────▼──────────────────────────────────────────────┐
│ Rust 后端(src-tauri/) │
│ ┌──────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
│ │ lib.rs 命令层 │→ │ shared/* │← │ bin/codex_monitor_ │ │
│ │ workspaces/ │ │ 领域核心 │ │ daemon(远程模式) │ │
│ │ files/ ... │ │ (source of │ │ #[path] include 同源 │ │
│ │ 薄适配层 │ │ truth) │ │ │ │
│ └──────┬───────┘ └──────┬──────┘ └──────────────────────┘ │
│ ▼ ▼ │
│ codex app-server 子进程(每 workspace 一个,stdio JSON-RPC) │
└──────────────────────────────────────────────────────────────┘
- 领域逻辑放
src-tauri/src/shared/*——app 与远程 daemon 编译进同一二进制、共用同一套*_core函数,避免逻辑漂移。 codex/、workspaces/、git/等是 Tauri 命令薄适配层;前端 IPC 只经src/services/tauri.ts。- daemon(
src-tauri/src/bin/codex_monitor_daemon.rs,TCP 行分隔 JSON-RPC)与 app 复用 shared core,方法名/负载对齐。 - 前端 feature-sliced:
src/features/{app,threads,workspaces,...},事件 fanout 只在src/services/events.ts。
命令注册表(src-tauri/src/lib.rs,337 行)
invoke_handler 注册 125 条命令(源码共 138 处 #[tauri::command],多出的是 dictation/terminal/menu 按 cfg 分桌面与移动两套实现)。每个 codex 命令模式统一:先查 remote_backend::is_remote_mode,是则 call_remote(转发 daemon),否则转 shared::codex_core::*_core。状态经 state::AppState manage:
workspaces: Mutex<HashMap<String, WorkspaceEntry>>sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>>——每个 workspace 一个 app-server 进程app_settings、codex_login_cancels、tcp_daemon等
事件流(贯穿全篇的主干)
codex app-server stdout(JSON 行)
→ backend/app_server.rs 分类(请求响应 / thread/list cwd 归属 / 纯通知)
→ AppServerEvent{workspace_id, message}
→ event_sink.rs → app.emit("app-server-event")
→ 前端 services/events.ts createEventHub 懒订阅 fanout
→ useAppServerEvents(30+ 方法白名单 + approval 后缀匹配)
→ useThreadEventHandlers → 各 reducer slice dispatch
→ React UI
第 3 章 app-server 接入:一个工作区一个 codex 进程,LSP 式握手
spawn(src-tauri/src/backend/app_server.rs,L749 spawn_workspace_session)
check_codex_installation(codex --version)。build_codex_command_with_bin构造命令:可配codexBin/codexArgs,Windows 特判 cmd/bat。current_dir(entry.path)设工作目录,设CODEX_HOME,参数["app-server"]——每个工作区一个独立 codex 进程 + 独立 codex home(互不污染,配置/会话/用量数据天然隔离)。- stdio 全管道化。
stdio 读写与 JSON-RPC 编解码
- 写侧:
write_message(JSON 单行\n定界)。 - 读侧两个 task:stdout 用
BufReader::lines()逐行serde_json::from_str;stderr 转发为codex/stderr事件。 send_request_for_workspace(L494):自增next_id→ 注册pending: oneshot与request_context(workspace, method)→ 超时 300s → 响应按 id 回填。send_notification/send_response(服务器请求如 approval 的应答)。
事件分类(智能路由的关键)
每条 stdout 行先判三类:
- 有
id+result/error= 请求响应 → 查request_context还原 workspace/method,并extract_related_thread_ids回填thread_workspace映射。 thread/list响应:额外解析 cwd →resolve_workspace_for_cwd(最长前缀匹配)建立 thread→workspace 归属;memory_consolidation子代理线程加入hidden_thread_ids并改发codex/backgroundThread(hide)合成事件(内存整理子代理不污染主界面)。- 纯通知:按
thread_workspace路由 workspace;全局通知(account/*)广播到 session 下所有 workspace。
与 kanban PTY+hooks 方案的本质区别
这是本分析最重要的对照点:
| kanban | CodexMonitor | |
|---|---|---|
| 数据来源 | PTY 字节流 + hooks 文本回调 | 结构化 JSON-RPC 通知 |
| 状态语义 | 解析 hook payload / 轮询 rollout jsonl | 类型化事件(thread/status/changed、item/started/completed、delta 流) |
| 控制方向 | 单向(只收) | 双向(send_response 应答权限、turn/steer 打断) |
| 可靠性 | 依赖各 Agent hook 实现,Codex 甚至要轮询 | 协议原生保证 |
| 代价 | 适配 7 种 Agent 的适配器矩阵 | 绑定 codex 官方协议(生态单一) |
一句话:kanban 把 Agent 当"黑盒子"(屏幕抓取 + 文本流),CodexMonitor 把 codex 当"服务"(官方插座直连)。前者广、后者深。
第 4 章 协议交叉印证:CodexMonitor × codex 官方 app-server 协议
本章所有 codex 侧引用均来自本仓库检出的 codex 官方源码(
参考项目/codex/codex-rs/)。这是把 CodexMonitor 的代码与 codex 本体对齐的一次"对照实验"。
4.1 协议方法全集(codex 侧)
codex 侧 codex-rs/app-server-protocol/src/protocol/common.rs(4072 行)定义了完整协议面(本节所有 common.rs: 行号均指该文件):
- 客户端请求(ClientRequestPayload,common.rs:482–1235):
initialize:482、thread/start:491、thread/resume:497、thread/fork:503、thread/archive:509、thread/list:630(cursor/limit/sortKey/sourceKinds)、turn/start:836、turn/steer:842、turn/interrupt:848、review/start:889、model/list:895、collaborationMode/list:964、skills/list:684、account/rateLimits/read:1058、account/read:1192 等。 - 实验性方法(
#[experimental]):thread/search、environment/*、process/spawn|kill、thread/increment_elicitation、memory/reset。 - 服务器→客户端请求(ServerRequestPayload,即 approval 请求,common.rs:1505–1554):
item/commandExecution/requestApproval、item/fileChange/requestApproval、item/tool/requestUserInput、item/permissions/requestApproval、item/tool/call、attestation/generate。 - 服务器通知(common.rs:1658–1731):
thread/started、thread/status/changed、turn/started、turn/completed、item/started、item/completed、item/agentMessage/delta、item/plan/delta、command/exec/outputDelta、account/updated、model/rerouted等。
4.3 app-server 的官方设计初衷(codex 侧 AGENTS.md)
AGENTS.md:104–106:把 app-server APIs 列为外部集成面的破坏性变更必搜项(CodexMonitor 这类客户端要盯版本)。:270:新 API 只进 v2,v1 冻结;:273<resource>/<method>单数命名;:274–276线上 camelCase;:286–287实验面用#[experimental]门控;:294–296list 方法默认 cursor/limit 分页。- 官方 README 定位:app-server = 给编辑器/IDE 的"插座"——让 IDE 不用解析终端 ANSI、不用 hack hooks,以 LSP 式协议直接驱动 Codex。
CodexMonitor 正是走在官方指定路径上的:src-tauri/src/backend/app_server.rs:760–771 spawn 参数、build_initialize_params(:419–430)的 clientInfo{name:"codex_monitor"} + capabilities.experimentalApi:true、initialized 通知(:1091)——与官方 codex codex-rs/app-server/src/request_processors/initialize_processor.rs:70–101 的握手契约逐项吻合。
4.4 rollout JSONL 用量统计的交叉印证
- 官方格式:
~/.codex/sessions/YYYY/MM/DD/rollout-<ts>-<uuid>.jsonl,行结构RolloutLine{timestamp, ordinal, item}(codexcodex-rs/protocol/src/protocol.rs:3380),item是 8 变体枚举RolloutItem:SessionMeta | ResponseItem | InterAgentCommunication | InterAgentCommunicationMetadata | Compacted | TurnContext | WorldState | EventMsg(同文件 :3186–3199)。TokenCountEvent含info(total/last_token_usage 的 input/cached/output)+rate_limits(同文件 :2140–2143)。 - CodexMonitor(
src-tauri/src/shared/local_usage_core.rs,840 行):spawn_blocking解析 JSONL——session_meta/turn_context的 cwd 过滤 workspace(:220–229)、取 model(:232–237)、token_count 差值去重计 input/cached/output(:281–380)、agent_message/agent_reasoning计时长(:260–279)、response_itemassistant 计运行数(:383–409)。 - 选了:token 用量、缓存、model、cwd、活跃时长/次数。漏了:
rate_limits(credits/plan_type——官方已落盘的最有价值字段)、originator/source(CLI vs VSCode vs appServer)、子 agent 血缘、exec_command/file_change 调用计数。
品味观察:用官方已落盘的数据做本地统计(不调 API、不挂钩子),是"与官方数据格式同频"的聪明做法;但 840 行手写解析也暴露了"够用为止"——真正该做的 credits 统计反而没读。
4.5 config.toml / auth.json / rules 三处小交叉
[features](codexcodex-rs/config/src/config_toml.rs:455→codex-rs/features/src/lib.rs:656的FeaturesToml):键定义在同文件steer:1286(恒启用)、collaboration_modes:1340(恒启用)、unified_exec:856、apps:1106(需 chatgpt 认证)。personality顶层枚举None|Friendly|Pragmatic(codexcodex-rs/protocol/src/config_types.rs:311–317)。CodexMonitor 同步的 5 个字段与官方键名完全吻合,且正确拒绝已废弃的collab键(CodexMonitorsrc-tauri/src/codex/config.rs:50–53)。auth.json:{OPENAI_API_KEY, tokens:{id_token, access_token, refresh_token, account_id}, last_refresh};idToken JWT payload 含chatgpt_plan_type/email。CodexMonitorsrc-tauri/src/shared/account.rs:63–99的 URL_SAFE_NO_PAD 解码→取chatgpt_plan_type/email 完全对应,并作为account/read失败时的 fallback(src-tauri/src/shared/codex_core.rs:627–650的account_read_core)。prefix_rule是 execpolicy 的 Starlark 内建(定义在execpolicy/src/parser.rs;config/src/requirements_exec_policy.rs:51–58是它在requirements.toml里的镜像结构,源码注释亦如此写),decision="allow"可让 agent 绕过审批。CodexMonitorsrc-tauri/src/rules.rs:97–100往~/.codex/rules/default.rules追加prefix_rule(pattern=[...], decision="allow")——语义正确。
这四组交叉印证说明一件事:Dimillian 写 CodexMonitor 时是逐字段对着 codex 官方源码和配置格式做的——协议方法名、握手参数、config 键、auth JWT、rules 语法全部对齐。这是"与上游保持契约级同步"的集成品味。
第 5 章 thread 模型与事件驱动状态机
5.2 后端不维护状态机
关键设计:CodexMonitor 的 Rust 侧不维护 thread 状态机——状态是事件驱动的前端 reducer。thread/status/changed、turn/started/completed、item/started/completed、approval 请求(*requestApproval 后缀)经 useAppServerEvents.ts 的 METHODS_ROUTED_IN_USE_APP_SERVER_EVENTS 路由到 useThreadsReducer 的 slices,threadNormalize.ts 兜底 camelCase/snake_case。running/idle/needs_approval 完全由前端从事件推导。
品味观察:这是"协议即真相"的推论——既然 app-server 会推送权威事件,后端就不需要镜像状态(少一层状态同步 bug)。对比 kanban 的后端状态机(session-state-machine.ts 78 行),CodexMonitor 把状态迁移放进了前端 reducer(1179 行)。
第 6 章 Rust shared 层:app 与 daemon 共享的领域核心
src-tauri/src/shared/ 共 17 个模块 / 31 文件 / 11,197 行——纯逻辑层,不依赖 Tauri/IPC,只依赖 types、codex::config/home、backend::app_server::WorkspaceSession、storage。app 与 daemon 编译进同一二进制、各自调用同一套 *_core 函数。
6.1 worktree 核心(workspaces_core/worktree.rs,642 行)
add_worktree_core 是高度泛型函数(FSpawn/FGit/FSanitize 等 9 个闭包注入),核心就是 git worktree add,三种分支情形:分支已存在 → worktree add <path> <branch>;本地无但有远程跟踪 → worktree add -b <branch> <path> <remote_ref>;否则 worktree add -b <branch> <path>。
- 根目录优先级:workspace 级
worktrees_folder> 全局global_worktrees_folder/<parent_id>>data_dir/worktrees/<parent_id>。 - 创建后生成
WorkspaceEntry{kind: Worktree, parent_id, worktree:{branch}};可copy_agents_md继承父仓库 agents.md;用 marker 文件记录一次性worktree_setup_script是否已跑。 remove_worktree_core:kill session →git worktree remove --force→git worktree prune --expire now。rename_worktree_core:git branch -m+git worktree move(带完整回滚)。
6.2 git / GitHub(git_ui_core/*)
混合实现(本分析品味亮点):
- 读操作走 libgit2(git2 crate):status/diff(diff.rs StatusOptions/Status 位枚举映射 A/M/D/R/T、DiffOptions patch、10MB 图片 base64、2MB 文本上限、has_ignored_parent_directory);log(log.rs revwalk + graph_ahead_behind)。忽略文件识别才用 git check-ignore --stdin -z CLI。
- 写操作走 git CLI(commands.rs,tokio_command):commit/push/pull(--autostash→--no-rebase 回退链)/fetch/stage/revert/checkout/git init。
- GitHub 全走 gh CLI(github.rs):gh issue list --json、gh pr list --json、gh pr diff --color never(parse_pr_diff 文本解析器切分文件)、gh api /repos/.../issues/N/comments --jq、gh pr checkout、gh repo create。
6.3 账号与用量
account.rs(221 行):account/rateLimits/read+account/read,失败回退解析$CODEX_HOME/auth.json→ idToken → JWT →chatgpt_plan_type/email(第 4 章已对照官方格式)。local_usage_core.rs(840 行):扫描$CODEX_HOME/sessions/<YYYY-MM-DD>/*.jsonl(含各 workspace 独立 codex_home),聚合每日 input/cached/output tokens、agent_time_ms、agent_runs,算 last7/last30、缓存命中率、峰值日、top4 模型;MAX_ACTIVITY_GAP_MS=2min归并 run。
6.4 多 agent 配置(agents_config_core.rs,1026 行)
读写 [features] multi_agent、[agents] max_threads/max_depth(默认 6/1,钳制 1-12/1-4);agent 定义为 [agents.<name>] 表,managed agent 配置落在 $CODEX_HOME/agents/<name>.toml。CRUD 齐全(create 模板生成、update 重命名同步改文件名、delete),含路径穿越防护与配置回滚。
6.5 设计品味
- shared 独立的原因:app 与 daemon 双进程共享同一 crate 的纯逻辑;所有状态经
Mutex<HashMap>注入、副作用(git/fs/RPC)经闭包或类型注入——天然可单测(tests.rs 437 行)。 - RPC 复用:
git_rpc.rs/workspace_rpc.rs只声明方法名常量 + DTO(to_params/from_params),实现全在*_core——daemon 的 handler 与 app 的 client 共用同一套签名,一层薄映射即可。 - 混合 git 策略:读用 libgit2(快、纯内存、类型安全),写用 CLI(行为保真),worktree 编排也用 CLI——务实而非教条。
第 7 章 worktree 与工作区:一等对象
7.1 工作区模型(workspaces_core)
WorkspaceEntry:id/name/path/kind/parent_id/worktree/settings。kind 有普通仓库、worktree、clone 三种。持久化在 workspaces.json(app data 目录)。
- add_workspace:本地路径或
git clone。 - add_worktree:从父仓库建 worktree(第 6.1 章)。
- add_clone:URL clone 新工作区。
- 每个工作区可有独立 settings(codexBin、codex_home、worktrees_folder)。
7.2 与 kanban 的 worktree 对照
| kanban | CodexMonitor | |
|---|---|---|
| 粒度 | 一卡一 worktree(任务级) | 一工作区可建多个 worktree(agent 级) |
| 身份 | worktree 是卡片的"影子" | worktree 是 workspace 的一种 kind(一等对象) |
| 生命周期 | 卡片 trash 即删 | 可增删改查、改名(带回滚)、改上游 |
| 目的 | 并行任务的资源隔离 | 给 agent 独立分支 + 独立 codex home |
品味观察:kanban 把 worktree 当作任务执行的副产物;CodexMonitor 把 worktree 当作可管理的实体(WorktreeEntry 可 rename/pin/archive)——它是"工作区管理产品"而非"任务看板",产品意图决定了数据模型。
第 8 章 git / GitHub:libgit2 读 + CLI 写 + gh 集成
(核心机制已在第 6.2 章覆盖,本章补充前端面)
- diff 面板:渲染用
@pierre/diffs+ 自建diffsWorker(WorkerPool)+@tanstack/react-virtual虚拟列表,主题在 design-system/diffViewerTheme。 - stage/unstage/revert:
stage_git_file/unstage_git_file/revert_git_file(useGitActions)。 - 行内评论:GitHub PR review——
usePullRequestLineSelection选行、get_github_pull_request_comments拉取、提交为独立 review thread。 - "Ask PR":PR 被选中即
prefillDraftPR 摘要进 composer;runPullRequestReview:确保 workspace 已连接 → 复用当前 thread 或新建 →buildPullRequestReviewPrompt(PR 元数据 + diffs ≤6 文件×40 行 + comments ≤8 + intent 指令,安全 fence)→sendUserMessageToThread送进新 agent thread 并 activate。
品味观察:"Ask PR"是"把 GitHub 流程交给 agent"的妙笔——agent 能读 PR 上下文、按意图干活,这正是 CodexMonitor 相对传统 git GUI 的差异化价值。
第 9 章 远程 daemon 与 iOS:一套 core 两份形态
src-tauri/src/bin/ 共 4,902 行:daemon 入口(1966)+ daemonctl(1414)+ rpc 域处理(codex 510 / workspace 273 / rpc.rs 201 / git 196 / prompts 149 / daemon 60)+ transport(103)+ dispatcher(30)。
9.1 daemon 架构
- 协议:TCP + 行分隔 JSON-RPC(JSONL,每行一个 JSON 对象),非 HTTP。默认
127.0.0.1:4732(daemon 内部)/0.0.0.0:4732(daemonctl 对外)。 - 帧格式:请求
{"id":n,"method","params"};响应{"id":n,"result"|"error"};事件无 id:{"method":"app-server-event","params":...}(broadcast::channel(2048)推给所有已认证连接)。 - 认证:
auth握手。token 来自--token或CODEX_MONITOR_DAEMON_TOKEN;未认证时非 auth 请求一律返回unauthorized;--insecure-no-auth跳过。并发限流:每连接Semaphore(32)。
9.2 RPC 分发与复用度
dispatcher.rs 按序 try_handle:daemon → workspace → codex → git → prompts。复用度极高:daemon 入口用 #[path=] 直接把主程序模块(backend/shared/files/workspaces_core 等)include 进来,DaemonState 方法几乎 1:1 包装 xxx_core——与本地 Tauri 命令共用同一批 core。差异仅是 AppState → DaemonState + DaemonEventSink;menu_set_accelerators 降级 no-op("remote parity")。
9.3 daemonctl 生命周期控制(1414 行里的大半)
- status:
probe_daemon三步状态机——ping(未认证则收到 unauthorized)→auth→daemon_info;产出NotReachable / Running{auth_ok} / NotDaemon。 - start:先探测;在跑但身份/版本/mode 不匹配(
should_restart_daemon)→ RPCdaemon_shutdown优雅停机 → 轮询 20×100ms → 仍不退则lsof -iTCP:<port>解析 PID → SIGTERM → SIGKILL。仅当认证通过且进程名是codex-monitor-daemon才允许强杀(can_force_stop_daemon)。 - 无日志文件、无 PID 文件——PID 全靠端口反查(极简主义,但依赖 lsof)。
- command-preview:输出完整启动命令模板(token 用
<remote-backend-token>占位符)。
9.4 为什么 iOS 要远程模式
iOS 无桌面资源:无法 spawn codex app-server 子进程、无法跑 git、无本地 workspace 文件系统——做成瘦客户端连桌面 daemon。terminal_mobile.rs 与 dictation/stub.rs 全部命令直接返回 "Terminal/Dictation is not available on mobile builds."(终端需要本地 PTY;听写需要本地 Whisper 模型)。
9.5 Tailscale 集成
tailscale_status:扫描候选二进制(PATH → homebrew → Tailscale.app bundle)→ tailscale version 验证(looks_like_tailscale_version 防 GUI 错误输出)→ tailscale status --json。macOS 用 launchctl asuser <uid> 以 GUI 用户身份执行。suggested_remote_host 解析 DNSName/TailscaleIPs/CurrentTailnet 生成 macbook.tailnet.ts.net:4732。iOS 端直接把建议 host + token 填进 Settings > Server。
9.6 品味观察
~5000 行看似多,核心逻辑全是复用的薄壳,真正新增的是三层粘合:#[path] 模块拼接、JSON 参数解析样板、daemonctl 生命周期管理。为什么值得:移动端无法本地跑 codex,若不这么做就得为 iOS 单独重写整个 backend——"一套 core + 一份协议"同时服务桌面与 iOS,行为天然一致。代价:桌面端多了一类"进程 + 端口 + 版本漂移"运维复杂度,TCP 明文 token 依赖 Tailscale 私有网络而非自身加密。
第 10 章 前端架构:feature-sliced + 零状态库
10.1 规模与组织
src/ 共 603 个 ts/tsx/css 文件、约 127K 行;src/features/ 496 文件 / 103K 行,25 个 feature,采用 feature-sliced 的 components/hooks/utils 子目录约定:
| feature | 文件/行 | 职责 |
|---|---|---|
| app | 141 / 27,205 | 启动、布局、侧栏、控制器(最重) |
| threads | 70 / 20,636 | 聊天线程状态机 |
| settings | 42 / 12,111 | 设置页 |
| git | 59 / 9,099 | diff/log/branch/GitHub |
| workspaces | 36 / 6,931 | 工作区管理 |
| composer | 32 / 6,270 | 输入框 + 自动补全 |
10.2 入口与路由
- 无路由库,纯状态驱动。
App.tsx(64 行)用useWindowLabel()(读 Tauri 窗口 label)分流——about窗口懒加载<AboutView>,否则<MainApp />。 main.tsx:Sentry 初始化 +app_open计数 + 移动端手势/视口兼容。MainApp.tsx(1882 行)巨型组合根:无 Context,通过 ~70 个 hook 的返回对象逐层传 props。视图切换靠activeTabstate(home|projects|codex|git|log)+centerMode(chat/diff)+showHome。
10.3 状态管理选型(品味核心)
零外部状态库:无 redux/zustand/jotai/mobx(已 grep 验证)。纯 useReducer + useState + useRef,通过 props 显式下钻。
threadReducer(1179 行,6 个 slice,无第三方):threadLifecycleSlice(493 行)/ threadItemsSlice(333 行)/ threadConfigSlice / threadQueueSlice / threadSnapshotSlice / common。约 40 个 action(ensureThread/upsertItem/appendAgentDelta/appendToolOutput/appendReasoningSummary/markProcessing…)。useThreads.ts(938 行,主聚合)内部用 ref 镜像 state(itemsByThreadRef等)供事件回调同步读取——解决"闭包捕获旧状态"的经典问题。- 事件到 reducer 的桥:
useThreadEventHandlers→useThreadTurnEvents/useThreadItemEvents/useThreadApprovalEvents→ 各回调 dispatch。
为什么不用外部 store(品味判断): 1. 单 workspace 桌面应用,状态图收敛到"一个 activeWorkspace + 一堆 threads",reducer 足够; 2. 事件密集流式更新(delta 级),需要 ref 镜像 + dispatch,外部 store 收益不大; 3. feature-sliced 本身提供模块边界,避免全局 store 的耦合; 4. 代价是 MainApp 巨型 props 传递和大量 useMemo/useCallback 胶水。
10.4 组件库与样式
无 Tailwind、无 Radix、无 shadcn。依赖仅 lucide-react(图标)、react-markdown+remark-gfm、prismjs、@xterm/xterm、@tanstack/react-virtual、Sentry。UI 全手写:CSS 变量 tokens(ds-tokens.css)+ 语义化类名,组件是普通 function component 拼出来的 ModalShell/ToastShell/PanelShell。
10.5 与 kanban 前端对比
架构同构(后端进程 + Tauri/浏览器前端 + 事件流 fanout + reducer 驱动 UI),但 CodexMonitor 的状态管理明显更重——reducer 1179 行/40 actions、事件解析 hook 数千行、ref 镜像同步、多 workspace 键控状态。差异根源:kanban 卡片状态是粗粒度快照(fetch 后整体替换),CodexMonitor 要承载流式 delta 渲染(agentMessage 逐 delta append、tool 输出流、reasoning 流)+ 审批队列 + token/rate-limit 实时面板 + subagent 级联归档。但架构层反而更轻:无 store 中间件、无 selector 库、无异步 action,复杂度靠 feature-sliced 目录和巨量单测压制。
第 11 章 前端功能特性:Composer / 听写 / diff / 用量
11.1 Composer 自动补全(完全自主实现)
useComposerAutocompleteState.ts 组装三组 triggers:$(skills+apps)、/(8 个硬编码 slash 命令 + prompts)、@(files)。数据全部走 RPC:skills→skills_list、apps→apps_list、prompts→prompts_list(均经 daemon 转发到 codex app-server);files→list_workspace_files(Rust 直接扫盘,上限 500)。触发判定/子序列打分/高亮全部手写,UI 为 ComposerSuggestionsPopover 定位在 textarea 上方,无第三方补全库。$ 选中 app 生成 app://id mention 绑定;/prompts: 支持 findNextPromptArgCursor Tab 跳转参数位。
11.2 Queue vs Steer(交互打磨的典范)
ComposerSendIntent = "default"|"queue"|"steer"。resolveSendMessageOptions:canSteerCurrentTurn = isProcessing && steerEnabled && activeTurnId。steer → RPC turn_steer;queue/不支持时 → send_user_message(turn/start,消息入队由 app 侧 ComposerQueue 管理)。默认模式取设置 followUpMessageBehavior;Shift+Cmd/Ctrl+Enter 发相反模式(oppositeSubmitIntent)。失败区分 steer_failed 且处理 stale-steer 错误,运行时按钮文案在 Steer/Queue 间切换。
11.3 图片附件
三入口均落为本地路径数组(useComposerImages 按 thread draftKey 存):picker 用 tauri open;drag-drop 用窗口级 onDragDropEvent + 本地 DOM drop(web 端 FileReader 转 data URL);paste 由 useComposerImageDrop.handlePaste 读 clipboardData。传给 codex 是 data URL 字符串数组:normalizeImagesForRpc 对 remote/mobile 模式调用 Rust read_image_as_data_url 逐个转换。
11.4 听写(Whisper)
Rust 侧 whisper-rs 本地推理,real.rs 定义 ggml-tiny/base/small/medium/large-v3 从 HuggingFace 下载;download/cancel/remove/status 走 daemon RPC,进度事件推前端。waveform 是前端自绘 36 条动态 bar(DictationWaveform,订阅 level 事件)。
11.5 用量与通知
- 用量:Home usage =
local_usage_snapshot(30天)(Rust 读 codex home 数据,useLocalUsage5 分钟轮询);侧栏 credits meter =account/rateLimits/updatedAppServerEvent → RateLimitSnapshot →getUsageLabels。 - 通知:纯前端驱动,订阅 AppServerEvents(turn 完成/审批请求);系统通知用
@tauri-apps/plugin-notification;Rust 仅send_notification_fallback——macOS debug 下用 osascript AppleScript(非 notify_rust)。声音用本地 mp3 资源 + HTMLAudio。均有最小间隔/窗口聚焦抑制。
11.6 品味观察
倾向自主实现:补全引擎、prompt 历史、rank 打分、waveform、usage view model 全手写;仅把重活交给库(@pierre/diffs、xterm、whisper-rs、tauri 插件、react-virtual)。交互打磨:stale-steer 容错、drag 位置 devicePixelRatio 归一化、代码 fence 展开、/prompts: Tab 跳参、hold-to-talk、follow-up 提示条动态说明 Steer 可用性、mobile 触屏布局分支、每条 RPC 写 DebugEntry 可追踪。
第 12 章 配置三权分立:settings.json / workspaces.json / config.toml
| 文件 | 内容 | 归属 |
|---|---|---|
settings.json(app data 目录) |
AppSettings:codexBin/codexArgs、backendMode、remote backend host/token、快捷键、defaultAccessMode、uiScale/theme、global_worktrees_folder | CodexMonitor 自身 |
workspaces.json |
WorkspaceEntry 列表(id/name/path/kind/worktree/settings) | CodexMonitor 自身 |
config.toml($CODEX_HOME) |
codex 自身:features(steer/collaboration_modes/unified_exec/apps)、personality、model | codex 是 source of truth |
同步策略(settings_core.rs,70 行 + codex/config.rs):5 个与 config.toml 同步的字段(collaboration_modes_enabled/steer_enabled/unified_exec_enabled/experimental_apps_enabled + personality)在读写两个方向都双向同步——get_app_settings_core 从 config.toml 覆盖内存值(codex 权威);update_app_settings_core 反向写回 config.toml(通过 config_toml_core.rs 的 toml_edit)+ 写本地 storage。
品味观察:尊重"codex 的配置归 codex"——App 只同步自己 UI 暴露的那几个开关,不做全量镜像,避免两套配置打架。第 4 章已证实键名与官方完全吻合。
第 13 章 工程化与测试:AGENTS.md 分层纪律 + 单测密度
13.1 AGENTS.md(6KB 部落知识)
- 领域逻辑 → shared/*;命令 → 薄适配层;前端 IPC 只经 services/tauri.ts;事件 fanout 只在 services/events.ts。
- 前端 feature-sliced:
src/features/{app,threads,...},编排在 hooks/ 与 bootstrap/。 - daemon 与 app 复用 shared core,方法名/负载对齐(remote parity)。
docs/codebase-map.md:任务导向的文件查找("if you need X, edit Y")——这是为 agent 开发的导航文档。
13.2 测试密度
- Rust 侧:
git_ui_core/tests.rs437 行、tray.rs 6 单测、files io.rs 6 个 symlink 逃逸测试、worktree/workspaces 各有测试——纯逻辑层因闭包注入而可测。 - 前端:每个 hook 都有
*.test.ts(x)(useSettingsDefaultModels、useThreads、composer 等)。 - 集成:
main.test.tsx、smoke等。
13.3 与 codex / kanban 的工程文化对照
- codex:bazel monorepo、126 crate、AGENTS.md 长而全(含 app-server API 最佳实践)。
- kanban:GritQL 静态检查 + husky + dogfood(用 kanban 开发 kanban)。
- CodexMonitor:分层纪律写入 AGENTS.md(agent 可读可执行)、docs/codebase-map.md 导航、双平台(桌面 + daemon)共用 core 的架构测试、
.codex/目录(项目自己用 codex 开发)。
共性:三个项目都被 agent 优先开发范式 塑造——AGENTS.md 不是文档而是"给 agent 的工程契约",代码库结构要为 agent 的可导航性服务。
第 14 章 开发思路与品味:Dimillian 的十个选择
本章是全文重心。Dimillian(Thomas Ricouard)以 SwiftUI 开源组件闻名(ACarousel 等),却为一个"编排 AI agent 的工具"选择了 Tauri(Rust + Web 前端)。这个选择本身就透露了大量信息。以下十个选择,每个都标注了源码证据。
选择一:用官方协议直连,而非黑盒抓取
CodexMonitor 没有走"spawn codex 的 TUI 然后解析屏幕"的路(那是 kanban 对 codex 做的事),而是 codex app-server + stdio JSON-RPC。这显示了对"上游生态"的尊重:官方提供插座(app-server 协议是给 IDE 设计的),就用插座;宁可绑定 codex 生态,也不做脆弱的屏幕适配。第 4 章证实所有 22 个方法全部是官方协议方法、握手参数逐项吻合、config 键名一致、auth JWT 解析对应、rules 语法正确——这是一份"契约级对齐"的代码,背后是逐字段读官方源码的耐心。
选择三:读用 libgit2,写用 CLI,GitHub 用 gh
不教条——读操作要"快且类型安全"(libgit2 纯内存),写操作要"行为与 git 完全保真"(CLI),GitHub 直接复用 gh(不重复造 API 客户端)。每个场景选最合适的工具,而不是统一抽象。这是老练的工程判断:libgit2 的写路径坑多(配置差异、hooks),读路径稳。
选择四:worktree 是一等对象,不是任务副产物
对比 kanban(worktree 是卡片的影子,trash 即删),CodexMonitor 把 worktree 做成 WorkspaceEntry{kind: Worktree} 的完整实体:可增删改查、改名(带回滚)、改上游、继承 agents.md。产品意图决定数据模型——它是"工作区管理产品",worktree 是给 agent 的独立执行环境(独立分支 + 独立 codex home)。
选择五:前端零状态库 + feature-sliced
无 redux/zustand,纯 useReducer + ref 镜像 + 显式 props 下钻;无 Tailwind/Radix,CSS 变量 tokens + 手写组件;UI 侧只用 6 类库。"复杂度靠目录结构和纪律压制,不靠库"。流式 delta 渲染需要 ref 镜像 + dispatch(useThreads.ts 938 行),外部 store 在这里收益不大;feature-sliced 的 25 个 feature 目录提供了足够强的模块边界。
选择六:能自己写的就自己写
补全引擎(触发判定/子序列打分/高亮)、prompt 历史、rank 打分、waveform(36 条动态 bar)、usage 聚合(840 行 JSONL 解析)、frontmatter 解析、rules 去重、锁文件——全部手写,只把重活交给库(xterm、whisper-rs、@pierre/diffs、react-virtual)。依赖极简主义:前端 600 多个文件,运行时依赖只有 19 条,其中 UI 相关的仅 6 类。
选择七:macOS 优先,但不止 macOS
托盘仅 macOS(tray.rs 701 行,非 macOS 空实现)、窗口外观用 objc2-app-kit 直接调 NSAppearance、图标解析调 defaults/sips——原生平台 API 直接调,不抽象;同时 Windows opt-in(separate tauri config 避免 macOS-only 窗口效果)、iOS 远程模式。这是"主平台做到极致,其他平台务实跟进"的策略。
选择八:iOS 远程 daemon——一套 core 两份形态
iOS 无法本地跑 codex,就起一个桌面 daemon(TCP JSONL JSON-RPC)让 iPhone 连 Tailscale 用。#[path] include 复用主程序模块、daemonctl 用端口反查 PID + 版本比对 + 强杀安全闸门管理生命周期。这是"为多端形态做架构投资"——一份核心 + 一份协议,桌面/iOS 行为天然一致;代价是 daemon 进程/端口/版本漂移的运维复杂度(daemonctl 1414 行里大半在防"杀错进程")。
选择九:agent 优先开发
AGENTS.md 是"给 agent 的工程契约"(分层规则、remote parity、feature-sliced 约定);docs/codebase-map.md 是"if you need X, edit Y"的任务导航;仓库自带 .codex/ 配置——这个项目自己就是用 codex 开发的(与 kanban 的 dogfood 同构)。代码库结构为 agent 的可导航性服务,这是 2025-2026 新一代 AI 原生项目群的共同特征。
选择十:防御性编程 + 交互打磨
canonicalize + symlink 逃逸校验(6 个测试)、400KB 读取截断、严格 UTF-8、Arc::ptr_eq 防竞态、stale-steer 容错、断线 DISCONNECTED_MESSAGE——工程上的保守;同时 Shift+Cmd+Enter 反转 Queue/Steer、hold-to-talk 波形、/prompts: Tab 跳参、follow-up 提示条动态说明——产品上的进取。保守的工程 + 进取的交互,是成熟独立开发者的典型气质。
一句话总结品味
用最少的依赖、最干净的架构分层,把官方协议用到位,做成一个"给 codex 的指挥台"——工程上克制保守,产品上打磨入微,架构上为多端形态提前投资。
第 15 章 总结:三个最独特的设计与取舍
三件事(如果只记三个设计)
1. 官方 app-server 协议直连,而非 PTY 抓屏 这是 CodexMonitor 与 kanban 最本质的分野。kanban 要兼容 7 种 Agent 所以必须黑盒适配;CodexMonitor 只服务 codex 所以走官方插座(LSP 式握手 + 类型化事件 + 双向控制)。取舍:深而不广——放弃"任意 agent 可插",换来"codex 的每一层能力都拿到"(steer 打断、审批应答、sandboxPolicy 精确传递、account/rateLimits 实时)。
2. shared-core 一套核心,桌面/iOS 两份形态
领域逻辑收进纯逻辑层(闭包注入副作用),app 与远程 daemon 用 #[path] include 共用同一批 *_core。这是为 iOS 远程模式做的架构投资——不这么做就得为移动端重写整个 backend。代价是 daemon 运维复杂度(进程/端口/版本漂移)。
3. 前端零状态库 + 流式 delta 状态机 无 redux/zustand/Tailwind/Radix,纯 useReducer + ref 镜像 + 手写 CSS tokens,前端 600 多个文件、19 条运行时依赖(UI 库仅 6 类)。事件驱动状态机(app-server 事件 → hub fanout → reducer slice)承载流式渲染。取舍:MainApp 巨型 props 胶水换来了零中间件复杂度——单 workspace 桌面应用的明智选择。
与 codex / kanban 的一页对照
| 维度 | codex(官方) | kanban | CodexMonitor |
|---|---|---|---|
| 角色 | Agent 本体 | 多 Agent 编排看板 | codex 专用桌面指挥台 |
| 集成路径 | — | PTY + hooks(黑盒) | app-server 协议(官方插座) |
| 状态模型 | rollout 文件资产 | 卡片 4 列状态机 | workspace × thread 事件流 |
| worktree | 无 | 一卡一 worktree | worktree 为一等 workspace |
| 前端 | TUI | React 看板 | React feature-sliced |
| 哲学 | 本地优先 CLI | 浏览器控制面 | 桌面 + iOS 远程 |
适合谁 / 不适合谁
- 适合:重度 Codex 用户(桌面端体验远胜终端)、想多 agent 并行且要 worktree 隔离的开发者、iOS + Tailscale 移动办公场景、研究"官方协议集成"范式的读者。
- 不适合:非 codex 用户(绑定生态)、需要浏览器访问的团队(无 web 版)、轻量用户(安装 Rust toolchain + codex + gh 门槛不低)。
- 与本系列的连接:它是 OpenAI Codex 源码分析的"加深版实战印证"(协议、rollout、config、auth 全部对得上号),也是 Kanban 源码分析的"官方协议对照组"。
🔍 源码指路(回源码核对)
| 想知道 | 看这里 |
|---|---|
| app-server spawn 与握手 | src-tauri/src/backend/app_server.rs(L749 spawn、:419 握手) |
| codex 方法调用全集 | src-tauri/src/shared/codex_core.rs(1033 行) |
| 事件分类路由 | src-tauri/src/backend/app_server.rs(extract_related_thread_ids / resolve_workspace_for_cwd) |
| shared 领域核心 | src-tauri/src/shared/(31 文件 11,197 行) |
| worktree 核心 | src-tauri/src/shared/workspaces_core/worktree.rs(642 行) |
| git 混合策略 | src-tauri/src/shared/git_ui_core/(libgit2 读 + CLI 写) |
| rollout 用量统计 | src-tauri/src/shared/local_usage_core.rs(840 行) |
| 远程 daemon | src-tauri/src/bin/codex_monitor_daemon.rs + codex_monitor_daemonctl.rs |
| 前端事件 hub | src/services/events.ts(createEventHub 懒订阅) |
| 前端状态机 | src/features/threads/hooks/threadReducer/(1179 行 6 slices) |
| Composer 补全 | src/features/composer/(useComposerAutocompleteState.ts) |
| codex 协议定义 | 参考项目/codex/codex-rs/app-server-protocol/src/protocol/common.rs |
| codex 握手实现 | 参考项目/codex/codex-rs/app-server/src/request_processors/initialize_processor.rs(:70–101) |
| codex 最佳实践 | 参考项目/codex/AGENTS.md(:104 app-server 破坏性变更面) |