Three spikes gating the implementation plan: - Spike 1: pane mode capability (tmux split + cleanup) - Spike 2: data source stability (SQLite fields + JSONL event names) - Spike 3: inline fallback safety (scroll region + terminal cleanup) Each spike has goal, verification method, pass criteria, and fallback decisions. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
516 lines
18 KiB
Markdown
516 lines
18 KiB
Markdown
# Codex HUD — Design Spec
|
||
|
||
> Date: 2026-03-22
|
||
> Status: Approved
|
||
> Author: manpengan + Claude
|
||
|
||
---
|
||
|
||
## 1. Problem
|
||
|
||
Codex CLI(OpenAI)运行时没有实时状态面板。用户看不到当前 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 (degraded fallback, NOT peer to pane mode)
|
||
|
||
> **定位:inline mode 是无 pane 能力时的退化方案,不是与 pane mode 平级的体验模式。**
|
||
> 它需要接管 scroll region 和终端清理,风险显著高于 pane mode。
|
||
> 开发和测试优先级应低于 pane mode。
|
||
|
||
```
|
||
| 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 体验)
|
||
- `--no-alt-screen` 不应自动强推;仅在 pane 能力探测失败时才进入此模式
|
||
|
||
### 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`
|
||
- 读取最新 `threads` 行:model, 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
|
||
|
||
```typescript
|
||
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` 启发式推断 — 删除
|
||
|
||
**渲染层共享边界:**
|
||
- `pane-renderer` 和 `inline-renderer` 只共享 `layout.ts` 输出的 `string[]`(纯文本行)
|
||
- 不共享任何底层终端控制逻辑(scroll region、cursor、PTY 管道)
|
||
- 两者的终端假设完全不同,不应试图抽象出公共终端控制层
|
||
|
||
## 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
|
||
|
||
```bash
|
||
codex-hud install
|
||
```
|
||
|
||
执行:
|
||
1. 写 wrapper 到 `~/.codex-hud/bin/codex`(薄 shell 脚本 -> 固定绝对路径 `dist/cli.js`)
|
||
2. 在 shell rc 文件中用标记块前置 PATH:
|
||
```bash
|
||
# >>> codex-hud >>>
|
||
export PATH="$HOME/.codex-hud/bin:$PATH"
|
||
# <<< codex-hud <<<
|
||
```
|
||
3. 写 manifest.json
|
||
|
||
### 7.2 Manifest
|
||
|
||
```json
|
||
{
|
||
"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
|
||
|
||
```bash
|
||
codex --no-hud [args...]
|
||
```
|
||
|
||
直接 `execvp` 真实 binary,wrapper 进程替换,零开销。
|
||
|
||
### 7.5 Uninstall
|
||
|
||
```bash
|
||
codex-hud uninstall
|
||
```
|
||
|
||
- 删除 `~/.codex-hud/bin/codex`
|
||
- 删除 shell rc 中的标记块
|
||
- 删除 `manifest.json`
|
||
- 保留 `config.json`(用户数据)
|
||
|
||
### 7.6 Doctor
|
||
|
||
```bash
|
||
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. Pre-Implementation Spikes
|
||
|
||
> 这三个 spike 必须在写 implementation plan 之前完成。每个 spike 是独立的小脚本或手工验证,目标是锁定架构假设,不是实现功能。
|
||
|
||
---
|
||
|
||
### Spike 1: Pane Mode Capability
|
||
|
||
**目标:** 确认 tmux/zellij 下可靠分屏、在主 pane 跑真实 codex、退出后自动清理 HUD pane。
|
||
|
||
**验证方法:**
|
||
```bash
|
||
# 在 tmux session 内运行:
|
||
# 1. 用 display-message 探测 session 可操作性
|
||
tmux display-message -p '#{session_id}'
|
||
|
||
# 2. split 出 4 行底部 pane,在里面跑 watch 模拟 HUD
|
||
tmux split-window -v -l 4 'watch -n 0.5 date'
|
||
|
||
# 3. 在主 pane 跑真实 codex(或任意 full-screen TUI,如 vim)
|
||
codex
|
||
|
||
# 4. 退出 codex,验证 HUD pane 是否自动关闭
|
||
# 用 tmux kill-pane -t <pane_id> 从代码层面关闭
|
||
```
|
||
|
||
**通过标准:**
|
||
- `display-message` 在正常 session 下返回有效 session_id,在嵌套/只读 session 下可检测失败
|
||
- split pane 成功,HUD pane 内容独立于主 pane,主 pane codex TUI 不受干扰
|
||
- codex 退出后,`tmux kill-pane` 能可靠关闭 HUD pane,不留残留
|
||
|
||
**失败后的降级决策:**
|
||
- 探测不可靠 → pane mode 进入条件收紧,宁可误降级到 inline 也不误进 pane
|
||
- kill-pane 不可靠 → pane 生命周期改为"codex 退出时发送信号给 HUD 子进程,HUD 进程自行退出"
|
||
|
||
---
|
||
|
||
### Spike 2: Data Source Stability
|
||
|
||
**目标:** 确认 `state_5.sqlite` 和 `sessions/*.jsonl` 哪些字段在活动会话里稳定更新,哪些不可依赖。
|
||
|
||
**验证方法:**
|
||
```bash
|
||
# 1. 跑一个真实 codex session,同时在另一窗口执行:
|
||
|
||
# SQLite:每 2 秒看 threads 表最新行
|
||
watch -n 2 'sqlite3 ~/.codex/state_5.sqlite \
|
||
"SELECT model, cwd, git_branch, tokens_used, updated_at \
|
||
FROM threads ORDER BY updated_at DESC LIMIT 3"'
|
||
|
||
# SQLite:看 jobs/agent_jobs 表是否有更新
|
||
sqlite3 ~/.codex/state_5.sqlite ".tables"
|
||
sqlite3 ~/.codex/state_5.sqlite "SELECT * FROM jobs LIMIT 5"
|
||
|
||
# JSONL:tail 最新 session 文件,观察 type 字段分布
|
||
tail -f ~/.codex/sessions/$(date +%Y/%m/%d)/rollout-*.jsonl | \
|
||
python3 -c 'import sys,json; [print(json.loads(l).get("type","?")) for l in sys.stdin]'
|
||
```
|
||
|
||
**通过标准(按字段逐一确认):**
|
||
|
||
| 字段 | 预期来源 | 通过标准 |
|
||
|------|----------|----------|
|
||
| `threads.model` | SQLite | session 开始时写入,不频繁变化 |
|
||
| `threads.cwd` | SQLite | 准确反映当前工作目录 |
|
||
| `threads.git_branch` | SQLite | 与 `git branch --show-current` 一致 |
|
||
| `threads.tokens_used` | SQLite | 随对话进行递增 |
|
||
| `threads.updated_at` | SQLite | 每轮对话后更新 |
|
||
| JSONL `type` 字段 | JSONL | 每个事件都有 type,且值稳定可枚举 |
|
||
| JSONL tool/task 事件 | JSONL | 至少能识别 tool 调用开始/结束、task 进度 |
|
||
|
||
**失败后的降级决策:**
|
||
- `tokens_used` 不更新 → HUD 不显示 tokens,标 n/a
|
||
- JSONL 事件名不稳定 → normalize 层对所有未知 type 返回 null,HUD 对应字段降为 n/a
|
||
- `git_branch` 不准确 → 改为本地 `git branch --show-current` 补充
|
||
|
||
---
|
||
|
||
### Spike 3: Inline Fallback Safety
|
||
|
||
**目标:** 确认 `codex --no-alt-screen` 下能预留底部区域,且 wrapper crash/exit 后不会弄脏终端。
|
||
|
||
**验证方法:**
|
||
```bash
|
||
# 1. 确认 --no-alt-screen 模式下 codex 以滚动方式输出
|
||
codex --no-alt-screen
|
||
|
||
# 2. 用小脚本模拟 scroll region + 底部 HUD,然后正常退出和异常退出各跑一次
|
||
node -e "
|
||
process.stdout.write('\x1b[s'); // save cursor
|
||
process.stdout.write('\x1b[1;' + (process.stdout.rows - 4) + 'r'); // scroll region
|
||
process.stdout.write('\x1b[' + (process.stdout.rows - 3) + ';1H'); // move to HUD area
|
||
process.stdout.write('=== HUD TEST LINE ===');
|
||
process.stdout.write('\x1b[u'); // restore cursor
|
||
setTimeout(() => {
|
||
// cleanup
|
||
process.stdout.write('\x1b[r'); // reset scroll region
|
||
process.stdout.write('\x1b[' + process.stdout.rows + ';1H\x1b[2K'); // clear HUD
|
||
console.log('cleaned up');
|
||
}, 3000);
|
||
"
|
||
|
||
# 3. 验证 Ctrl-C 后终端是否正常(光标可见,滚动恢复)
|
||
# 4. 在不同终端宽度下测试(80, 120, 200 列)
|
||
```
|
||
|
||
**通过标准:**
|
||
- scroll region 设置后,主内容区域可滚动,底部 4 行固定不动
|
||
- 正常退出后终端恢复原始状态(光标可见,scroll region 清除)
|
||
- Ctrl-C / kill 后终端不残留 HUD 内容,不留隐藏光标
|
||
- macOS Terminal.app + iTerm2 + Ghostty 下行为一致(至少两种实测)
|
||
|
||
**失败后的降级决策:**
|
||
- scroll region 在任一目标终端不稳定 → inline mode 标记为"实验性",默认不启用,需 `--inline` flag 显式开启
|
||
- cleanup 不可靠 → cleanup 逻辑改为双重保险:正常路径 + `process.on('exit')` 兜底
|
||
|
||
---
|
||
|
||
**三个 spike 全部通过后,才进入 implementation plan。**
|
||
每个 spike 的结论(通过 / 部分通过 + 条件 / 失败 + 降级)应记录在 implementation plan 的前置说明中。
|
||
|
||
## 11. Confidence & Risk Assessment
|
||
|
||
### 10.1 已确认(可直接依赖)
|
||
|
||
| 项 | 依据 |
|
||
|----|------|
|
||
| `~/.codex/state_5.sqlite` 可读 | 本地实测,threads 表含 model/cwd/git_branch/tokens_used |
|
||
| `~/.codex/sessions/*.jsonl` 存在 | 本地实测,含 session_meta/event_msg/response_item/task_started 等 type |
|
||
| Codex 支持 `--no-alt-screen` | `codex --help` 已确认 |
|
||
| tmux `split-window` 分屏可行 | 标准 tmux 功能 |
|
||
|
||
### 10.2 待验证(normalize 层隔离,不写死)
|
||
|
||
| 项 | 风险 | 对策 |
|
||
|----|------|------|
|
||
| JSONL 事件字段名(task_started 等) | 可能随 Codex 版本变化 | normalize(raw) -> HudEvent \| null,字段名只在 normalize 内部出现 |
|
||
| SQLite 表结构(state_5.sqlite) | 表名含版本号,可能升级 | 启动时检测 `state_*.sqlite` 最新版本;schema 读取失败降级为 n/a |
|
||
| `threads.tokens_used` 精确语义 | 可能是 prompt tokens / completion tokens / 合计 | 命名为 tokensUsedTotal,UI 只展示原始数值,不做百分比推算 |
|
||
| context window usage | 当前未确认有原生来源 | 默认 n/a,不伪装 |
|
||
|
||
### 10.3 明确风险
|
||
|
||
| 风险 | 影响 | 缓解 |
|
||
|------|------|------|
|
||
| pane 生命周期管理 | 嵌套 session、退出时未关闭 pane、split 失败 | 双重探测进入条件;exit handler 强制关闭 pane |
|
||
| inline scroll region | 光标恢复失败、终端兼容性差异、resize 后撕裂 | 定位为 degraded fallback,开发优先级低于 pane mode;cleanup 必须覆盖所有退出路径 |
|
||
| JSONL 事件协议不稳定 | Codex 升级后解析失败 | normalize 层隔离,失败静默降级,HUD 其他部分不受影响 |
|
||
| SQLite schema 升级 | state_5 变成 state_6 | glob 匹配最新 state_*.sqlite,schema 不匹配时降级 |
|
||
| tokensUsedTotal 不等于 context | 用户可能误读为"剩余空间" | UI 只写 "tokens" 不写 "context",不画百分比条 |
|
||
|
||
## 11. Tech Stack
|
||
|
||
- Node.js 20+ / TypeScript
|
||
- `node-pty` (inline mode PTY)
|
||
- `better-sqlite3` (SQLite 读取)
|
||
- macOS + zsh 为首选目标
|