host 962bb50a1c feat: browser stream waits through round gaps instead of disconnecting
Stale bridge state used to end the stream on the first stale read,
so every round gap and every worker restart killed the watch (the
2026-10-30 demo watch died this way when a 15:03 deploy restarted
the worker). Now a stale state switches to a waiting heartbeat
every 5s while the task is still alive (running/queued/paused/
interrupted), and frames resume when the next round's MCP starts
polling. The stream ends only on a terminal task state, a missing
task row, or 10 minutes of continuous staleness. The panel shows
a waiting status and keeps the last frame. Also force-close the
SSE connection on stream end: HTTP/1.1 keep-alive otherwise left
terminal streams hanging with no EOF.
2026-10-30 15:14:37 +08:00
2026-10-26 00:11:40 +08:00
2026-10-26 00:11:40 +08:00
2026-10-26 00:11:40 +08:00
2026-10-26 00:11:40 +08:00

Codex app-server(无 GUI 版,照搬开源核心 + 自建云端控制面)

目标分两层,不要混成一层:

  1. 不重写 agent 循环:把 Codex 开源能给到的全部原样接过来, 做成一个可以被任何客户端(pi agent、你自己的 App、脚本) 调用的 app-server。
  2. 自建控制面:在官方 agent 之上加一层常驻服务——项目、 任务队列、worker 池、评审与合并、权限与配额、成本与记忆、 网页命令台——让多任务、多用户、远程派发能真正跑起来, 而不是只停在本地单机演示。

项目现状(2026-10-29,main 93c25a9,PR #130 已合并部署)

旧版 README 只写到本地 host 层,云端还写着“演示、不是真实 云服务”——那句话已经过期。本轮更新以生产现状为准:

  • 生产已上线:控制面跑在常驻主机上,Postgres 为后端, 公网入口 https://api.diante.me,命令台 https://api.diante.me/console(GET /health 可查 backend=postgres、worker_ok 与当前 commit)。
  • 命令台是 React 版:源码在 cloud/console-react/(Vite + React 19 + TypeScript),构建产物是单文件 cloud/console.html, 只改 src 再 npm run build:console,产物绝不手改。 当前视觉是一页式 GitHub 风格(PR #129)+ 按宽度自适应分栏 (PR #130):细顶栏 + 6 张数据卡片 + 派发、任务、对话、项目、 环境、Workers、技能、自动化、分诊、管理区、API Key 全部铺在 一页;窄屏 <900px 一栏堆叠,中屏 900–1399px 两栏(左:任务 列表 + 项目,右:其余),宽屏 ≥1400px 三栏(左 310px、中弹性、 右 330px),页面最大宽 1560px,盒子按内容高度不拉伸。 此前 Muse 风格四栏固定布局已被它取代,不要再按旧截图改。
  • 执行层是 Docker + gVisor:生产任务走 Docker 执行器, 生产 autoscaler 起的 worker 带 --docker-runtime runsc;内层 codex 沙箱在生产容器里仍是 danger-full-access(见下“诚实边界”),隔离靠外层容器 + 租户目录 + egress 白名单,不是双层沙箱。
  • 模型走中转网关:生产默认模型已改为 gpt-5.6-terra (原 gpt-6.1-sol 在 2026-10-27 实测持续 503; 价目表与 /models 优先级列表未动,terra 成本进 unpriced)。 系统 key 与中转站是设计本身,不做 OpenAI 官方账号对接, 也不做 per-user 中转 key。
  • 已落地的控制面能力:OAuth 真实登录(GitHub/Google)、 项目 RBAC 与 API key 管理、任务队列与 worker 心跳、自动扩容 (协调员保护)、租户配额与 USD 上限、成本统计、自动记忆、 网页搜索 MCP(SearXNG 路)、任务继续消息持久化、子任务 默认模型/强度、计划模式、followup 与 fork、评审队列与 PR/CI 门。细节与 API 见 cloud/README.md 与 docs/。
  • 仍未证明 / 暂搁置的:40 人并行(2026-10-29 用户令先绕过, 串行分波次或 18 人规模代替,40 人仍记未证明);第二台主机的 真跨机 HA(现有 HA 验证是同主机双进程模拟);G10 断点 provider 修法只在 PR #127,未合并、未部署。

一句话架构

浏览器 / 客户端 / GitHub webhook
      │  HTTPS + X-API-Key(OAuth 登录、webhook 靠签名)
      ▼
cloud/server.py(控制面,默认 127.0.0.1:8765,生产经 nginx + TLS)
      │  项目 / 任务 / 队列 / 权限 / 配额 / 成本 / 记忆 / 评审
      ▼
cloud/worker.py + autoscaler(worker 池,local / docker 执行器)
      │  调 host.py:dispatch / run / goal / review / merge
      ▼
client.py ──► codex app-server(官方二进制,codex-core 原样)
      │  thread/start · turn/start · 事件流 · 审批请求
      ▼
agent 循环 + 沙箱 + 工具 + MCP + skills + 线程持久化

本仓库自己写的有三层:client.py(协议客户端)、host.py (本地宿主编排)、cloud/(云端控制面 + worker + 命令台)。 agent 循环、沙箱、工具执行、上下文压缩、线程存储,全部是官方 codex 二进制里的,不是我复刻的简化版。

实测版本:codex-cli 0.164.0-alpha.9,协议定义就是从它 导出的,见 protocol/。

目录

  • start.sh:启动官方 app-server,支持 stdio / unix / ws 三种传输。
  • client.py:零依赖 Python 客户端,演示完整链路。
  • run_parallel.py:宿主层多 agent 编排(见下文“子代理”)。
  • host.py:本地宿主编排(dispatch / review / merge / goal / automations / daemon / health,见下文“宿主编排”)。
  • cloud/:云端控制面(server.py 控制面、worker.py worker、 autoscaler.py 自动扩容、console.html 命令台产物、 console-react/ 命令台源码、sub2api.py 中转网关、 websearch_mcp.py 网页搜索 MCP、db.py SQLite/Postgres 双后端)。控制面本身零第三方依赖,只用 Python 标准库。
  • cloud/console-react/:命令台源码。改页面只改 src, 再 npm run build:console 生成 cloud/console.html。
  • deploy/:主机部署与运维脚本(deploy.py + host_deploy.sh, tar 与 git 直拉两种模式;deploy/README.md 为准)。
  • docs/:架构与专题文档(云端架构、自动记忆、网页搜索 MCP、 线程生命周期等)。cloud/README.md 里仍写着“本地演示、 不是真实云服务”的旧诚实边界已过期,以本 README 顶部的 生产现状为准。
  • config.example.toml:沙箱、审批、MCP、features 的配置示例。
  • protocol/:codex app-server generate-json-schema 原样导出的 协议 schema(v1 + v2),客户端的方法名和字段以它为准。
  • examples/subagent-prompt.txt:让主 agent 派生子代理的 prompt 模板。
  • examples/jobs.json:并行审查的 job 示例。
  • work/job-a、work/job-b:并行示例用的两个只读样例目录。

快速开始

1. 先确认登录(这一步不在开源里)

在你自己的机器上先跑一次 codex login(ChatGPT 登录), 或配好带 API key 的 model provider。否则会出现下面实测里 的状况:thread 能建、turn 能发,但模型调用返回 401, turn 以 failed 结束——这是登录问题,不是 app-server 问题。

2. 跑一回合

cd ~/workspace/codex-app-server
python3 client.py --prompt "修掉 README 里的错别字" \
  --cwd /path/to/your/repo \
  --sandbox workspace-write --approval-policy on-request

只看协议、不跑模型:

python3 client.py --list-threads
python3 client.py --thread-id <上次的 thread id> --prompt "继续"

3. 换传输方式

./start.sh --listen unix:///tmp/codex.sock
./start.sh --listen ws://127.0.0.1:8080
# 再用你自己的客户端连这个 socket / ws,而不是 stdio

4. 配置

把 config.example.toml 复制到 ~/.codex/config.toml 再改, 不要把登录信息、API key、你自己的 relay 地址写进本仓库。 沙箱三档:read-only / workspace-write / danger-full-access; 审批三档:untrusted / on-request / never。

开源照搬清单(全部来自官方 app-server)

能力 来源 说明
Agent 循环 codex-core 推理 → 工具 → 结果 → 再推理,直到 turn 完成
Thread 生命周期 app-server thread/start、resume、fork、archive、delete、list,事件历史持久化
Turn 与流式事件 app-server turn/start、turn/steer、turn/interrupt、turn/completed,agent 文本/工具事件流式回推
沙箱 codex-core macOS Seatbelt、Linux Landlock/bubblewrap;本机 VM 的系统 codex 缺 host 和 bwrap,工具会 fail closed,已用 bin/ 配对三件套修复,见 TEST-REPORT.md“工具执行修复”
审批 app-server 命令、改文件、权限、MCP 询问等请求回推给客户端裁决;也可配 approvalsReviewer=auto_review 让子代理先审
工具 codex-core shell、文件读写、搜索、改代码(apply_patch)、图片等
MCP / skills / 插件 app-server thread 内按需加载,features list 可看本版开关
AGENTS.md codex-core 全局、仓库根、子目录三层合并,thread 启动时自动读
上下文压缩 codex-core 长会话自动 compact,不用客户端自己截断
exec-server 拆分 codex-core 推理(app-server)与执行(exec-server)可分开部署,见 codex exec-server --help
Goal / 多 agent 开关 features 本版 multi_agent 已是 stable,codex features list 可验证

子代理:两层,不要混

内置子代理(开源已给)

主 agent 在 prompt 明确要求时派生 worker / explorer 等子代理, 各自独立上下文、并行跑、主 agent 汇总。模板见 examples/subagent-prompt.txt,要点是:分工写清楚、要求分开 列结果、最后合并去重、只列有证据的项。

限制(重要):子代理继承父 thread 的沙箱和审批设置, 不是独立权限边界。“只读评审”是指令约束,不是技术隔离。 真要隔离,改代码的 job 必须走下面一层。

宿主编排(开源没给,自己写,已给示例)

run_parallel.py 就是这一层:宿主并行开多个 thread, 每个 thread 一个独立工作目录(生产上用 git worktree), 最后宿主收集结果再汇总。桌面版“多线程指挥台”的感觉 主要就是这一层,不是 app-server 本身。

python3 run_parallel.py --jobs examples/jobs.json

示例默认是只读审查(read-only + never),要并行改代码时: 每个 job 换成各自的 worktree、改成 workspace-write, 事后逐个 review diff 再合并,冲突仍需人工处理—— core 里没有自动解决冲突的调度器。

云端控制面(cloud/,生产已跑,不再是演示)

本地 host.py 只管一台机器上的任务;多用户、远程派发、 排队、配额、评审门要靠 cloud/。它不是 host.py 的包装, 而是控制面只负责“谁可以派、派给谁、状态在哪、结果通知谁”, 执行仍走 worker 调 host.py,agent 逻辑不重写。

python3 -m cloud.server          # 默认 127.0.0.1:8765,生产经 nginx + TLS 对外
# 除 GET /health、/webhooks/*、/oauth/* 外,所有端点都要 X-API-Key 请求头
# 任务实时链路:GET /tasks/{id}/trace(快照)与 GET /tasks/{id}/trace/stream(SSE)
# 命令台:GET /console(每次现读 cloud/console.html,无需重启)
  • 任务状态机:queued → running → completed / failed / interrupted / merged / merge-failed;merged 只由 POST /tasks/{id}/merge 同步状态,不执行合并本身(合并仍由 host.py merge 执行,成功后再调本接口;仅 completed 可改 merged,重复调用 409,越权 403)。
  • worker 与扩容:worker 心跳注册(POST /workers/heartbeat), autoscaler 按队列扩缩;2026-10-29 已修协调员槽位泄漏 (PR #118:协调员完成不清 _running 曾导致容量门静默拦单, 40 人轮与 18 人轮的“认领停滞”都是它)与缩容误杀协调员 (PR #117)。长 goal 任务异常退出不再一律判 failed, 无结论的非 0 退出记 interrupted,断点在盘上可 goal-resume 续跑。
  • 权限、配额、成本:项目 RBAC、API key 只存 sha256 哈希 (明文只返回一次)、租户月度 USD 上限与建任务/分支/重试 准入、按任务与按项目/按日的成本(实际与估算分开)、 每 key 每分钟限流(环境变量可调,窗口与上限见部署 env, 不要把生产调的值当成代码默认值写进文档)。
  • 记忆与搜索:跨线程记忆与项目 KB(按项目隔离)、自动记忆 (生产默认账号已开,worker 每轮读库)、网页搜索 MCP (Perplexity → Exa → Tavily → Brave → SearXNG → DuckDuckGo 自动回退;Docker 任务容器不继承 worker 环境,搜索 env 须经 worker 的 extra_env 透传,见 PR #114)。
  • 继续、计划、followup、fork:继续消息已持久化进会话历史 (PR #126,此前失败任务只落任务记录、不进历史,网页重载后 看不到助手回复);建任务可带 plan(只读沙箱,不能与 approval=full 或 goal 同用);POST /tasks/{id}/followups (queued 排队不打断当前轮,direct 立即进链路且跑完当前轮后 优先于排队执行);POST /tasks/{id}/fork(仅终态任务可分支, 继承 executor/模型/强度/权限/计划,goal 字段置空)。
  • 模型与网关:子任务默认模型/强度(child_model / child_effort,PR #121);模型路由走 sub2api 中转网关, 断点命令的 model provider 须透传(PR #127 未合并,见顶部)。
  • 部署:deploy/deploy.py(tar 默认,--source git 走主机 直拉 GitHub;产物唯一命名 + 三道 sha/commit 门 + 换目录 代替原地解包,health 须 ok=true 且 backend=postgres 且 commit 对上才算成功;主干推送不自动部署生产,见 deploy/README.md)。部署前先确认无人同时在推,并发部署 会撞目录换位;部署完须核 4 个 unit 全 active。

宿主编排(host.py,无 GUI,对齐 ChatGPT / Codex 桌面版宿主)

ChatGPT / Codex 桌面版作为宿主,本质不是再写一个 agent,而是 管好这几件:线程、并行、隔离、委派、长任务、评审、自动化、 边界。host.py 就是这一层,一个 app-server 进程托管多个 thread, 后台读线程按 threadId 分发 turn/completed,所以多 thread 的 turn 可以真正并发(不是轮流排队)。

ChatGPT / 桌面版宿主能力 host.py 状态
多 thread 并行 host.py parallel --jobs …,一个 app-server 进程内多 thread 并发 已实测
一任务一 git worktree 隔离 dispatch 自动 git worktree add -b host/<task>,非 git 仓库退化为独立目录拷贝 已实测
diff 评审 host.py review <task>:status + diff + untracked 已实测
评审后合并 / 丢弃 merge:先在 worktree 提交,再 --no-ff 合并进主仓,冲突自动 abort;discard:删 worktree 和分支 已实测(host-b 已合并进主仓)
线程续跑 / fork / 归档 run(resume + 新回合)、fork、archive、list 已实测
宿主级子代理委派 delegate:子 thread(默认 read-only)跑完,结果可 --report-to-parent 汇总回主 thread 已实测
模型内子代理 prompt 明确要求派生,见 examples/subagent-prompt.txt;与宿主委派是两层,别混 已实测(模板 + 协议)
长任务 / 目标循环 goal:同一 thread 多回合,每轮落盘检查点,中断后 goal-resume 从下一轮续跑(goal-met / goal-unmet 终局后不许续跑,须新建);--turn-timeout 0 不限时;回合超时下一轮提示写明是回合中断不是验收失败。两种验收形态:旧形态 --verify 验收命令通过即达标,到 --max-rounds 则 unmet;新形态(console 打勾 goal 模式,goal_mode=1)不设验收命令、不设轮次上限(--max-rounds 0),prompt 本身就是目标,模型在回合末尾单独一行输出“目标已达成”即宣告达标,否则一直运行,停靠取消或预算(max_daily_turns 耗尽时记 interrupted、可续跑) 旧形态已实测:端到端、断点续跑、正常多轮反馈修复;新形态单测已过,真模型宣告达标尚未实弹
自动化 / 评审队列 automations --add + run-due:到期的任务自动派发,结果进 host-state.json 评审队列 已实测
沙箱边界 read-only thread 的写文件请求被拦下,文件确实没建出来 已实测
审批控制 untrusted + deny 时 agent 明确收到审批拒绝,没拿到命令输出 已实测
模型内子代理 3 子代理并行检查并汇总,视角是真的不同,不是重复一句话 已实测
中断 / 续跑控制 turn/interrupt 后状态为 interrupted;host.py interrupt --task 已接协议,直接中断不生效时写 <state_dir>/interrupt-requests/<task>.json,属主在等 turn 时每秒轮询到后用自己的 live 连接自中断(跨进程可靠路径);compact 需先 resume(host.py 的 compact 命令已包好这一步);超时回合也记 interrupted 且保留部分文本 单测已过(等待循环、请求路径、透传);真 server 跨进程中断尚未实弹,属主自中断逻辑与直接中断共享同一协议调用
长等待进度 等 turn 时每满 --progress-interval(默认 30 秒,0 关闭)往 stderr 打一行 turn progress: elapsed=…s 并往链路文件记一条 kind=progress(console 按 fallback 当 JSON 行展示);进度绝不走 stdout,不破坏 dispatch 的 JSON 与 goal 的结论行/用量行解析 单测已过(等待循环进度回调);真模型长等待下的进度尚未实弹
合并冲突处理 先合的进主仓,后合同一行的冲突 abort,主仓干净,冲突 worktree 留着人工解 已实测
自动化幂等 未到期的自动化 run-due 时不重复派发 已实测
宿主提供 MCP 工具 隔离 CODEX_HOME 注册 stdio MCP,agent 能发现并调用;注意 MCP 审批是 elicitation 协议,不是通用 decision 字段,host.py 已分流处理 已实测(修过一轮才通)
模型切换 同一 thread 内换模型续跑,turn 的 model 字段随之改变 已实测
运行中改向 turn/steer(expectedTurnId 校验)能把进行中的长任务改成短任务 已实测
3 并行 3 个 worktree 任务同时跑,全部完成且互不污染 已实测
未追踪文件评审 review 时 diff 为空也要看 untracked 栏 已实测
失败不崩宿主 不存在的模型导致 task failed 并带上游 404 错误,host 不崩 已实测
生产级项目闭环 真实仓:并行功能开发 + 测试套件验收 + 评审修复闭环 + 合并链,主仓从 2 个测试涨到 17 个全部通过 已实测
目标循环反馈 验收失败时把命令和失败输出喂给下一轮,否则 agent 会原地不动 已实测(修过一轮才通)
中断恢复 dispatch/delegate/goal/run-due 先落盘再跑 turn,中断后可用 run 续跑 已实测(委派超时后修)
fork 隔离 fork 建新 worktree + 拷工作区文件(跳过 .git)+ turn 级 sandboxPolicy 覆盖 read-only 重置 已实测(修过两轮才通)
跨进程 state 竞态 host-state.json 单文件,多 host.py 进程同时写会丢任务,同一进程内并行安全 已修:锁文件 + 保存时合并,实测双进程并发保存不丢
项目配置 每次都要传 model/repo/state 等参数 已修:host-config.json + init/config,CLI 优先于配置,密钥只走环境变量
定时自动化 run-due 要手动跑,跑完没日志 已修:host-run.log 记录派发/跳过,automations --cron-line 生成 crontab 行,--install-cron 才装机
合并后自动验测 + 回滚 merge 合完不验主仓测试,合坏了靠人发现 已修:merge 默认仓库有 tests/ 就跑 unittest,验测失败 reset 回合并前 HEAD,分支/worktree 保留;--no-verify 跳过,--verify 可指定命令
任务依赖 + 优先级 parallel 是扁平的,没有依赖和优先级 已修:jobs 里 depends_on/priority,拓扑排序 + 按波次跑,依赖失败时依赖任务标 skipped 不跑;依赖任务从依赖分支建 worktree(有血缘关系),不是文件拷贝
失败自动重试 失败任务就停在 failed,不重试 已修:dispatch/parallel 里 --max-retries,失败后同 prompt 再跑,重试前查预算;默认 0,行为不变
评审评论回流 评审只看 diff,修要另起 run prompt,评论没地方钉 已修:review-comment --file --line --comment 把评论存进任务,review 时列出 open 评论,fix-review 按 open 评论逐条修,修完标 resolved
server 崩溃重连 app-server 死了,host 就 BrokenPipe 失败 已修:Server.ensure_alive,死了重拉并 resume thread(thread 在 CODEX_HOME 里不丢);request 遇 BrokenPipe 重试一次;单测 kill 进程后重拉 + list 能通
成本预算 没有预算,模型跑多少全靠自觉 已修:配置 max_daily_turns,按 turn 数算(协议不暴露真实 token 数,这是诚实边界),超预算不派发/不重试;usage 记在 state 里,doctor 可看
失败通知 失败只有 state 里的 status,跑完没人知道 已修:失败时写 host-run.log + 跑配置的 notify_command(本地 shell,模板 {task} {status});failures 命令列失败任务,doctor 列失败/缺失 worktree/open 评论
多机器 state 冲突 锁只防同一台机器,多机器共享 state 文件会静默覆盖 部分修:state 加 version/last_writer/updated_at,保存时磁盘版本比加载时新就记 STATE-CONFLICT 日志并合并保留双方任务,不静默丢;真多机同步仍需中心服务(见下“没对齐”)
合并时提交缓存文件 merge 的 git add -A 会把 pycache/*.pyc 也提交进主仓,下次合并被 .pyc 改动挡住 已修:merge 前清 worktree 的 pycache/*.pyc,主仓的 .pyc 改动先还原;单测覆盖 pyc 不入库
守护进程 + 健康检查 + 自动恢复 host.py 跑完就退出,不常驻;state 坏了静默当空 state;中断任务一直停在 running 已修:daemon(双 fork 到 PID 1,循环 recover + run-due)、health(不跑模型,查二进制/配置/state/server/daemon)、restore-state(坏了从 .bak 恢复并留底)、recover(超过阈值的 running 标 interrupted,可用 run 续跑,不自动重跑模型回合)
GitHub PR / CI 一条线 本地 merge 完就完,没有 PR、没有 CI 门 已修(代码 + 本地可测部分):github-workflow 生成 .github/workflows/host-verify.yml(checkout + python + unittest,幂等);pr(push 分支 + gh pr create,成功后 pr_url/pr_number 写回任务记录);pr-checks(解析 gh pr checks,all_pass 仅全 pass 为真,空 checks 不算过);pr-merge(先查 checks,未全过拒绝且不调 merge,全过才 gh pr merge --merge 并标 merged,不动本地主分支)。gh 调用走 run_gh(),单测用假 gh。本仓的真实 PR 链路现已走通,见下“GitHub PR/CI”一节。
边界:cwd、沙箱、审批、模型 dispatch / turn 参数里定,见 config.example.toml 已实测
任务登记 host-state.json:task -> thread/worktree/状态/diff;agent 状态本身仍在 CODEX_HOME,不另存第二份 已实测

没对齐的部分(诚实说,不是偷工):

  • 云端派发、推送、GitHub 触发:本地 host 层仍没有,run-due 要靠你自己的 cron 定时调,才等于“定时自动化”;但云端控制面 层已经有了(见上文“云端控制面”):队列派发、webhook 入站、 PR/CI 门、推送订阅与分发。Slack 触发未对真实 Slack 服务器 实测,不要把它写成已验证。
  • GUI 的 inline 评论、side-by-side diff、点按钮批准:无 GUI 版 本来就不要这层,评审就是 git diff 文本 + merge/discard 命令。
  • Computer Use:另一条管线,不在宿主编排里。
  • 并行改同一文件:worktree 能防互相踩,但两个分支都在文件末尾 追加时,合并第二支仍可能冲突,host 会 abort 等人工解冲突, 不会自动猜合并结果——这和桌面版一样,是正确做法,不是 bug。 2026-10-26 补了一层安全网:merge 冲突时先试 -X ours,解完 后逐行核对任务分支的新增行是否都在主仓里,缺一行就回滚 (gap2 实测拦下了一次 -X ours 丢 mul2 的情况);依赖任务 现在从依赖分支建 worktree,依赖先合、依赖任务后合时不再 重复加同一段。
  • 多机器同步:本地 host-state 仍是单机文件(有 version 和 冲突日志,不会静默丢任务),真多机同步要走云端控制面 的中心数据库(Postgres),不在本地 host 的范围里; 同主机双进程模拟过的 HA 不等于第二台主机仍存活。
  • 守护进程的边界:daemon 本身也活不过 VM 平台回收(和 微信桥 serve 一样,回收后要重拉),所以 VM 上更稳的定时 仍是 Muse cron 定时调 run-due/health,而不是只靠 daemon 或 crontab;daemon 适合常驻机器,VM 上是“会话内常驻 + 回收后重拉”的定位。

宿主快速开始(最小闭环)

下面全部是 host.py --help 核对过的真实子命令和参数, 不要凭印象编命令。先装检查,再走闭环;不要在示例里 直接派发真实模型任务来“试一下”——dispatch 一步就会跑模型。

cd ~/workspace/codex-app-server
./install.sh                    # 只检查 python3、git、codex,不写密钥、不跑模型
python3 host.py health          # 不跑模型:查二进制、配置、state、server、daemon
python3 host.py init --repo /path/to/your/repo
# 上一步写 host-config.json(CLI > 配置 > 默认;key 绝不进配置文件,只走环境变量)
python3 host.py dispatch --name example-task --task "你要做的事" --repo /path/to/your/repo
# ^ 这一步跑模型:需要模型 key 已走环境变量提供,且会建 git worktree host/example-task
python3 host.py review --task example-task   # 看 status + diff + untracked + open 评审评论
python3 host.py merge --task example-task    # 默认仓库有 tests/ 就跑 unittest,验测失败自动回滚
# 合并不了就别硬合:python3 host.py discard --task example-task  # 删 worktree 和分支
python3 host.py daemon --interval 300        # 双 fork 常驻:每轮 recover + run-due
python3 host.py daemon-status
python3 host.py daemon-stop

三条边界,闭环前先知道:

  • health 不跑模型、不花额度,随时可跑;daemon-status / daemon-stop 同样不跑模型。
  • daemon 在这台 VM 上活不过平台回收(和微信桥 serve 同理): 回收后要重拉;VM 上更稳的定时是 Muse cron 调 health / run-due,而不是只靠 daemon 或 crontab。
  • run-due 到期会跑模型、会花额度:先用 python3 host.py automations --add --name <名> --task "<任务>" --repo <仓> --interval 86400 登记自动化,再确认模型 key 已走环境变量提供,才让定时去调 python3 host.py run-due;未到期的自动化 run-due 时不重复派发。
  • 云端控制面另起一端(生产上 worker 会跑真实模型回合, 不再是“演示、不跑模型”): python3 -m cloud.server(默认 127.0.0.1:8765), 除 GET /health、/webhooks/*、/oauth/* 外都要 X-API-Key 请求头。 任务实时链路:GET /tasks/{id}/trace(已结束任务的快照)与 GET /tasks/{id}/trace/stream(运行中任务的 SSE:先 snapshot 再逐条 entry,终态发 done 关流;命令台详情的链路区用 fetch 流式读它, EventSource 不能带 X-API-Key 头故不用)。 新业务链路见上文“云端控制面”:plan、followups (queued/direct)、fork,以及继续消息持久化。

GitHub PR/CI:本仓已走通,旧“无远端、无登录”已过期

旧版这里写着“本仓没有 git remote、gh 未登录、真实 PR 没跑”—— 那是 2026-10-25 左右的快照,现已过期。本仓现为 github.com/openqj/codex-app-server,PR → CI(host-verify) → --merge 合并 → deploy.py git 模式部署已是常规链路 (例如 #124 React 命令台、#125 Muse 风格、#126 继续消息持久化、 #128 一页式排版、#129 GitHub 风格、#130 自适应分栏,均经此链路上线生产)。 不要再拿旧段落去推断本仓的 PR 能力。

但要分清两条线:

  • 本仓的发布链路:分支 → PR → CI 全过 → 合并进 main → git 模式部署到生产主机,部署须验 health 与 commit 一致。
  • host.py 的 pr/pr-checks/pr-merge:那是给任意目标仓库用的 本地命令,仍要求目标仓有 remote 且运行机器上 gh 已登录; 没有登录时它会把 gh 的错误原文打印出来,不编造 PR URL。 不要把 PAT 写进任何文件、state、日志或聊天记录;需要登录时 走 Secure Vault。
  • 流程(远端和登录齐了之后):
    1. host.py github-workflow 生成 CI workflow(已生成的文件 要提交进仓库,GitHub Actions 才会跑它);
    2. 任务 completed 后 host.py pr --task <名>:push 分支 + gh pr create,pr_url/pr_number 写回任务记录;
    3. host.py pr-checks --task <名>:看 CI 全过没有;
    4. host.py pr-merge --task <名>:全过才合并,合并由 GitHub 完成,不动本地主分支——和本地 merge 命令是两条 线,不要混用:本地 merge 是直接合进本地主仓,PR merge 是走 GitHub 的 PR 流程。
  • 两条线不冲突的原因:本地 merge 用 merge_task()(自动验测
    • 回滚),PR merge 用 pr-merge(checks 门 + GitHub 合并); 一个任务要么走本地 merge,要么走 PR,不要两边都合。

开源没给到的(别指望照搬)

  • 模型本身、ChatGPT 登录、计费、Codex Cloud 云端沙箱。
  • GitHub / Slack 等渠道的托管派发:云端层已有 webhook 入站、 推送订阅与自动 PR,但 Slack 触发未对真实服务器实测, 托管派发不等于 ChatGPT 托管服务。
  • 桌面版的 inline 评论、side-by-side diff、点按钮批准: 命令台有评审队列与对话时间线,但评审仍是 git diff 文本 + merge/discard 命令,不是桌面版评审 UI。
  • Computer Use(屏幕操作)是另一条管线,不在 CLI 中间层里; 本版 features 里虽然有 computer_use 开关,但那不是 “复制 app-server 就自动得到一个桌面操作 agent”。

诚实边界(2026-10-29,更新 README 时仍成立的)

  • 内层沙箱:Docker 容器嵌套 pidns 下 bwrap 的 fresh proc mount 被内核拒绝(宿主级 + 带 userns 的 AppArmor profile 才能跑通 workspace-write),所以生产是 Docker 隔离 + 容器内 danger-full-access,不是双层沙箱;宿主级 workspace-write 下 agent 只能写自己 worktree,但 /tmp 不隔离,写 /tmp 会落到 宿主 /tmp 上。
  • 并行规模:18 人并行已探过,40 人并行未证明且已暂搁置; 需要 40 人规模时走串行分波次或拆小批次,不要把 18 人的通过 写成 40 人的通过。
  • HA:多机 HA 0→4 的跨机部分是同主机双进程模拟验证, 真“杀一台主机仍存活”需要第二台 VPS;VM 会被平台定时回收, 不能作 HA 节点。
  • 未合并的在途工作:G10 断点 provider(PR #127)只推了 PR, 未合并、未部署;不要在 README 里把它写成已上线。
  • 密钥:API key、OAuth secret、中转 key 只走环境变量或 Secure Vault,绝不写进仓库、state、日志或聊天记录; key 明文只在签发时返回一次。

安全

  • 常驻服务不要用 danger-full-access,也不要把 --dangerously-bypass-approvals-and-sandbox 写进启动脚本, 那是给外部已经沙箱化的环境准备的。
  • 审批策略默认:自动化用 never + workspace-write 或更严; 交互使用再用 on-request,由客户端(人)逐条裁决。
  • client.py --approval auto 会放行所有审批请求,只用在 你完全信任的仓库和一次性任务里,默认是 deny。
  • ws 传输对外暴露时务必配 --ws-auth,不要裸奔在公网。

本机实测记录(2026-10-25)

隔离 CODEX_HOME(不污染你真实的 ~/.codex)下跑:

python3 client.py --codex-home /tmp/codex-client-test \
  --cwd work/job-a --sandbox read-only --approval-policy never \
  --prompt "只用一句话回答:2+2等于几?不要调用任何工具。"

结果:

  • initialize 成功,返回 codexHome / platformOs。
  • thread/start 成功,拿到 thread id,model 为默认模型。
  • turn/start 成功,收到 turn/started、userMessage 事件。
  • 模型调用处失败:401 Unauthorized: Missing bearer token, 随后 turn/completed 状态为 failed。 原因:隔离目录里没有任何登录/provider 密钥,且默认 provider 指向 api.openai.com。这是预期内的登录边界, 不是协议或客户端 bug。在你已登录的机器上同一条链路 会直接出结果,不需要改代码。

完整输出见 TEST-REPORT.md。

S
Description
codex-app-server, imported from GitHub main, with Gitea CI
Readme
9.4 MiB
0 Stars 1 Watchers 0 Forks
Languages
Python 87.8%
HTML 6.7%
TypeScript 3.8%
Shell 1.1%
CSS 0.5%