ci / test (push) Successful in 45m25s
- 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
19 KiB
19 KiB
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 sha256668bcd82…3125e);original/2026-10-27 自/tmp/codex-asar/webview原样 vendored(20918 文件,index.htmlsha85e5c5b4…),必须保持字节一致,改动只发生在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 没有对应表面可承载某功能时,本表如实写“原生无对应表面”,不自造页面/组件。
集成架构(结论先行)
- 线程共享是集成点:
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 附着逻辑。 - 入口替换方式:
GET /console改为按需 302 到 UI host(默认http://127.0.0.1:8095/,可配CODEX_UI_URL);UI host 不可达时回落到旧控制台。旧 React 控制台保留,新路由GET /console-classic继续 servecloud/console.html(理由见 §3)。 - original/ 不进仓:解压后 650MB / 20918 文件,不可提交。入仓的只有
host/适配层(约 104KB)与启动脚本;original/由CODEX_UI_ORIGINAL_ROOT指向已下载的栈目录,部署时以 zip sha256 校验(见 §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,不可达时回落旧控制台)。
理由:
- 旧控制台是 React 构建产物(
cloud/console-react/,46 个源码文件),承载 B 表里大量“原生无对应表面”的管理面(配额/管理、resident、automations、full-auto、审计、成员、待办等)。悄悄删除等于删除功能,违反“不删除较新工作”。 - 原生 UI 是单用户线程界面,无法承载多租户控制面;“替换”指的是默认入口,不是不留后路。
test_console.py(5 个测试)只验壳与路由,不跑浏览器 JS;路由改动须同步更新这 5 个测试(/console无 key 200 出 HTML 的断言要改为 302/回落行为,/console-classic承接原断言),并新增 UI host 相关测试。
§4 入仓清单(实现范围)
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在服务器上不存在)。
- 适配 A1–A4:
cloud/server.py:GET /console改为 302 到CODEX_UI_URL(默认http://127.0.0.1:8095/),2 秒探测失败回落旧控制台;新增GET /console-classicservecloud/console.html(原/console行为整体平移)。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}。- 测试:
cloud/tests/test_console.py按 §3 更新;新增cloud/tests/test_codex_ui_host.py(Python 侧:方法表计数 179、7 个仿真方法在 bridge 里存在、original/ 未被入仓——即仓内无cloud/codex-ui-host/original/、启动脚本存在且可执行)。 - 验证:
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 无法启动;这一步属于部署(须用户点头),不在本轮本地完成范围内。