Files
codex-app-server/docs/codex-native-ui-alignment.md
T
host 3e0ff898e0
ci / test (push) Successful in 45m25s
feat: wire native Codex UI as /console entry, keep classic console at /console-classic
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB)
- Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29
  experimental-protocol methods (150 -> 179), Linux binary candidates,
  read-only POST /__codex_git
- GET /console 302s to the UI host (:8095) when reachable, else falls back
  to the classic React console; GET /console-classic always serves it
- Alignment tables: docs/codex-native-ui-alignment.md
- Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
2026-11-06 19:03:35 +00:00

19 KiB
Raw Blame History

Codex 原生 UI ↔ codex-app-server 双向对齐表

  • 基线:Gitea main 37ba6876bf326ebd6b1e01ae7d2c5684dafe439b(2026-11-06,线上 code.tose.top 生产基线)
  • 工作区:~/workspace/codex-app-server-codex-ui,分支 feat/codex-native-ui
  • UI 栈:~/workspace/user/downloads/codex-ui-stack/(zip sha256 668bcd82…3125e);original/ 2026-10-27 自 /tmp/codex-asar/webview 原样 vendored(20918 文件,index.html sha 85e5c5b4…),必须保持字节一致,改动只发生在 host/ 响应层与 app-server 侧
  • 清点依据:A(UI host 清点)、C(codex 0.164.0-alpha.9 全量 150 方法实测探活)、D(37ba687 基线重核:控制面 179 个端点,控制台为 cloud/console-react/ 源码构建的 353KB React 包)。B 路按旧分支 5ba4d35 清点的数字(60 端点、820 行 console、resident 为 0)在本基线全部作废,未采用。
  • codex 二进制:codex-cli 0.164.0-alpha.9(/opt/hatch-image/bin/codex)
  • 约定:凡原生 UI 没有对应表面可承载某功能时,本表如实写“原生无对应表面”,不自造页面/组件。

集成架构(结论先行)

  1. 线程共享是集成点:host.py 一个 app-server 进程托管多 thread,线程状态持久化在 CODEX_HOME(默认 ~/.codex)。UI host 的 app-server-bridge.mjs 自己 spawn 一个 app-server,但因共享同一 CODEX_HOME,thread/list 能看到 host.py 任务的 thread(C 实测:thread/list 返回了既有 thread)。因此 UI 不需要直连 host.py 的 app-server 进程,也不需要新增 socket/ws 附着逻辑。
  2. 入口替换方式:GET /console 改为按需 302 到 UI host(默认 http://127.0.0.1:8095/,可配 CODEX_UI_URL);UI host 不可达时回落到旧控制台。旧 React 控制台保留,新路由 GET /console-classic 继续 serve cloud/console.html(理由见 §3)。
  3. original/ 不进仓:解压后 650MB / 20918 文件,不可提交。入仓的只有 host/ 适配层(约 104KB)与启动脚本;original/ 由 CODEX_UI_ORIGINAL_ROOT 指向已下载的栈目录,部署时以 zip sha256 校验(见 §4)。
  4. 模型走 relay,不走 mock:栈自带的 relay-config.json 里 baseUrl 指向本地 mock(http://127.0.0.1:8095/mockrelay/v1),真实 Bumian 地址只写在 JSON 里、host 代码无引用,apiKey 为空。当前配置下模型调用走确定性 mock 替身。接真实 relay 须把 baseUrl 改为 relayBaseUrl 并经环境变量给 key(只写 envKey 名,key 值绝不入文件)。

A 表——UI 要、codex-app-server / 官方 app-server 缺的

# UI 需要的方法/功能 缺口现状(实测) 补齐方案与落点
A1 thread/stop 官方 0.164.0-alpha.9 不存在(unknown variant,C 实测;实验 schema 枚举 172 方法里也没有它) 桥接层仿真:app-server-bridge.mjs 拦截 thread/stop,转调 turn/interrupt(host.py:939 已确认:对齐 Codex App 停止按钮就是发 turn/interrupt)。落点:入仓的 cloud/codex-ui-host/host/app-server-bridge.mjs
A2 thread/rollback 同上,不存在 桥接层仿真:转调 thread/revert(实测存在)。同上落点
A3 thread/startAeon 同上,不存在 桥接层仿真:转调 thread/start(Aeon 为实验模式,本二进制无此入口;降级为普通 start,并在方法注释里写明)。同上落点
A4 interactive/liveSessions/list、interactive/session/upload、interactive/sessionSandbox/list、interactive/sessionSandbox/read(4 个) 同上,均不存在 桥接层仿真:list 类回空列表、upload/read 回结构化 -32601 method not found (interactive sessions not available in codex 0.164.0-alpha.9)。不伪造会话数据。UI 的 live-session 面若调用,即表现为“无 live 会话”,不静默成功。落点同上
A5 实验协议 29 个方法(UI 清单漏列,二进制实测 schema 里有) UI 清单只有 150 个;local-host.js 按清单动态生成 services,漏列的 29 个 UI 调不到。29 个:account/bedrock/checkGovCloudRequirements、account/sendAddCreditsNudgeEmail、account/workspaceMessages/read、`command/exec/resize terminate
A6 桥接在 Linux 上找到 codex 二进制 app-server-bridge.mjs 的默认候选只有 $CODEX_APP_SERVER_BIN 与两个 macOS ChatGPT 内路径;在 Linux 上不设环境变量即报 codex binary not found(C 实测) 入仓的 bridge 增加候选:$CODEX_BIN(host.py 同款环境变量名)→ /opt/hatch-image/bin/codex → PATH 里的 codex。落点同 A1
A7 UI 要 git.request("stable-metadata")(Send 门控靠它非空) local-host.js 里 git 是 stub,固定回 {available:false, …},其余回 null 桥接层补只读 git 能力:local-host.js 的 git.request 改为调 host 新增端点 POST /__codex_git(只接受 stable-metadata / branch / status 三种只读 op,在给定 cwd 内跑 git 只读命令,拒绝写操作与 cwd 外路径)。落点:入仓的 cloud/codex-ui-host/host/local-host.js + web-server.mjs
A8 UI 要终端面 local-host.js:274 把 terminal 服务显式置 null;进程类能力只剩 app-server 的 process/* / thread/shellCommand 不补终端面:原生 UI 的终端服务在本栈是 stub,app-server 的 `process/spawn
A9 UI 要账号 / 用量 / 远控 account/rateLimits/read、account/usage/read、plugin/share/list、`remoteControl/enable disable
A10 UI host 无鉴权 vs 控制面要求 X-API-Key UI host 全部端点同源、无鉴权,只绑 127.0.0.1:8095;控制面除 /health、/console、/oauth/*、/webhooks/* 外一律要求 key 不给 UI host 加自造登录面:保持 loopback-only 绑定;/console 的 302 只发生在控制面已鉴权(或本机回环)的请求之后——实现方案:cloud/server.py 的 /console 在 302 前先做一次 UI host 健康探测(GET / 2 秒超时),探测失败则回落旧控制台;UI host 本身不暴露到外网。密钥不参与 UI host 鉴权
A11 UI 要 config/read 含 relay 覆盖、model/list C 实测均 ok;config/read 返回 model=gpt-6.1-sol、model_provider=openkun(与本机 ~/.codex/config.toml 一致);model/list 仅一条 无缺口:桥接的 -c 覆盖(relayArgs)保持原样;verify.mjs --deep 第 13 项即校验此点。实现不改动

B 表——app-server 有、UI 缺的(控制面 179 个端点 → 原生 UI 表面)

控制面分组(D 实测,179 = 任务 66 + 项目/环境/上传 17 + 租户配额/管理 11 + OAuth/身份/key 14 + GitHub App/webhook 7 + resident 7 + automations 10 + 记忆/KB/成本/推送 23 + worker/监控/模型 10 + 其它 full-auto 9 + todos 5):

# 控制面功能(端点) 原生 UI 对应表面 结论
B1 任务创建 / 列表 / 详情(POST /tasks、GET /tasks、GET /tasks/{id}) 线程列表(thread/list)与线程阅读(thread/read、thread/items/list)是线程,不是任务;任务含 prompt/执行器/模型/goal/分支/worktree 等宿主字段,原生 UI 无任务队列面 原生无对应表面(任务列表、建任务表单)。任务的线程内容可经 thread/read、thread/items/list 看到,属部分映射,不等同
B2 任务操作:取消/暂停/重试/合并/发布/提 PR/分叉/继续(POST /tasks/{id}/{cancel,pause,retry,merge,release,pr,fork,continue}) 取消→turn/interrupt(+ A1 的 thread/stop 仿真);重试/继续→thread/resume、thread/start;分叉→thread/fork 部分映射:cancel/retry/fork/continue 有方法级对应;pause/release/merge/pr 原生无对应表面(合并与 PR 是宿主 Git 流程,原生 UI 无此概念)
B3 任务 trace(GET /tasks/{id}/trace、/trace/stream SSE)、时间线、成本、结果全文、agents、文件/目录树(/files、/tree) thread/timeline/list(trace 的部分映射)、thread/items/list(内容)、fs/readDirectory、fs/readFile(文件浏览的部分映射) 部分映射:trace/时间线/文件有近似方法;成本(GET /tasks/{id}/cost)、agents 分工、SSE trace 流 原生无对应表面
B4 任务线程管理(/threads、/thread、/thread/{rename,archive,unarchive,compact,message}、/chat-history、/messages、followups、reviews、approvals、handoff、refs、worktree、audit-loop、goal、sleep/wait) thread/name/set、`thread/archive unarchive、thread/compact/start、thread/items/list、review/start、thread/approveGuardianDeniedAction`
B5 浏览器远控(/tasks/{id}/browser/{state,stream,input,control}) 无。原生 UI 的浏览器面是它自己的渲染面,不接管外部浏览器 原生无对应表面
B6 项目与成员(POST/GET /projects、PATCH /projects/{id}、/projects/{id}/members) `project/list read
B7 环境(POST/GET /environments、/environments/{id}/archive) environment/info 是单机执行环境信息,不是“项目仓库命名基准检出” 原生无对应表面
B8 租户配额 / 管理(GET /users/me/quota、/admin/quotas、/admin/sweep、/admin/backup、/admin/audit-ledger/*、/admin/push/dispatch、/admin/cost-by-creator、/admin/ops/approval-timeout、/users/me/cost) account/usage/read、account/rateLimits/read 需 ChatGPT 登录且不可用(A9);其余无 原生无对应表面
B9 OAuth / 身份 / API key(/oauth/{provider}/*、/users/me/keys、/users/me/profile、/users/me/gateway/rotate、/github-app/*) account/login/start、account/logout 存在,但本栈 mode-decide 改写跳过登录直进 app,账号面实际不可达;key 管理无 原生无对应表面(OAuth 与本站 key 管理)
B10 GitHub webhook / Slack webhook(`POST /webhooks/github slack)、GitHub 仓库列表(GET /github/repos`) 无
B11 resident 常驻监听(7 个端点,POST/GET/PATCH /projects/{id}/resident-watches、pause/resume/end) 无。控制台 console-react/src/ 全文 0 处 resident,连旧控制台都没有这个面 原生无对应表面
B12 automations 与分诊(10 个端点,/projects/{id}/automations、bind/heartbeat/cancel、/triage) 无。automations.py 的自动化是云端定时建任务;原生 UI 无自动化面(host.py 的本地 automations 是另一套) 原生无对应表面(bind/heartbeat 连旧控制台都没调)
B13 full-auto(9 个端点,/full-auto/{status,config-plan,validate-action,merge-gate,ci-fix,release-plan,rollback-plan,execute,audit}) 无 原生无对应表面
B14 项目记忆 / KB / 成本 / 推送(23 个端点,/projects/{id}/{memory,kb,cost,push/subscriptions}、/users/me/memories、/users/me/identity-context、/users/me/auto-memory*) `memory/status reset 是 codex 本地记忆状态,不是项目记忆库;skills/list 与项目技能清单(GET /projects/{id}/skills`)部分对应
B15 技能 / 应用 / MCP(GET /projects/{id}/{skills,apps,mcp-servers}、`POST …/mcp-servers/reload oauth-login`) skills/list、`plugin/list
B16 worker / 监控 / 模型(GET /workers、/workers/heartbeat、/metrics、/metrics-report、/audit-scorecard、/routing-shadow、/models、/relay/models) model/list、modelProvider/capabilities/read 覆盖模型面;其余无 部分映射:模型列表有对应;worker 池、指标、评分卡、路由影子 原生无对应表面
B17 待办(5 个端点,/projects/{id}/todos) 无 原生无对应表面
B18 模型总览与中转模型列表(GET /models、GET /relay/models) model/list(C 实测仅一条 gpt-6.1-sol) 部分映射:单模型列表有对应;中转站多模型下拉无原生面(旧控制台用 /relay/models 做模型下拉,新 UI 用 model/list)

§3 旧 console.html 的去留决定

决定:保留旧控制台,改路由为 GET /console-classic,GET /console 改为新 UI 入口(302 到 UI host,不可达时回落旧控制台)。

理由:

  1. 旧控制台是 React 构建产物(cloud/console-react/,46 个源码文件),承载 B 表里大量“原生无对应表面”的管理面(配额/管理、resident、automations、full-auto、审计、成员、待办等)。悄悄删除等于删除功能,违反“不删除较新工作”。
  2. 原生 UI 是单用户线程界面,无法承载多租户控制面;“替换”指的是默认入口,不是不留后路。
  3. test_console.py(5 个测试)只验壳与路由,不跑浏览器 JS;路由改动须同步更新这 5 个测试(/console 无 key 200 出 HTML 的断言要改为 302/回落行为,/console-classic 承接原断言),并新增 UI host 相关测试。

§4 入仓清单(实现范围)

  1. cloud/codex-ui-host/:自 ~/workspace/user/downloads/codex-ui-stack/ 拷入 host/ 全部 12 个文件 + start.sh + verify.mjs(原样,改动只在下述适配处;original/ 不入仓)。
    • 适配 A1–A4:app-server-bridge.mjs 增加 7 个缺失方法的仿真分支。
    • 适配 A5:appserver-methods.json 追加 29 个方法(150 → 179)。
    • 适配 A6:app-server-bridge.mjs 增加 $CODEX_BIN、/opt/hatch-image/bin/codex、PATH 候选。
    • 适配 A7:local-host.js 的 git.request 走新增 POST /__codex_git(只读三 op);web-server.mjs 增加该端点。
    • 适配:relay-config.json 保持栈自带 Bumian 配置原样(apiKey 为空),另加 relay-config.README.md 说明 mock 现状与接真实 relay 的两步(baseUrl 改为 relayBaseUrl + 设 BUMIAN_API_KEY 环境变量);不换 provider、不写 key。
    • 适配:projectless.mjs 的默认工作区根在 Linux 上改为 $CODEX_WEB_WORKSPACE_ROOT 或 ~/Codex(原 ~/Documents/Codex 在服务器上不存在)。
  2. cloud/server.py:GET /console 改为 302 到 CODEX_UI_URL(默认 http://127.0.0.1:8095/),2 秒探测失败回落旧控制台;新增 GET /console-classic serve cloud/console.html(原 /console 行为整体平移)。
  3. scripts/run-codex-ui.sh(新建):启动 UI host 的唯一入口,设置 CODEX_APP_SERVER_BIN、CODEX_UI_ORIGINAL_ROOT(默认已下载的栈目录),exec node cloud/codex-ui-host/host/web-server.mjs --port ${UI_PORT:-8095}。
  4. 测试:cloud/tests/test_console.py 按 §3 更新;新增 cloud/tests/test_codex_ui_host.py(Python 侧:方法表计数 179、7 个仿真方法在 bridge 里存在、original/ 未被入仓——即仓内无 cloud/codex-ui-host/original/、启动脚本存在且可执行)。
  5. 验证:python3 run_tests.py cloud 相关子集 + node verify.mjs(shallow)+ node verify.mjs --deep(真实 app-server 可用时)+ 真实联调冒烟(起 :8095,thread/list 与等价调用,日志为证)。

§4.1 实现注记(实现后补记,与规范同等效力)

  • A5:appserver-methods.json 是纯 JSON,加文件头注释会破坏合法性,故注释未写进 JSON 本身;“realtime/* 与 userVerification/* 仅 schema 有名、未逐个探活”记在这里(本节),与 A5 表格同效。
  • A4 口径:以本表 A4 为准——list 类(interactive/liveSessions/list 与 interactive/sessionSandbox/list)回空列表;upload 与 sessionSandbox/read 回结构化 -32601。实现初版曾把 sessionSandbox/list 划入 -32601 组,协调员已按本表纠正。
  • web-server.mjs 的 --root 默认值增加了 CODEX_UI_ORIGINAL_ROOT 回落(1 行):不加则启动脚本设该变量无效;接受该改动,启动脚本无需改传 --root。

§5 剩余缺口(实现后仍存在,如实记录)

  • B 表里标记“原生无对应表面”的功能(B1 任务队列、B6 项目制、B7 环境、B8 配额/管理、B9 OAuth/key、B10 webhook、B11 resident、B12 automations、B13 full-auto、B14 项目记忆/KB/成本/推送大部、B17 待办、B5 浏览器远控)没有原生界面承载,仍走 /console-classic(旧 React 控制台)。这是按用户要求不自造界面的直接结果,不是遗漏。
  • A9(ChatGPT 登录类方法)不可补;A8 终端面待验证阶段核实。
  • original/ 不进仓:部署时须另行提供与 zip sha256 一致的 original/ 目录,否则 UI host 无法启动;这一步属于部署(须用户点头),不在本轮本地完成范围内。