- DockerExecutor: extra mounts (host.py work/state, repo, codex-home
at same paths), worker --docker-extra-mounts, container user
configurable; Dockerfile copies codex-resources beside the codex
binary and puts bwrap on PATH (previous /usr/local/lib layout
made codex report bubblewrap unavailable inside the container).
- Real Docker task on external host: triple added in ~18s, merged
inside a container, main repo now add+mul2+triple, --rm cleanup.
Honest boundary: this host/kernel forbids unprivileged user
namespaces, so bwrap fails on host and in containers; production
mode here is Docker isolation + inner danger-full-access, not
double sandboxing.
- Cloud merge status: POST /tasks/{id}/merge syncs status only
(merge itself stays with host.py merge); completed -> merged,
maintainer+ (approve_merge), other states and repeat calls 409,
audit merge_task. Tests: cloud 223, host 27, all passing.
248 lines
16 KiB
Markdown
248 lines
16 KiB
Markdown
# CHANGELOG
|
||
|
||
语义化版本见 `VERSION`(当前 0.1.0)。以下按日期倒序,只列已交付
|
||
的大项,每项附实测证据;未建成的东西在“诚实边界”里明说,不夸大。
|
||
|
||
## 2026-10-26:收尾(Docker 沙箱实测 + 云端合并状态)
|
||
|
||
- Docker 沙箱执行器接通并在外部主机上真实跑通:镜像
|
||
`codex-app-server:latest`(codex 0.164.0-alpha.9,codex-resources
|
||
必须放在 codex 二进制旁边、且 bwrap 进 PATH,否则容器内报
|
||
bubblewrap is unavailable)、worker `--executor docker`(额外
|
||
挂载 `/app/work` 与 `/var/lib/codex-app-server`,容器用户取
|
||
宿主服务用户 uid/gid)、Docker 任务 triple 加函数约 18 秒完成、
|
||
容器内合并后主仓库含 add + mul2 + triple、容器经 --rm 清理。
|
||
诚实边界:这台主机/内核禁非特权 user namespace,bwrap 在
|
||
主机和容器内都起不来,因此生产模式是 Docker 隔离 + 容器内
|
||
danger-full-access(host-config-docker.json),不是双层沙箱。
|
||
- 云端合并状态:新增 `POST /tasks/{id}/merge`,只同步状态、
|
||
不执行合并(合并仍由 host.py merge 执行,成功后再调本接口);
|
||
仅 completed 可改为 merged,maintainer 以上(approve_merge),
|
||
其它状态与重复调用返回 409,越权 403,审计记 merge_task。
|
||
- 实测证据:云端测试 219 个、host 测试 27 个、合并状态单测
|
||
4 个,全部通过(`python3 -m unittest discover -s cloud/tests`
|
||
与 `python3 -m unittest discover -s tests`)。
|
||
|
||
## 2026-10-26:缺口补齐(云端控制面第二批)
|
||
|
||
- 记忆 / KB / 成本 / 模型接成 HTTP 端点:记忆写/查/ supersede、
|
||
KB 索引/搜索(按项目隔离)、按任务与按项目/按日的成本(实际与
|
||
估算分开)、模型总览;全部走 `X-API-Key` + 项目 RBAC,
|
||
401 / 403 / 404 语义与跨项目隔离均有测试。
|
||
- SQLite 并发:共享 db 助手(WAL + busy_timeout + 有界重试),
|
||
并注明单机上限,Postgres 是下一阶段,不是本版已做。
|
||
- OAuth 脚手架:`cloud/oauth.py`,state 10 分钟过期、生成授权 URL、
|
||
回调验 state 后用可注入的 HTTP 客户端换 code;secret 只走环境变量,
|
||
没有假登录页,真实 OAuth 应用注册仍缺。
|
||
- 推送订阅与分发:`cloud/push.py`,webhook / slack / webpush /
|
||
apns / fcm 通道,即时/摘要/静默偏好,只发状态变更事件;缺发送方/
|
||
凭证时报 error,绝不静默报成功;真实推送凭证未配、真实推送未发。
|
||
- 实测证据:云端测试从 102 个涨到 157 个,全部通过
|
||
(`python3 -m unittest discover -s cloud/tests`);
|
||
host 测试同期 27 个,全部通过
|
||
(`python3 -m unittest discover -s tests`);
|
||
另有一次 live smoke:记忆写 201/读通、KB 索引/搜索命中、
|
||
OAuth 登录 URL 只返回不跳转、推送分发报“无发送方”错误、
|
||
无 key 查项目返回 401。
|
||
|
||
## 2026-10-26:云端控制面(第一批)
|
||
|
||
- 新 `cloud/` 包,零第三方依赖、只用 Python 标准库:
|
||
`server.py`(ThreadingHTTPServer + SQLite 状态,任务状态机与
|
||
host.py 对齐,含心跳与 stale 回收)、`auth.py`(API key 只存
|
||
SHA-256 哈希、按项目 RBAC owner / maintainer / contributor /
|
||
viewer、追加式审计日志)、`queue.py`(依赖拓扑 + 优先级 +
|
||
预算门)、`sandbox.py`(Local / Docker / Cloud 三种执行器,
|
||
缺 Docker 或缺云账号时明确报错、不静默降级)、`memory.py`
|
||
(线程摘要,可删/可 supersede,只取 top-k)、`kb.py`
|
||
(SQLite FTS,LIKE 兜底,按项目隔离)、`models.py`
|
||
(404 / 429 / 5xx / 超时才 fallback,不对 401 / 400 fallback,
|
||
实际用到的模型如实记录)、`cost.py`(读不到账单时返回
|
||
available=false,估算值明确标 estimated,实际与估算分开)、
|
||
`webhooks.py`(GitHub HMAC + Slack 签名与重放窗口,只解析,
|
||
只在状态变更时推送)。
|
||
- 实测证据:云端测试 102 个、host 测试 27 个,全部通过;
|
||
smoke:health 正常、无 key 查项目返回 401;代码里无密钥。
|
||
- 诚实边界:这是本地可跑的控制面演示,不是已建成的真实云服务;
|
||
仍缺常驻宿主、GitHub 远端 + PAT、Slack App、云容器账号、
|
||
定时/worker 跑模型时的模型 key 供给方式、真实 OAuth 应用注册、
|
||
真实推送凭证;server 本身不跑模型回合。
|
||
|
||
## 2026-10-26:GitHub PR / CI 一条线
|
||
|
||
- `host.py github-workflow`:生成 `.github/workflows/host-verify.yml`
|
||
(checkout + python + unittest),幂等。
|
||
- `host.py pr`:校验任务已完成且有分支,push 分支 + `gh pr create`,
|
||
成功后把 pr_url / pr_number 写回任务记录。
|
||
- `host.py pr-checks`:解析 `gh pr checks`,all_pass 仅在 checks
|
||
非空且全部 pass 时为真,空 checks 不算过。
|
||
- `host.py pr-merge`:先查 checks,未全过拒绝且不调 merge,
|
||
全过才 `gh pr merge --merge` 并标 merged,不动本地主分支
|
||
(与本地 `merge` 是两条线)。
|
||
- 实测证据:全部 gh 调用走可 mock 的 run_gh;测试从 19 个涨到
|
||
27 个,全部通过,含本地 bare 远端 push + 假 gh PR(写回编号 42)
|
||
与 workflow 幂等。
|
||
- 诚实边界:真实 PR 本轮没跑——本仓没有 git remote,gh 未登录;
|
||
真实 PR 仍缺远端与经 Secure Vault 的 PAT,那两样齐了才能跑。
|
||
|
||
## 2026-10-26:第二阶段(daemon / health / 自动恢复)
|
||
|
||
- `host.py daemon`:双 fork 到 PID 1,pidfile + host-daemon.log,
|
||
每轮跑 recover + run-due(各为独立 host.py 子进程);
|
||
`daemon-status` / `daemon-stop` 配套。
|
||
- `host.py health`:不跑模型,只查二进制、配置、state 有效性、
|
||
备份、daemon pid、server initialize + list、缺失 worktree;
|
||
失败时 exit 1。
|
||
- `host.py restore-state`:state 损坏/丢失时从 .bak / .bak.1 /
|
||
.bak.2 恢复,损坏文件改名为 .corrupt.<时间戳> 留证。
|
||
- `host.py recover`:超过 `--older-than`(默认 3600 秒)未更新的
|
||
running 任务标 interrupted 并记日志,可用 `run` 续跑;
|
||
故意不自动重跑模型回合,避免未经批准花 relay 额度。
|
||
- 附带修掉一个静默丢数据洞:旧 state 读取把 JSON 解析错误
|
||
当空 state 吞掉。
|
||
- 实测证据:daemon pid 的 PPID 为 1,测试中每 5 秒 tick、
|
||
stop 后 pidfile 删除;health 先 ok=true(7 个 server 线程),
|
||
损坏 state 后 ok=false / exit 1,restore 后 ok=true(备份版本 9);
|
||
recover 把 2 小时未动的 running 标 interrupted、1 分钟的保留;
|
||
测试从 15 个涨到 19 个,全部通过。
|
||
- 诚实边界:daemon 本身活不过平台 VM 回收(与微信桥 serve 同理);
|
||
VM 上更稳的定时是 Muse cron 调 run-due / health,不是只靠 daemon
|
||
或 crontab;本轮没有自动装 Muse cron 或 crontab,因为 run-due
|
||
到期会花模型额度,装定时需用户明确指定间隔与是否允许自动跑模型。
|
||
|
||
## 2026-10-26:第二类缺口(全部补齐)
|
||
|
||
- 合并自动验测 + 回滚:`merge` 默认仓库有 tests/ 就跑 unittest,
|
||
验测失败 reset 回合并前 HEAD,分支/worktree 保留;
|
||
`--no-verify` 跳过,`--verify` 可指定命令。
|
||
- 任务依赖 + 优先级 + 自动重试:jobs 里 depends_on / priority,
|
||
拓扑排序 + 按波次跑,依赖失败时依赖任务标 skipped 不跑;
|
||
依赖任务从依赖分支建 worktree(有血缘关系),不再文件拷贝;
|
||
dispatch / parallel 里 `--max-retries`,失败后同 prompt 再跑,
|
||
重试前查预算,默认 0、行为不变。
|
||
- 评审评论回流:`review-comment --file --line --comment` 把评论
|
||
存进任务,`review` 列出 open 评论,`fix-review` 按 open 评论
|
||
逐条修、修完标 resolved。
|
||
- server 崩溃重连:Server.ensure_alive,死了重拉并 resume thread
|
||
(thread 在 CODEX_HOME 里不丢);request 遇 BrokenPipe 重试一次;
|
||
单测 kill 进程后重拉 + list 能通。
|
||
- 成本预算 + 失败通知:配置 max_daily_turns,按 turn 数算
|
||
(协议不暴露真实 token 数,这是诚实边界),超预算不派发/
|
||
不重试;usage 记在 state 里,`doctor` 可看;失败时写
|
||
host-run.log + 跑配置的 notify_command(本地 shell,
|
||
模板 {task} {status});`failures` 列失败任务,`doctor` 列
|
||
失败/缺失 worktree/open 评论。
|
||
- 多机器 state 版本:state 加 version / last_writer / updated_at,
|
||
保存时磁盘版本比加载时新就记 STATE-CONFLICT 日志并合并保留
|
||
双方任务,不静默丢;真多机中心同步仍是检测、不是云同步。
|
||
- 合并时不再提交缓存文件:merge 前清 worktree 的
|
||
__pycache__ / *.pyc,主仓的 .pyc 改动先还原;单测覆盖 pyc 不入库。
|
||
- 实测证据:测试从 3 个涨到 15 个,全部通过;模型验证在
|
||
gap2 / gap3 仓上跑通(依赖并行、评审评论修复、带自动验测的合并),
|
||
并在实测中拦下两处真问题:文件拷贝依赖分支导致重复加同一段的
|
||
合并冲突(-X ours 兜底的新增行核对拦下一次 mul2 丢失并回滚,
|
||
后改从依赖分支建 worktree 修复)、merge 的 git add -A 提交
|
||
__pycache__ / *.pyc(已修)。
|
||
- 诚实边界:独立任务在同一文件位置追加仍会合并冲突,这是 git
|
||
自身行为,host abort / 回滚、不猜合并结果;多机共享 state
|
||
现在能检测、不静默丢,但 ChatGPT 云端式中心同步不在本地 host
|
||
范围内。
|
||
|
||
## 2026-10-26:生产加固
|
||
|
||
- state 文件锁(fcntl,只锁短读/写;保存时按任务/自动化名合并
|
||
磁盘状态,多进程并发写不丢对方任务;双进程 0.5 秒窗口竞写实测
|
||
双方任务都保留)。
|
||
- 项目配置 host-config.json(`init` / `config` 命令,CLI > 配置 >
|
||
默认;API key 绝不进配置文件,只走环境变量)。
|
||
- 定时自动化:run-due 在 host-run.log 记派发/跳过,
|
||
`automations --cron-line` 只打印 crontab 行不装机,
|
||
`--install-cron` 幂等装机;本轮测试里没有实际装 crontab,
|
||
避免未经批准的模型花费,且 VM crontab 可能活不过平台回收,
|
||
Muse cron 调 run-due 是更稳的 VM 选项。
|
||
- 诚实边界:锁只防同一台机器,不防两台机器共享同一 state 文件;
|
||
host.py 本身不常驻,仍需外部调度器触发 run-due。
|
||
|
||
## 2026-10-26:真实项目跑一轮(在打包前)
|
||
|
||
- 在 codex-app-server 本仓(非敏感的 wechat-bridge)上 dogfood:
|
||
先 git init(初始提交,.gitignore 排除 367M bin/、work/、
|
||
host-state*.json、host-run.log、本地 host 配置),真实任务用
|
||
单独的 host-state-real.json。
|
||
- 并行真实任务:real-tests 加 tests/test_host.py(3 个 unittest,
|
||
全部通过,host.py 本身未改);real-backup 加 save_state 备份轮转
|
||
(.bak / .bak.1 / .bak.2,在锁内)。
|
||
- 只读委派评审 real-backup 后发现多步轮转非原子、有截断拷贝风险、
|
||
无 fsync、fcntl-less 平台问题;便宜的修复已在 fix 回合里落地
|
||
(with open 读 state / 写临时文件、修掉 real-tests 发现的
|
||
ResourceWarning、备份经临时文件 + os.replace 拷贝)。
|
||
- 两任务都合并进 main,main 测试全过且无警告,手动双保存确认
|
||
.bak 里是上一版。
|
||
- 真实项目才有的教训:非 git 项目要先 git init 才能用
|
||
worktree / 合并门;缺 .gitignore 会把 bin/ 提交进去;
|
||
真实任务与玩具任务必须用不同的 state 文件。
|
||
- 记录在 TEST-REPORT.md;本轮打包前仍欠的下一步是:安装文档、
|
||
host 快速开始、版本/变更日志、合并后主仓自动验测(那一步当时
|
||
仍是手动)——其中合并后自动验测已在“第二类缺口”里补上,
|
||
安装文档与版本/变更日志由本 CHANGELOG 与 install.sh / README
|
||
快速开始补上。
|
||
|
||
## 2026-10-25:host 编排(多线程并行、worktree 隔离、评审/合并门)
|
||
|
||
- 单个 app-server 进程托管多 thread,后台读线程按 threadId 分发
|
||
turn / completed,多 thread 的 turn 真正并发,不是轮流排队。
|
||
- `dispatch` 自动 `git worktree add -b host/<task>`,一任务一
|
||
worktree;非 git 仓库退化为独立目录拷贝。
|
||
- `review`:status + diff + untracked;`merge`:先在 worktree
|
||
提交,再 `--no-ff` 合并进主仓,冲突自动 abort;
|
||
`discard`:删 worktree 和分支。
|
||
- `run`(resume + 新回合)、`fork`、`archive`、`list`;
|
||
`delegate`:子 thread(默认 read-only)跑完,结果可
|
||
`--report-to-parent` 汇总回主 thread。
|
||
- `goal`:同一 thread 多回合,直到 `--verify` 验收命令通过
|
||
或到 `--max-rounds`。
|
||
- `automations --add` + `run-due`:到期任务自动派发,结果进
|
||
host-state.json 评审队列;未到期不重复派发(幂等实测)。
|
||
- 边界实测:read-only thread 的写文件请求被拦下、文件确实没建;
|
||
untrusted + deny 时 agent 收到审批拒绝、没拿到命令输出;
|
||
模型内 3 子代理并行检查视角确实不同;turn/interrupt 后状态
|
||
为 interrupted,`compact` 需先 resume(host.py 的 compact 命令
|
||
已包好这一步);合并冲突时主仓干净、冲突 worktree 留着人工解;
|
||
隔离 CODEX_HOME 下的 MCP 工具能被发现并调用(MCP 审批是
|
||
elicitation 协议、不是通用 decision 字段,host.py 已分流处理,
|
||
修过一轮才通);同一 thread 内换模型续跑、turn 的 model 字段
|
||
随之改变;turn/steer 能把进行中的长任务改短;3 并行 worktree
|
||
互不污染;review 时 diff 为空也要看 untracked;不存在的模型
|
||
导致 task failed 并带上游 404 错误、host 不崩。
|
||
- 工具执行修复(VM 专属):系统裸 codex 二进制缺两个同伴
|
||
(codex-code-mode-host 与 codex-resources/bwrap),工具会
|
||
fail closed;配对目录 `bin/`(codex + codex-code-mode-host +
|
||
codex-resources/bwrap,约 367M,官方 0.164.0-alpha.9)经
|
||
start.sh 优先使用;relay 支持的模型回合实测 10.3 秒内在
|
||
work/xcm-test/test-result.txt 写出“codex-ok”。Mac 上正常
|
||
npm 安装自带这三件套,不需要 bin/。
|
||
- 协议层:start.sh 启动官方 codex app-server(0.164.0-alpha.9),
|
||
零依赖 Python stdio JSON-RPC 客户端(initialize / thread/start /
|
||
turn/start、审批、resume / fork),宿主级并行多 thread 编排,
|
||
配置示例与官方协议 schema(generate-json-schema 导出)。
|
||
隔离 CODEX_HOME 下验证到模型边界为止:initialize / thread/start /
|
||
turn/start 与事件流都通,turn 随后因缺模型登录返回 401 failed,
|
||
这是登录边界、不是协议 bug。
|
||
- 诚实边界:云端派发、推送通知、Slack / GitHub 触发、GUI inline
|
||
diff 评论、Computer Use 都不在开源里、没有复刻;本版是本地
|
||
host + 本地可跑的控制面演示,不是真实云服务。
|
||
|
||
## 诚实边界(贯穿所有版本)
|
||
|
||
- 没有建成真实云服务:cloud/ 是本地可跑的控制面演示,
|
||
生产仍缺常驻机器、GitHub 远端 + PAT、Slack App、云容器账号、
|
||
模型 key 供给方式、真实 OAuth 应用注册、真实推送凭证。
|
||
- 没有真实 PR、没有真实推送:PR 只做到代码 + 本地可测部分
|
||
(假 gh、本地 bare 远端);推送只做到订阅与分发逻辑,
|
||
缺凭证时明确报错。
|
||
- 没有真实 token 成本:协议不暴露真实 token 数,预算按 turn
|
||
数算;成本端点里实际与估算严格分开,估算值标 estimated。
|
||
- 没有在任何文件、state、日志或汇报里写密钥:API key、PAT、
|
||
Slack token、OAuth secret 一律只走环境变量或 Secure Vault,
|
||
配置文件里只放 env_key 这类名字,不放值。
|