CodexMonitor 架构分析
"官方的 Codex 桌面版真心不如开源的 Codex Monitor 好用" — 社区评价
核心亮点:轻量(Tauri)、自带文件管理器、一键发送上下文
STAR 快速回顾
S:官方 Codex 桌面版功能冗重,安装包大,用户需要更轻量的替代品。
T:用 Tauri 开发一个轻量级 Codex 客户端,安装包仅 ~10MB。
A:
- Tauri 前后端分离(Rust 后端 + React 前端)
- 自研文件管理器,不依赖 IDE
- 右击文件/代码 → 一键发送到输入框
R:开源项目,社区评价"比官方好用"。
1. 整体架构
┌─────────────────────────────────────────────────────────┐
│ CodexMonitor │
├─────────────────────────────────────────────────────────┤
│ 前端 (React + TypeScript + Vite) │
│ ├── features/ feature-sliced UI + hooks │
│ ├── services/ Tauri IPC 包装层 │
│ ├── styles/ CSS 按域拆分 │
│ └── types.ts 共享类型定义 │
├─────────────────────────────────────────────────────────┤
│ 后端 (Rust + Tauri) │
│ ├── lib.rs Tauri 入口 + IPC 命令定义 │
│ ├── state.rs 全局状态管理 │
│ ├── backend/ Codex app-server 通信层 │
│ ├── workspaces/ 工作区管理 │
│ ├── threads/ 线程管理 │
│ ├── files/ 文件读写 │
│ ├── git/ Git 操作 │
│ ├── terminal/ 集成终端 │
│ └── dictation/ 语音输入(Whisper) │
└─────────────────────────────────────────────────────────┘
2. 核心设计模式
2.1 WorkspaceSession:每个工作区一个进程
// src-tauri/src/backend/app_server.rs
pub(crate) struct WorkspaceSession {
pub(crate) entry: WorkspaceEntry, // 工作区配置
pub(crate) child: Mutex<Child>, // Codex 进程
pub(crate) stdin: Mutex<ChildStdin>, // 输入流
pub(crate) pending: Mutex<HashMap<u64, oneshot::Sender<Value>>>, // 请求-响应映射
pub(crate) next_id: AtomicU64, // JSON-RPC ID
pub(crate) background_thread_callbacks: Mutex<HashMap<String, mpsc::UnboundedSender<Value>>>, // 后台线程事件
}
设计思路:
- 每个 workspace 独立启动一个
codex app-server进程 - 通过 stdio JSON-RPC 通信
pendingmap 实现请求-响应配对background_thread_callbacks支持后台线程事件推送
2.2 EventSink:事件流架构
// src-tauri/src/backend/events.rs
pub(crate) trait EventSink {
fn emit_app_server_event(&self, event: AppServerEvent);
}
pub(crate) struct AppServerEvent {
pub(crate) workspace_id: String,
pub(crate) message: Value, // JSON-RPC 消息
}
事件流:
Codex 进程 (stdout/stderr)
↓
WorkspaceSession
↓ (emit_app_server_event)
EventSink
↓
前端 WebView
2.3 Tauri IPC 映射
// src/services/tauri.ts
// 前端调用后端命令的包装层
export async function list_workspaces(): Promise<WorkspaceEntry[]> {
return invoke('list_workspaces');
}
export async function send_user_message(
workspaceId: string,
threadId: string,
message: string
): Promise<void> {
return invoke('send_user_message', { workspaceId, threadId, message });
}
// 后端对应命令
// src-tauri/src/workspaces.rs
#[tauri::command]
async fn list_workspaces() -> Result<Vec<WorkspaceEntry>, String> {
// ...
}
3. 关键模块解析
3.1 工作区管理 (workspaces)
职责:
- 添加/移除工作区
- Worktree 支持(隔离工作副本)
- Clone 支持(独立代码库)
- 配置持久化(
workspaces.json)
// src-tauri/src/workspaces/mod.rs
#[tauri::command]
async fn add_workspace(path: String) -> Result<WorkspaceEntry, String> {
// 验证路径是目录
// 启动 Codex app-server
// 保存到 workspaces.json
}
#[tauri::command]
async fn add_worktree(
workspace_id: String,
branch: String,
) -> Result<WorktreeSession, String> {
// git worktree create <path>
// 为新 worktree 启动独立的 Codex 进程
}
3.2 文件管理器(完全脱离 IDE)
设计:
- 不依赖外部 IDE,自己实现文件树
- 支持搜索、文件类型图标、Reveal in Finder
- 右击文件名 → 一键发送到输入框
// src-tauri/src/files/mod.rs
#[tauri::command]
async fn list_workspace_files(
workspace_id: String,
path: Option<String>,
) -> Result<Vec<FileNode>, String> {
// 递归遍历目录
// 过滤隐藏文件
// 返回 FileNode 树
}
#[tauri::command]
async fn read_workspace_file(
workspace_id: String,
path: String,
) -> Result<String, String> {
// 读取文件内容
// 支持大文件分块读取
}
前端集成:
// src/features/app/components/FileTree.tsx
<FileTree
workspaceId={workspaceId}
onFileSelect={(path) => {
// 一键发送到输入框
composer.appendContext(`@${path}`);
}}
onRightClick={(path, selection) => {
// 右击发送选中代码
composer.appendContext(`@${path}\n\`\`\`\n${selection}\n\`\`\`\n`);
}}
/>
3.3 Git 集成
// src-tauri/src/git/mod.rs
#[tauri::command]
async fn get_git_status(workspace_id: String) -> Result<GitStatus, String> {
// git status --porcelain
// 返回:分支名、 staged/unstaged 变更数、 ahead/behind
}
#[tauri::command]
async fn get_git_diffs(workspace_id: String) -> Result<Vec<GitDiff>, String> {
// git diff --stat
// 支持 stage/unstage 单文件
}
#[tauri::command]
async fn get_github_pull_requests(
repo_url: String,
) -> Result<Vec<PR>, String> {
// gh pr list --state=OPEN
}
3.4 语音输入 (Dictation)
// src-tauri/src/dictation/mod.rs
#[tauri::command]
async fn dictation_start() -> Result<AudioStream, String> {
// whisper-rs 实时转写
// hold-to-talk 快捷键
}
#[tauri::command]
async fn dictation_stop() -> Result<String, String> {
// 返回转写文本
// 发送到当前 thread
}
依赖:
whisper-rs:OpenAI Whisper 的 Rust 绑定cpal:跨平台音频输入- macOS:原生
AVFoundation
4. 状态管理
// src-tauri/src/state.rs
pub(crate) struct AppState {
pub(crate) workspaces: Mutex<HashMap<String, WorkspaceEntry>>,
pub(crate) sessions: Mutex<HashMap<String, Arc<WorkspaceSession>>>,
pub(crate) terminal_sessions: Mutex<HashMap<String, Arc<TerminalSession>>>,
pub(crate) remote_backend: Mutex<Option<RemoteBackend>>,
pub(crate) storage_path: PathBuf, // workspaces.json
pub(crate) settings_path: PathBuf, // settings.json
pub(crate) app_settings: Mutex<AppSettings>,
pub(crate) dictation: Mutex<DictationState>,
}
持久化:
workspaces.json:工作区列表 + 配置settings.json:Codex 路径、默认访问模式、UI 缩放localStorage:UI 状态(面板大小、透明度开关)
5. 可迁移经验
5.1 前后端分离
| 场景 | 方案 | CodexMonitor 示例 |
|---|---|---|
| 轻量 GUI | Tauri | Rust 后端 + Web 前端 |
| 跨平台 | Tauri | macOS/Windows/Linux |
| 进程通信 | stdio JSON-RPC | Codex app-server |
5.2 文件管理器设计
核心原则:
- 完全脱离 IDE:自己实现文件树
- 轻量:不用 Qt/GTK,直接用 WebView
- 一键上下文:右击发送选中代码
// 可复用的上下文发送模式
function appendContext(context: string) {
// 1. 读取文件/代码
// 2. 格式化(添加 @路径、```代码块)
// 3. 追加到 composer 输入框
}
5.3 工作区隔离
| 类型 | 用例 | 隔离级别 |
|---|---|---|
| 本地工作区 | 直接编辑代码 | 无隔离 |
| Worktree | 多分支并行 | Git 隔离 |
| Clone | 完全独立副本 | 完全隔离 |
5.4 事件驱动架构
// 后端:事件发射
session.send_request(...).await?;
event_sink.emit_app_server_event(payload);
// 前端:事件监听
useEvent('codex/connected', (event) => {
setConnected(true);
});
useEvent('codex/thread/update', (event) => {
updateThread(event.threadId);
});
5.5 外部进程管理
// 构建 Codex 命令(处理 PATH 环境变量)
fn build_codex_command_with_bin(codex_bin: Option<String>) -> Command {
let mut command = tokio_command(bin);
if let Some(path_env) = build_codex_path_env(codex_bin.as_deref()) {
command.env("PATH", path_env);
}
command
}
// 检查安装
async fn check_codex_installation(codex_bin: Option<String>) -> Result<Option<String>, String> {
let mut command = build_codex_command_with_bin(codex_bin);
command.arg("--version");
let output = command.output().await?;
// 解析版本号
}
6. 依赖分析
6.1 Rust 核心依赖
| 包 | 用途 |
|---|---|
tauri |
GUI 框架、IPC |
tokio |
异步运行时 |
git2 |
Git 操作 |
serde_json |
JSON 序列化 |
tokio::process |
子进程管理 |
uuid |
ID 生成 |
chrono |
时间处理 |
whisper-rs |
语音识别 (macOS) |
6.2 前端依赖
| 包 | 用途 |
|---|---|
react |
UI 框架 |
vite |
构建工具 |
typescript |
类型安全 |
@tauri-apps/api |
Tauri 客户端 |
7. 构建与发布
# 开发模式
npm run tauri dev
# macOS 生产构建
npm run tauri build
# 输出: src-tauri/target/release/bundle/macos/*.app
# Windows 构建(可选)
npm run tauri:build:win
# 输出: src-tauri/target/release/bundle/nsis/*.exe
安装包大小:~10-15 MB(对比官方桌面版的几十 MB)
8. 相关笔记
- Pi - 极简 AI 编码框架 — 极简架构哲学
- AI Editors - Index — AI 代码编辑器对比