CodexMonitor 架构分析

"官方的 Codex 桌面版真心不如开源的 Codex Monitor 好用" — 社区评价

核心亮点:轻量(Tauri)、自带文件管理器、一键发送上下文

STAR 快速回顾

S:官方 Codex 桌面版功能冗重,安装包大,用户需要更轻量的替代品。

T:用 Tauri 开发一个轻量级 Codex 客户端,安装包仅 ~10MB。

A

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>>>, // 后台线程事件
}

设计思路

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)

职责

// 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)

设计

// 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
}

依赖


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>,
}

持久化


5. 可迁移经验

5.1 前后端分离

场景 方案 CodexMonitor 示例
轻量 GUI Tauri Rust 后端 + Web 前端
跨平台 Tauri macOS/Windows/Linux
进程通信 stdio JSON-RPC Codex app-server

5.2 文件管理器设计

核心原则

// 可复用的上下文发送模式
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. 相关笔记