Files
codex-hud/docs/superpowers/specs/2026-03-22-codex-hud-design.md
manpengan e38e56ba3e docs: add codex-hud design spec and original plan
Design spec covers dual-mode architecture (tmux pane + inline fallback),
SQLite/JSONL data layer, HUD layout, installation, and error handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 08:48:25 +08:00

11 KiB
Raw Blame History

Codex HUD — Design Spec

Date: 2026-03-22 Status: Approved Author: manpengan + Claude


1. Problem

Codex CLIOpenAI运行时没有实时状态面板。用户看不到当前 session 的 token 消耗、tool 活动、task 进度等信息,只能在 TUI 内滚动查看零散输出。

claude-hud 为 Claude Code 解决了这个问题,但它依赖 Claude Code 的原生 statusline plugin API。Codex 没有这个 API。

2. Key Discovery

Codex 在本地写入了结构化数据,足以支撑 HUD

数据源 路径 内容
SQLite ~/.codex/state_5.sqlite threads, jobs, agent_jobs, agent_job_items, logs 等表
Session JSONL ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl 结构化事件session_meta, event_msg, response_item, task_started
History ~/.codex/history.jsonl 全局历史
TUI log ~/.codex/log/codex-tui.log TUI 日志

其中 threads 表直接包含:rollout_path, cwd, model, tokens_used, git_branch, updated_at

结论:主数据源放在 SQLite + Session JSONL不走 stdout 启发式解析。

3. Architecture

3.1 Mode Selection

启动时按优先级降序选择渲染模式:

codex-hud 启动
       |
  探测 pane 能力(不只看 $TMUX/$ZELLIJ 环境变量)
  |-- tmux: `display-message` 验证 client/session 可操作 --> pane mode
  |-- zellij: `action` 能力探测                         --> pane mode
  |-- 探测失败                                           --> inline mode (inject --no-alt-screen)
  |-- inline 也失败TTY 不可用 / 渲染初始化失败 / 被禁用)--> passthrough mode纯透传无 HUD

探测逻辑:

  • 有环境变量不代表可安全 split可能是嵌套 session、只读 client、shell 残留)
  • tmux: 执行 tmux display-message -p '#{session_id}' 验证可操作性
  • zellij: 执行对应 action 探测
  • 探测失败才降级

3.2 Pane Mode (preferred)

+--------------------------------------+
|      Codex full-screen TUI (main)    |
|      原生体验,参数不做任何修改          |
+--------------------------------------+
| claude-4o | ~/pro | main* | 2m       |  <-- HUD pane (4 lines)
| tokens 12,340 | Write  12s            |      纯 SQLite 轮询
| shell(3) read(7) write(2)            |      无 PTY
| task 3/5 | rollout-2026-03-22         |
+--------------------------------------+
  • tmux split-window -l 4 开底部 pane
  • HUD 作为 wrapper 内子模块运行在 pane 中(不是独立进程)
  • 主 pane 启动真实 codex参数原样透传
  • Codex 退出时自动关闭 HUD pane

3.3 Inline Mode (fallback)

|  Codex 滚动输出 (--no-alt-screen)    |
|  > Read file: src/index.ts           |
+--------------------------------------+
| claude-4o | ~/pro | main* | 2m       |  <-- ANSI scroll region
| tokens 12,340 | Write  12s            |      PTY wrapper 维护
| shell(3) read(7) write(2)            |
| task 3/5 | rollout-2026-03-22         |
+--------------------------------------+
  • PTY wrapper 注入 --no-alt-screen,预留底部 4 行
  • ANSI scroll region + cursor save/restore
  • Codex 以滚动模式运行(牺牲全屏 TUI 体验)

3.4 Passthrough Mode (last resort)

  • child_process.spawn 真实 codex完全透传
  • wrapper 退出码跟随 codex
  • 触发条件:不在 tmux/zellij + inline 注入失败/被显式禁用 + TTY 不可用 + 渲染初始化失败

4. Data Layer

两种渲染模式共用同一套数据层。

4.1 Observers

sqlite observer  -- 500-1000ms 低频轮询 --> 稳定状态 (model, tokens, branch, jobs)
jsonl observer   -- fs.watch 唤醒 + offset 增量 tail + 低频轮询兜底 --> 实时事件
                          |
                    state/store.ts
                    收到事件 --> 立即重绘
                    空闲时   --> 1s timer 兜底刷新elapsed 计时用)

SQLite observer:

  • 每 500-1000ms 轮询 ~/.codex/state_5.sqlite
  • 读取最新 threadsmodel, cwd, git_branch, tokens_used, updated_at
  • 可选读 jobs, agent_jobs

JSONL observer:

  • fs.watch 只负责唤醒macOS 会丢边缘事件)
  • 真正读取靠"记住 offset 后增量 tail"
  • 额外加低频轮询兜底(防止 fs.watch 丢事件)
  • 事件标准化:normalize(rawEvent) -> HudEvent | null
    • 不硬编码事件字段名,先 normalize 再处理
    • 无法识别的事件返回 null静默跳过

4.2 HudState

type HudState = {
  // from SQLite threads table
  model: string | null
  cwd: string
  gitBranch: string | null
  tokensUsedTotal: number | null     // thread 累计 tokens_used不是 context 窗口占用率
  sessionPath: string | null

  // from JSONL events (via normalize)
  activeTool: { name: string; startedAt: number } | null
  toolCounts: Record<string, number>
  taskProgress: { done: number; total: number } | null

  // from timer
  elapsedSec: number

  // render meta
  renderMode: RenderMode             // "pane" | "inline" | "passthrough"
  termWidth: number
  termHeight: number
}

type RenderMode = "pane" | "inline" | "passthrough"

type HudEvent =
  | { type: "task_progress"; done: number; total: number }
  | { type: "tool_start"; tool: string }
  | { type: "tool_end"; tool: string }
  | { type: "session_meta"; model: string }
  // ... normalize layer handles mapping raw event names to these

语义约束:

  • tokensUsedTotal 是累计消耗值,不是 context window 占用百分比,不渲染成百分比条
  • activeTool 带时间戳,用于显示持续时长和超时回收
  • 无法可靠获取的字段显示 n/a~estimated

5. Components & File Structure

src/
  cli.ts                      # 入口:解析 argv运行模式检测路由

  launcher/
    resolve-codex.ts          # 找真实 codex binary
    argv.ts                   # passthrough args, --no-hud, inline 模式注入 --no-alt-screen
    pane.ts                   # tmux/zellij pane 生命周期 (open, resize, close)

  detect/
    tmux.ts                   # display-message 探测,返回 { capable, sessionId }
    zellij.ts                 # action 探测,返回 { capable }

  observers/
    sqlite.ts                 # 轮询 state_5.sqlite产出 SqliteSnapshot
    jsonl.ts                  # tail 最新 session JSONL产出 HudEvent stream

  state/
    store.ts                  # 合并 SqliteSnapshot + HudEvent -> HudState, emit change
    timer.ts                  # session elapsed (wrapper 启动时间起算)

  terminal/
    pane-renderer.ts          # pane 模式clearScreen 循环,写入 stdout
    inline-renderer.ts        # inline 模式ANSI scroll region + cursor save/restore
    layout.ts                 # dense(4) / compact(3) 布局,输入 HudState 输出 string[]
    tty.ts                    # resize 监听cleanup (scroll region 还原, cursor 还原)

  types/
    hud.ts                    # HudState, HudEvent, SqliteSnapshot, RenderMode

  config.ts                   # ~/.codex-hud/config.json, defaults
  install.ts                  # install / uninstall / doctor

相比原计划删除的部分:

  • observers/stdout-parser.ts — 删除(不做 stdout 启发式解析)
  • observers/app-server.ts — 删除
  • observers/process-tree.ts — 删除
  • state/tools.ts / state/agents.ts 启发式推断 — 删除

6. HUD Layout

6.1 Dense Mode (4 lines, terminal height >= 24)

Line 1: {model} | {cwd_short} | {branch}{dirty} | {elapsed}
Line 2: tokens {tokensUsedTotal} | {activeTool.name}  {duration}
Line 3: {toolCounts summary}
Line 4: task {done}/{total} | {sessionLabel}

6.2 Compact Mode (3 lines, terminal height < 24)

Line 1: {model} | {branch} | {elapsed}
Line 2: tokens {tokensUsedTotal} | {activeTool.name}
Line 3: {toolCounts compact} | task {done}/{total}

6.3 Width Degradation

Width 丢弃
< 100 sessionLabel
< 80 toolDuration, dirty 标记
< 60 toolCounts只保留 activeTool
< 40 只保留 model + elapsed

6.4 Field Provenance

  • 来自 SQLite / JSONL normalize可信: 正常白色
  • 推断/估算: 暗色 + ~ 前缀
  • 不可用: n/a(暗色)

7. Installation

7.1 Install

codex-hud install

执行:

  1. 写 wrapper 到 ~/.codex-hud/bin/codex(薄 shell 脚本 -> 固定绝对路径 dist/cli.js
  2. 在 shell rc 文件中用标记块前置 PATH
    # >>> codex-hud >>>
    export PATH="$HOME/.codex-hud/bin:$PATH"
    # <<< codex-hud <<<
    
  3. 写 manifest.json

7.2 Manifest

{
  "realBin": "/usr/local/bin/codex",
  "wrapperPath": "~/.codex-hud/bin/codex",
  "installedAt": "2026-03-22T...",
  "version": "0.1.0",
  "shellRcFilesModified": ["~/.zshrc"],
  "pathEntry": "$HOME/.codex-hud/bin"
}

7.3 Real Binary Resolution

  1. CODEX_REAL_BIN 环境变量(显式 override
  2. manifest.json 记录的路径
  3. PATH 中排除 ~/.codex-hud/bin 后的第一个 codex

7.4 Bypass

codex --no-hud [args...]

直接 execvp 真实 binarywrapper 进程替换,零开销。

7.5 Uninstall

codex-hud uninstall
  • 删除 ~/.codex-hud/bin/codex
  • 删除 shell rc 中的标记块
  • 删除 manifest.json
  • 保留 config.json(用户数据)

7.6 Doctor

codex-hud doctor

检查:

  • real binary 可达
  • wrapper 在 PATH 中优先
  • codex --no-hud --help 真实可执行
  • ~/.codex/state_5.sqlite 可读
  • tmux / zellij 能力检测
  • config 可写

8. Error Handling & Degradation

8.1 Observer Failure

场景 行为
SQLite 不可读 tokensUsedTotal: null显示 n/a每 5s 重试
最新 JSONL 找不到 toolCounts 清空activeTool: null不报错
JSONL normalize 返回 null 静默跳过,不更新 state
fs.watch 失败 降回纯轮询 (1s),不影响 HUD 显示

8.2 Render Failure Cascade

pane 初始化失败   --> 自动降级到 inline mode
inline 初始化失败 --> 自动降级到 passthrough mode
任何模式崩溃      --> tty.ts cleanup --> 退出

8.3 Process Exit Cleanup (mandatory, all paths)

1. inline mode: 还原 scroll region清除 HUD 行,显示光标
2. pane mode: 关闭 HUD pane
3. PTY: 等待子进程退出或超时 kill
4. 退出码跟随真实 codex

处理 exit, SIGINT, SIGTERM, uncaughtException。任何 crash 先跑 cleanup再打印简短错误以真实 codex 退出码退出。绝不让用户终端停在残留 scroll region 或隐藏光标状态。

9. Product Rules

  • pane 模式不修改传给 codex 的任何参数
  • inline 模式才注入 --no-alt-screen
  • --no-alt-screen 不应自动强推,它是 fallback 而非平级体验
  • 无法可靠获取的指标显示 n/a~estimated,绝不虚构精度
  • wrapper 必须支持 codex --no-hud 立即 bypass
  • crash 后必须恢复 terminal 状态
  • install 必须可逆one command uninstall
  • shell rc 修改使用标记块,安装幂等

10. Tech Stack

  • Node.js 20+ / TypeScript
  • node-pty (inline mode PTY)
  • better-sqlite3 (SQLite 读取)
  • macOS + zsh 为首选目标