# CHANGELOG 语义化版本见 `VERSION`(当前 0.1.0)。以下按日期倒序,只列已交付 的大项,每项附实测证据;未建成的东西在“诚实边界”里明说,不夸大。 ## 2026-10-28:网页搜索 Docker env 透传(e2e 失败后的修复) - 根因:Docker 任务容器不继承 worker 进程环境,只拿 extra_env(-e);`SEARXNG_URL` 只写进 worker unit 的 EnvironmentFile 时,容器内 codex -> MCP 根本看不到它, auto 链跳过 SearXNG、只剩 DuckDuckGo,实弹 e2e 报错 “all search providers failed: duckduckgo (DNS)”。 - 修复:`cloud/worker.py _docker_task_context` 把 worker 环境 里已设置的 `SEARXNG_URL` / `PERPLEXITY_API_KEY` / `EXA_API_KEY` / `TAVILY_API_KEY` / `BRAVE_API_KEY` 合并进 extra_env(值不进日志;付费 key 与网关 key 同款走法)。 - 单测:新增 2 个(有/无搜索 env 时的容器 env),连同 websearch 旧测、租户隔离旧测共 25 个通过;cloud 全量 1038 个通过(skip 4)。只本地提交,未推、未建 PR、 未合并、未部署;重跑 e2e 须等本修复部署后。 ## 2026-10-28:网页搜索接 worker + egress(公开 SearXNG 路) - `cloud/worker.py --websearch-mcp`(默认关):开启后 `_tenant_dirs` 播种 config.toml 之后自动播种 websearch MCP(容器内路径),老租户下一次任务自动合并; autoscaler unit 已加该 flag(主机上须单独应用 unit)。 - `deploy/egress/tinyproxy-filter` 已加 Perplexity、Exa、 Tavily、Brave、DuckDuckGo 与精确的 `searx.diante.me` (不用 diante.me 通配,防任务经代理摸到控制面)。 - 单测:`cloud/tests/test_websearch_worker.py`(播种开/关、 老租户合并、filter 的 BASIC 语法检查)与 websearch 旧测、租户隔离旧测共 23 个全部通过。 诚实边界:本批只本地代码,未推、未建 PR、未合并、 未部署;SearXNG 主机部署、DNS、证书、nginx 与 tinyproxy 重载均在主机步骤里,另见 docs/web-search-mcp.md。 ## 2026-10-28:网页搜索 MCP v1(oh-my-pi 模式,Python 实现) - 新增 `cloud/websearch.py` + `cloud/websearch_mcp.py`(纯标准库): stdio MCP 服务器暴露 `web_search` / `web_fetch`,provider 链 按 Perplexity → Exa → Tavily → Brave → SearXNG → DuckDuckGo 的 auto 顺序回退,pin 单个名字时不回退;抓取去脚本样式、 默认 15000 字符头尾截断并标 `[TRUNCATED]`;key 只走进程 环境变量,错误串不含 key(单测断言)。 - 新增 `ensure_codex_home_websearch`:把两个脚本拷进租户 codex-home,缺失时追加 `[mcp_servers.websearch]`,已有 section 绝不重写、租户改过的脚本绝不覆盖。 - 单测:`cloud/tests/test_websearch_mcp.py` 12 个全部通过 (链式回退、pin 不回退、全部失败、Perplexity 答案与引用、 Brave/Tavily 映射、DuckDuckGo HTML 解析、抓取截断与拒绝、 MCP 帧、配置合并幂等),不联网、不需要真实 key。 诚实边界:付费后端只以 mock 验证请求字段与映射,未用真实 key 实测;worker 自动播种未接线、生产 egress 白名单未改、 本批只本地提交,未推、未建 PR、未合并、未部署。 ## 2026-10-28:长 goal 任务异常退出可断点续跑(不再 terminal failed) - 实弹事故:11:49 认领的 8 轮 goal 长任务跑约 60 分钟后终局 failed、无 goal 结论;host-state 仍是 goal-running、 goal_rounds_done=0、turns 为空——第 1 轮 turn 从 11:53 起 再无进展,600 秒回合超时未触发(同期探针脚本与 autoscaler 一并静默,12:48:48 全局 OOM 杀掉另一租户的 supermemory-ser 后系统恢复,12:48:56 任务判 failed)。根因链:宿主机内存 挤兑致全机停滞 + 恢复时 goal 进程异常退出、无结论行,云端 旧语义(returncode 非 0 一律 failed)把可续跑的断点判成终局。 - 修法:host.py `run_goal_loop` 回合异常(turn 抛错、验收起 不来、检查点落盘失败)不再向上传播,统一记 interrupted、 打印 "goal interrupted after rounds: <原因>" 标记行、exit 0; cloud/sandbox.py 四处 goal 结论解析(local goal、docker goal、docker 两处 resume)同步新语义:interrupted 标记 → interrupted;returncode 非 0 但无结论 → interrupted(断点在 盘上,保住 /goal-resume 活路);有 met/unmet 结论的非 0 退出 仍 failed,rc==0 无结论仍 failed;worker 终局审计名与状态 对齐(interrupted 记 worker_interrupted,不再误记 failed)。 - 单测:sandbox 新测 5 个(local 无结论非 0 → interrupted、 非 0 带 unmet 结论仍 failed、interrupted 标记、docker goal 与 docker 两处 resume 无结论非 0 → interrupted),host 新测 1 个 (turn 抛错 → interrupted + 标记行 + 落盘原因),并更新 1 个 旧测(首轮前落盘断言:旧“异常上传播 + 状态 goal-running” 改为新语义)。 诚实边界:导致恢复时退出的确切异常已不可考(任务行与执行 结果已被探针清理删掉,stderr 无存档);本次只修“异常退出 的终局语义”,不修宿主机内存挤兑本身(另一租户容器,不动); 本批只本地提交,未部署、未做生产实弹复验,部署与复验另等 用户点头。 ## 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/`,一任务一 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 这类名字,不放值。