Commit Graph
36 Commits
Author SHA1 Message Date
muse e768959c93 fix(host): _read_loop 跳过非 dict JSON 消息,不再静默死亡
收到合法 JSON 但值不是 dict(如 ""、123、[]、null)时,
旧代码走到 msg.get('params') 抛 AttributeError/TypeError,
被外层 except 吞掉后读线程退出,此后所有 request() 等到超时。
现校验后记 stderr 日志并跳过,读循环继续;脏消息不进
events、不当正常请求处理。畸形 JSON 本已由 json.loads 的
except continue 处理,本次未改动该路径。
回归:tests/test_host_bugfix_host.py ReadLoopNonDictTest(2 项)
2026-11-03 02:49:25 +00:00
host f39ed3654e ci3: lint/mypy 第二阶段清理 + 覆盖率 76% -> 78%(门槛不动)
基底:#251 头 174b631(堆叠于 #242 feat-ci-expand 之上),
基线 origin/main aebc6238;本轨不改 #251 已定语义、不动
workflow 门槛、不删检查、不加 skip。

lint(ruff E,F@120):109 -> 10
- F401 43 -> 0:删未用导入;try/except 双绑定的名字出现
  “打地鼠”(删 except 分支后 try 分支又被标出),两分支
  均已核对正文无引用后清掉;MCP 三模块的 Error 名不删,
  改走 importlib 属性绑定(见下),两路径绑定都保留。
- E702 40 -> 0:全在 host.py main() 的 argparse 段,纯分行,
  tokenize 按分号 token 拆(含 2377 行多行语句只断首个分号);
  一次 5 空格缩进失误已回滚重做并以 ast + 全量 host 测试验证。
- E501 13 -> 0:host.py 5 项随 E702 分行连带清掉;mcp_server
  3 项把描述拆成相邻字面量(AST 比对 24 条工具描述与原值
  完全一致,注意 ruff 按东亚宽度计长,CJK 算 2);test_host
  5 项断开 git commit 参数表。
- E402 8 -> 10:生产侧 2 项清掉(db.py _re、github_app
  _base64 移到顶部,之前无引用、无重名);剩余 10 项全是
  tests/ 的 sys.path 前置 import(6 项旧 + 4 个新测试文件
  沿用同一既定模式),移上去会破坏单文件直跑,有意保留。
- F841 4 -> 0:只去绑定、不删调用(run_task、DB 入队、
  tarfile 上下文、git 提交),各有注释与原测试/新测试覆盖;
  host dispatch_from_branch 新增真实 git 仓库回归测试。
- F811 1 -> 0:server 本地 _row_get 与 peer_messages.row_get
  逐行相同,删本地定义,全部调用点解析到同一 import;
  新增 test_server_row_get(同一对象 + 三路行为)。

mypy(生产范围):24 -> 0,零新增 type: ignore
- no-redef 9 -> 0:MCP 三模块 try/except 两次 from-import
  改 importlib.import_module + 属性绑定(cloud 优先、flat
  copy 回落、内层 ImportError 同样回落,语义与原来一致;
  flat-copy 子进程测试证实回落后 __module__ 为顶层名);
  server _row_get 随 F811 消除。
- arg-type 10 -> 0:harness2_tasks shared 改 TypedDict
  逐键精确类型;CodexProbeAdapter 补 no-op validate_tree
  (_adapter_validate 本就跳过缺失方法,行为一致)。
- var-annotated 3 -> 0:sub2api _SHARED_ADMIN/_login_slots、
  verify-multiuser RESULTS 加精确注解。
- assignment/misc 2 -> 0:marker_tools ZoneInfo 先声明
  Any 再 try import(草稿先验 mypy 通过)。

bandit:总数 286 不变,High 仍 4 项,全在 host.py,本轨不改
(属第 3 项白名单轨范围),只做 triage:
- 1319 run_verify:无插值,整串验测命令,管道/&& 须 shell
  语义(新增 test_host_run_verify 断言管道与 && 真实生效)。
- 1497 notify:模板来自算子配置,三处插值全部 shlex.quote,
  证据为既有 test_notify_failure_quotes_shell_metacharacters
  (; touch 与 $(touch ) 不执行)。
- 2099/2103 goal verify:无插值,任务配置验测串,同 run_verify。
- 无新增 High;pip-audit 0 漏洞。

覆盖率(同口径三套件 coverage 计量):75.96% -> 77.87%
(22392 行/缺 5385 -> 22437 行/缺 4963,显示 76% -> 78%)
- client.py 0% -> 90%(pipe 假子进程 + main 分支)
- run_parallel.py 0% -> 97%、migrate 包装 0% -> 95%
- playwright_mcp 37% -> 62%、imagegen_mcp 47% -> 79%、
  websearch_mcp 51% -> 86%、deploy.py 56% -> 77%
- verify-multiuser 0% -> 27%(仅 check/req helper;main()
  是主机侧真实多用户实弹验证,按门禁不建生产任务、
  不以 mock 冒充,仍为缺口)
- host.py 55% -> 57%(文件 2020 -> 2060 行系 E702 分行
  的计量口径变化,非代码膨胀)
- 未到 85%:门槛保持 75% 不动,不删低覆盖模块出统计口径;
  缺口主力仍是 server.py / worker.py / host.py 大文件,
  下一阶段须针对性深测,非本轨快赢可及。

验证:三套件 1576 -> 1629 项全过(tests 127、cloud/tests
1449 skipped=4、deploy/tests 53);ruff E9 通过;mypy 0;
bandit High 4(同上);pip-audit 0 漏洞;试跑产物已删,
提交后工作树干净。
2026-11-03 02:48:54 +00:00
host ce85043a24 trace: 标注被截断的链路消息,避免误读为模型未写完
2026-10-29 实弹:468 字 final_answer 在链路里只剩前 300 字、
断在 fu 后,控制台照原样当助手回复展示,用户误以为模型
没写完。截断本身是设计(防链路文件写爆),缺的是标记。

- host.py _summarize_trace_item:agentMessage 超 300 字时
  条目带 truncated=True,短消息不带,完整回复仍在任务
  result_text 里,不在链路条目里。
- 控制台:TraceEntry 加 truncated 字段,mapTrace 与
  traceLineText 在被截断的消息后标注“链路摘要已截断至
  300 字,完整回复见右侧任务结果”,console.html 为构建产物同步。
- 单测:tests/test_host_trace.py 新增截断标记用例(468 字
  截 300 字且带标记、短消息不带标记)。

验证:host 10 项、cloud test_task_trace 14 项全过;
React tsc+vite 构建通过,产物 322151 字节、含截断标注、
含 #150 的 total_tokens/首字/总耗时,无回退。
2026-10-30 01:01:50 +08:00
host 9ea0a05fb1 host: fix state-loss from tmp collision and cross-container flock failure
2026-10-29 task 2 lost its host-state entry (version 390 -> single
digits) while running under Docker/gVisor. Root cause, proven live:
- save_state tmp name contained only pid, and every containerized
  host.py runs as pid 1, so concurrent writers shared one tmp file
  and interleaved writes produced corrupt JSON;
- fcntl.flock does not exclude across runsc containers sharing a
  bind-mounted lock file (both entered at 0.0s in a live probe);
- _read_state_file silently treated corrupt JSON as empty state, so
  the next save legitimately rewrote the file with only the
  surviving writer's tasks.

Fix:
- tmp name now hostname + pid + thread + uuid4 (backup tmp same);
- mkdir atomic lock replaces flock (atomic on the host fs, holds
  across bind mounts/gofer), with stale-lock recovery after 120s
  and a loud timeout instead of unlocked writes; reads need no lock
  because publication is tmp + os.replace;
- save path uses a strict disk read: corrupt JSON, non-object top
  level, or wrong-shaped tasks/automations now raise instead of
  overwriting (recovery stays with restore_state + .bak).

Tests: tests/test_host_state_lock.py, 7 items (tmp uniqueness in
process and across processes, 8-process concurrent saves all
survive with version 8, stale lock recovery, fresh lock timeout
raises, corrupt/wrong-shaped disk refuses to overwrite).
Full suites: cloud 1168 (skipped 4), host 104, deploy 39, all green.
2026-10-29 15:57:46 +08:00
host 585242fc8f 长任务 P0 三件:断点进库、错误分类+熔断、Run/Step 时间线
断点进库:task_checkpoints 表(PG 新库 + 老库幂等补表 + 迁移清单
对齐);worker 在 goal 任务执行期间每 30 秒把磁盘 host-state 断点
镜像进库,终局再补一次;断点续跑认领前,若 host-state 条目丢失
但库里有断点,先按库断点重建最小条目再跑,库里也没有断点时不
造假、走原有拒绝路径。

错误分类+熔断:sandbox 四处解析加永久错误分支(KeyError、配置
缺失等无结论异常退出判 failed 死信,不再判 interrupted;host 已
明言 interrupted 的不受影响,未知错误不分类);worker 自动恢复
加退避(10 秒起指数退避、封顶 300 秒)与无进展熔断(库断点轮次
连续 3 次不变即标 auto_resume_stalled 等人工)。

时间线:task_steps 表 + host 每轮写 host-steps 步骤文件 + worker
导入;GET /tasks/{id}/timeline 一次返回 steps 与 checkpoints;
认领、暂停、自动恢复、终局都记步骤。旧任务返回空数组,不编造。

单测:cloud/tests/test_p0_three.py 14 项(分类、熔断、镜像重建、
步骤导入去重、timeline 接口)。
2026-10-29 15:13:01 +08:00
host a5f2e4e091 fix: 断点丢失时 goal-resume 拒绝成 failed,阻断自动恢复空转
2026-10-29 长任务实弹:Docker goal 断点续跑在 host-state 里找
不到任务时抛 KeyError,worker 把无结论异常退出判 interrupted,
自动恢复再排队、再 KeyError,12 秒一轮空转 49 次(人工暂停
拦下,第 50 次人工续跑又烧掉),文件 32 章原样未动。
修法:host.py goal-resume 先查任务在不在 state 里,不在就打印
拒绝行、正常退出;sandbox 三处解析(本地、Docker resume、
Docker 断点)见拒绝行即判 failed 终态,不进自动恢复循环。
单测:host 缺任务拒绝不炸 + 三处解析拒绝判 failed。
2026-10-29 14:14:13 +08:00
host ae3aff8602 feat: 长任务无敌补齐三件:连续失败上限 + worker退出审计 + 配额恢复自动续跑
1. host goal 循环连续失败护栏:3 轮连续失败即中断出口,
   任务记 interrupted、可断点续跑,不再无限空转
   (2026-10-29 实弹:断点后 274 轮全失败)。
2. worker 退出杀残留路径补审计 worker_shutdown_interrupted,
   detail 为任务 id,与 sweep_interrupted 同口径。
3. 配额中断自动恢复:每轮现查配额,仍超限/查不到时留
   在 interrupted 不重排队,下轮再查;现查为 None 时
   (新月份/额度上调/无价上限重置)才 CAS 回 queued。

单测:配额仍超限不动、配额恢复续跑、连续 3 轮失败停转。
2026-10-29 13:07:57 +08:00
host 94137c4a29 goal 异常退出记 interrupted,可断点续跑
长 goal 实弹(2026-10-28):宿主机内存挤兑致回合挂起 59 分钟,恢复时 goal 进程异常退出、无结论行,云端旧语义(returncode 非 0 一律 failed)把可续跑的断点判成终局。

修法:host.py run_goal_loop 回合异常统一记 interrupted 并打印 goal interrupted after rounds 标记行;cloud/sandbox.py 四处 goal 解析同步新语义(标记或无结论非 0 退出 -> interrupted,有结论的非 0 退出仍 failed);worker 终局审计名与状态对齐。单测:sandbox 新测 5 个、host 新测 1 个、更新旧测 1 个;三套件 cloud 1017、host 61、deploy 29 全绿。只本地提交,未推未部署。
2026-10-28 15:18:26 +08:00
host e31fcc6cba feat: G7 分层 fail-open/fail-closed + G8 Docker 轮询掐断 + G9 worker 忽略 host 预算 + G13/G14 归属(第三批)
- G7:认领门 closed 时查配额异常回 quota_check_unavailable、标 interrupted(可重试/续跑);轮询与轮次边界用 open,异常时回 quota_check_failed——新回合边界拒绝,执行中轮询经 _quota_hit 闭包过滤此串、只记 warning 不杀在跑任务,确认超限才掐断;查询函数本身不吞异常冒充不限额。
- G8:Docker 路径加 _docker_call_with_quota_poll(后台线程跑阻塞式 docker 调用,主循环每 quota_poll_interval_seconds 缺省 30 秒查一次,确认超限按容器名真杀容器,结果改写 quota_exceeded 走统一收尾:interrupted、成本照记、续跑消息保留,配额命中后不走 ALREADY_EXISTS 回落);同步路径保持边界检查。
- G9:worker 路径默认忽略 host max_daily_turns(host.py 顶层 --ignore-host-budget 只清该项,Local/Docker 执行器在子命令前追加,worker CLI 缺省开、--no-ignore-host-budget 关);standalone 直接跑 host.py 时预算照常。
- G13/G14:/pr 不再走 system 豁免,建任务前按请求用户查配额(402/503 均不排队不改状态),队列路 pr_jobs.created_by 记请求用户(老表 ALTER 补列),worker 跑 pr 任务时审计操作人用 created_by(老行回落 system)。注意:G14 原案的 system 豁免(服务端与 worker 写死)与 P1 冲突(P1:system 按普通属主走默认额度,不另设无限额系统预算),本次 rebase 未合入该豁免,system 豁免需另行决策。
- 配额口径与 P0/P1 对齐:无配额行按默认额度(非不限额),只有显式 NULL 行才不限额;无价上限只计 source='actual' 行;缺属主(含父任务回查仍无)拒绝新回合。
- README 配额节同步以上口径与 G15 接受现状(best-effort 超支、UTC 月切分、在跑任务只在轮询/轮次边界响应配额改动)。G4 与 G15 不在本批。
2026-10-28 11:14:10 +08:00
host f186c60e4e fix: thread/unarchive 不再预 resume(已归档 thread 直接调 thread/unarchive) 2026-10-28 08:33:04 +08:00
host 755029f220 feat: 线程等待与状态读取 8 项(wait/read/message/handoff/worktree/pr-checks/automation/sleep)
对照 Codex app:wait_threads 首变+afterCursor+8上限、
read_thread 详情摘要、send_message_to_thread 复活失败任务、
handoff 登记与上报轮询、worktree 派生状态、PR checks JSON+
check-runs 日志与注释、automation GET/PATCH、clock.sleep
(API+host)。诚实边界见 docs/thread-wait-ops.md:无
worktree 阶段流、无 automation 变化静默、无独立测试结果
记录、handoff 服务端不执行迁移、wait 仅数据库级变化检测。

三套件:cloud 940 OK(4 跳过)、host 91 OK、deploy 29 OK
2026-10-28 04:33:20 +08:00
host ef532a38c2 feat: 线程操作与 skills/MCP 管理面暴露(host CLI + 服务端 API + 控制台)
线程:host.py 新增 thread-list/read/rename/archive/unarchive/compact 子命令(纯 JSON stdout 契约,read/rename/unarchive/compact 先 resume);服务端按任务定 scope(thread/list 的 cwd 过滤解析,Docker/cloud/异机 409,改操作仅终态,审计 thread_*);控制台线程区(元数据、列表、重命名、归档、取消归档、压缩,终态门控)。

skills/MCP:host.py 新增 skills-list/apps-list/mcp-servers-list/mcp-servers-reload/mcp-server-oauth-login 子命令;服务端按项目 scope(读 read,改 manage_project,审计 mcp_*);控制台技能与 MCP 区(列表、重载、OAuth 发起展示,绝不谎称登录完成)。

测试:host 84、cloud 687(4 跳过)、deploy 19 全过。诚实边界:Docker 任务线程在容器内故 409;无仓库任务推不出 cwd 时 404;OAuth 只做到发起透传,完成态靠协议通知;merged/merge-failed 改操作 409(比控制台门控窄,服务端报错原文展示)。
2026-10-27 16:37:28 +00:00
host 1402dc67fd P1 security batch: quota execution checks, cost accounting, goal/coordinator semantics, PR channel, orphan signature, control-plane and console hardening
- Docker quota watchdog + fail-closed quota checks, system tasks need real owner
- max_cost_usd actually accumulated, cancel records cost, estimated rows excluded from unpriced cap, cache_write declared non-billed
- goal-resume keeps original verify on breakpoint resume, goal retry falls back to resume, goal_max_rounds capped at 100 at create and execution
- coordinator children inherit parent model/effort, orphans marked interrupted with audit
- PR jobs on docker workers return explicit needs_local_worker instead of silent empty
- worker code signature covers 6 files and is checked before claim, fail-closed
- sub2api lazy provisioning with rotation, backup whitelist + 0600, push SSRF block, repos/workers desensitized, key/project audits and limits, console buttons/quota/status wording, coordinator/automation quota reserve
2026-10-27 16:27:50 +00:00
host 7091a2d0d2 P0 security batch: true global admin, quota gates on all spending paths, default quota for quota-less users, fix-review author trust + docker/owner attribution, GitHub App setup issuer binding, real cancel kill, wider shell env exclusion
- CODEX_GLOBAL_ADMINS replaces maintainer-derived global admin;
  admin endpoints (quotas, sweep, backup, push dispatch) fail-closed
  403 when unset, with audit on allow and deny.
- continue/followups/messages/retry check both caller and original
  creator quotas (USD + unpriced token cap), 402 on either overrun.
- Quota-less users now get the #86 default quota instead of unlimited;
  init_db backfills and OAuth create/bind/existing-login branches
  INSERT OR IGNORE a default row; explicit NULL row stays unlimited.
  user_quotas added to migration tables.
- /fix-review: untrusted authors rejected before dedup claim, no task;
  trusted tasks force executor=docker, created_by=project owner, owner
  quota gate, prompt is a fixed template plus a 2000-char excerpt.
- GitHub App setup state only issued by global admins, state rows bind
  issued_by (ALTER-compatible for old DBs), callback rejects issuers
  no longer in the admin list without consuming the state.
- Cancel: popen poll checks DB status every second, Docker path runs a
  cancel watchdog that kills by container name, write-back is CAS-guarded
  so late results never overwrite a new status.
- SHELL_ENV_EXCLUDE adds *_TOKEN, *_AUTH*, *_CREDENTIAL*, *API_KEY*;
  comment states the residual risk: same-UID /proc environ remains
  readable, true isolation needs UID separation or hidepid.
- Tests: new cloud/tests/test_p0_security.py (17 cases); old tests
  updated to the new semantics (admin env, default quota, trusted
  fix-review author) without weakening any gate.
2026-10-27 15:35:23 +00:00
host e4b5545a24 feat: 成本预算与失败通知第一批(G1/G2/G5/G6/无价上报)
- G1:修正 _check_quota docstring(Local popen 30 秒轮询掐断、
  Docker/同步路径只有边界检查),README 配额节同步;followup
  创建不查配额是已声明设计,保持不动。
- G2:notify_failure 对插值 shlex.quote,模板错误记日志不静默
  吞,命令返回值/超时/异常记 host-run.log;dispatch 前置预算门
  与 goal 预算中断补 budget-exceeded 通知(没配命令时只写日志)。
- G5/G6:重试与续跑的配额检查按任务创建者查(费用记创建者名下),
  维护者替他人操作不再用错人的额度;续跑 API 侧补查,超限回
  402 且不改状态,不再先 200 排队再悄悄 interrupted。
- 无价上报:配额自查/管理端点返回增加 unpriced_actual_tokens
  (actual 且 usd_micros 为 NULL,与 CostStore 口径一致),只读
  上报、不计价、不拦任务;不给 gpt-5.6-terra 编造价格。
测试:host 68、cloud 760(4 跳过)、deploy 19 全过。
2026-10-27 10:13:51 +00:00
host ec47328d3c sub2api 网关接入:每用户独立账号 + 任务注入 + 网关可配置
- cloud/sub2api.py:管理 API 客户端(管理员登录/token 刷新/
  建用户/以用户发 key/删用户/查组/网关模型目录)+ 幂等开通
  ensure_user_gateway_key(入库失败尽力清理孤儿 key 与账号)
- worker:网关门(sub2api 时按 created_by 幂等开通,失败任务
  直接 failed;协调员子任务跟父任务属主)+ 默认模型走网关
  目录首个;三条执行路径(Local popen/同步、Docker 同步)注
  入任务级 env 与 --model-provider;NO_PROXY 追加网关 host
- host.py:--model-provider,sub2api 时以 -c 注入 provider 表
  (不改租户 config.toml);未配 SUB2API_BASE_URL 时 loud fail
- sandbox:Local/Docker 执行器传 --model-provider 与任务 env;
  docker 密钥只传名(-e SUB2API_API_KEY),值由 CLI 子进程
  环境提供,绝不进 argv;runner 不支持 env 时 loud fail
- server:OAuth 建号 best-effort 开通;/relay/models 走调用者
  自己的网关模型目录;init_db 补 user_gateway_keys 表
- 测试:新单测 11 个;全量 cloud 679(4 跳过)、host 65、
  deploy 19 全过
- 诚实边界:真模型调用实弹需上游账号挂组后才做;切流前
  xcmapi 保持现网
2026-10-27 17:22:54 +08:00
host 89c8fb1a79 Key 修法:agent shell 不再继承密钥类环境变量
- 实弹证实:默认配置下 agent shell 继承 server 进程 env,local 与
  docker 两路都能拿到共享 relay key(v3 标记文件法双路 PRESENT)。
- 修法用 codex 原生 shell_environment_policy.exclude:server 自己
  的中转调用走进程 env(env_key),不受 shell 策略影响,两者可
  分开;inherit 保持默认 all,PATH、代理变量等不动;gh 用文件
  登录不用 env,故不排 *_TOKEN。
- host.py Server._start 经 _server_command 统一加 -c 旗标,local
  与 docker 共用一处,覆盖两路;-c 语法已用本机二进制实测
  (合法值启动正常,非法值立即报 invalid shell environment
  policy 退出)。
- 测试:新增 _server_command 断言;全量 host 65、cloud 668
  (4 跳过)、deploy 19 全过
2026-10-27 17:15:09 +08:00
host e0cecd70aa feat: P1 补齐(自动评审队列、自动化+分诊、并发 caps、运行时审批队列) 2026-10-27 15:53:50 +08:00
host dbcc8e641a feat: 任务列表显示 turn_id 与 root_turn_id
host.py dispatch 打印补 turn_id/root_turn_id;执行器解析(local/Docker)透传;worker 写回 tasks 新列(新值非空才覆盖);server 迁移与接口字段;控制台任务列表新增 turn/root turn 两列(短显+悬停全文);新单测 9 个。
2026-10-27 14:52:00 +08:00
host c4d3ec9bff feat: 命令台子代理链路(主任务/模型子代理/委派子任务 + 过程调用实时同步)
host:链路条目带 thread 归属;delegate 向父链路文件写 agent-start/done 标记。服务端:GET /tasks/{id}/agents 聚合父链路 + 子链路文件成代理链(状态口径:委派以结束标记为准、无标记即进行中;模型子代理无结束信号时父任务运行中为进行中、否则未知)。命令台:新增子代理链路区,随 SSE 流防抖刷新;扁平链路也渲染 agent 标记。
2026-10-27 11:51:11 +08:00
host 49dfb7c458 fix: goal 宣告检测容忍行尾句号,修提示与检测自矛盾致空转
_llm_declared_done 此前要求回合文本里单独一行恰好等于
目标已达成,但 run_goal_loop 三处提示都让模型输出
目标已达成。(带句号);模型照做后检测永不触发,实弹
goal 任务空转 292 轮才被手动取消。改成比对前去行尾空白
与句末标点(。!?;,、. 等),提示不动、单独一行要求
不动,行内带其它文字仍不算。

回归测试 test_goal_marker_trailing_punctuation_counts:裸标记、
句号、感叹号、句号前空格、行尾空格算达成;前缀文字、逗号
后续、反问、空串不算。
2026-10-27 05:28:59 +08:00
host 3a4997d346 feat: 旧任务继续会话——终端任务带新消息 + 全部选项同一会话续跑
server:POST /tasks/{id}/continue(终端非 merged 任务重排队,
resume_message 列存本轮消息,不传选项沿用原值,plan/goal 冲突
与新建一致,goal_mode=1 时转新 goal 语义;CAS 防并发改状态,
审计 task_continued);tasks.resume_message 三处迁移 + PG 建表。
worker:认领后本轮 prompt 换成续跑消息(只换内存副本),
goal 续跑走 goal-resume 而不是 goal,跑完清 resume_message
(进程被杀时不清,下次认领重跑同一条消息)。
host:goal-resume --goal-text(旧任务接续 goal 模式:跳过状态
与记录守卫,旧线程上从第 1 轮开新目标循环,新语义不限轮次;
无线程时拒绝)。
sandbox:Local/Docker goal_resume_task + popen goal-resume kind;
附带 popen goal 命令改成新语义(无验收不传 --verify,轮次 0
原样传,此前 goal 任务到本地 worker 会直接报错)。
console:详情页终端非 merged 任务出继续会话表单(消息 +
模型/强度/权限/计划/goal,模型下拉复用中转列表)。
测试:新增 test_task_continue.py 11 个 + test_host_continue.py 2 个。
2026-10-27 04:40:11 +08:00
host 9537e6f0d2 WIP: 任务附件——host seed-dir 与上传模块(未完,server/worker/console 待续) 2026-10-27 04:29:40 +08:00
openqjandhost c86b450851 feat: goal 模式新语义——prompt 即目标,无验收命令,不限轮次,模型宣告完成 (#67)
console 打勾 goal 模式时不再有专用 goal 文本、验收命令、轮次
三个输入(已移除,换成说明行),只发 goal_mode=1;server 侧
goal_mode=1 时 goal 存 prompt、验收命令空、轮次存 0,带验收
命令或与计划模式同用时 400,旧的带验收命令任务照原样可用。

host.py goal 循环:verify 为空时不跑验测,轮次提示里写明
标记约定;模型在完整跑完的回合末尾单独一行输出“目标已
达成”即达标(超时回合的部分文本含标记也不算);--max-rounds
0 为不限轮次,goal-resume 续跑按 0 处理(不用 or 兜底成 3)。
刹车只有取消与预算耗尽(后者记 interrupted、可续跑)。

迁移与 worker:tasks 加 goal_mode 列(server 迁移、worker 列
补齐、PG schema 三处同步);worker 按 goal_mode 或
verify_command 判定 goal 路径,重试与后续消息排空对新语义
同样只跑一次/不排空;Local/Docker goal_task 的 --verify 可选、
--max-rounds 0 原样传。

Co-authored-by: host <host@test.local>
2026-10-27 03:54:48 +08:00
host 1128fd3076 feat: 长任务补齐——流式进度 + 跨进程中断 + worker 超时约定
- 长等待流式进度:run_turn 改 1 秒切片轮询(_wait_turn),每满 --progress-interval(默认 30 秒,0 关闭,CLI > 配置 > 默认)往 stderr 打 turn progress 并往链路文件记 kind=progress;进度绝不走 stdout,不破坏 dispatch JSON 与 goal 结论行/用量行解析(经 cloud 真正则验过)
- 跨进程中断:interrupt 直接不生效时写 <state_dir>/interrupt-requests/<task>.json,属主等 turn 时每秒轮询到后用自己的 live 连接发 turn/interrupt 并记 interrupted;10 秒内未响应也不删文件,属主下次轮询仍处理;接线覆盖 dispatch/run/goal/goal-resume/delegate/parallel/fix-review/run-due;旧 server 不接受 interrupt_file 时退回不传再跑
- cloud worker 超时约定:--task-timeout 0 归一化为 None(不限时,与 host turn_timeout 同约定,Docker 路径仍受 docker_timeout 约束),默认 1800 不变;超时仍记 failed(现有单测合同不动)

测试:host 56 全过(新增 7)、cloud 526 全过(4 跳过,新增 1 个 0=不限时)。
2026-10-27 03:05:15 +08:00
host ede7c6afee feat: 长任务对齐 Codex App + /goal 检查点续跑
- --turn-timeout / turn_timeout 可配置,0 不限时(对齐桌面 App 等待语义);超时回合保留部分文本,任务记 interrupted 而非 failed
- Server.interrupt_turn + host.py interrupt --task(协议 turn/interrupt,无活动回合时不改任务状态)
- goal 循环抽成 run_goal_loop:每轮落盘检查点(含首轮前初始保存),goal-resume 从下一轮续跑;终局 goal-met/goal-unmet 后拒绝续跑
- 回合超时/中断时下一轮提示写明是回合中断而非验收失败;预算耗尽时记 interrupted 不冒充 unmet;--verify-timeout(默认 600,0 不限时,超时记 rc 124)
- 云端解析契约未动:goal met/unmet after rounds、round 行、usage 行格式保持
- 真模型实弹:端到端、断点续跑、正常多轮反馈修复(低档、gpt-5.6-terra)
2026-10-27 02:45:44 +08:00
openqjandhost 799f7c3edb feat: 任务实时链路 SSE (#61)
host.py:回合通知摘要成链路条目,增量追加到 <state>/host-traces/<task>.jsonl(上限 5000 行);dispatch/run/goal/delegate 设链路文件
cloud/server.py:GET /tasks/{id}/trace 快照 + /trace/stream SSE(snapshot → entry → done;worktree 在别机时空链路 + 说明)
console:详情链路区,运行中 fetch 流式渲染(EventSource 不能带 X-API-Key 头),终态走快照;DOM 上限 1000 条
单测:test_host_trace 7 + test_task_trace 6;host 49、cloud 525、deploy 14 全过

Co-authored-by: host <host@test.local>
2026-10-27 02:27:15 +08:00
openqjandhost 6022f1dada chore: default model gpt-6.1-sol -> gpt-5.6-terra (#60)
Co-authored-by: host <host@test.local>
2026-10-27 02:00:09 +08:00
host 63823fc2e3 逐任务真实成本:上游用量事件 + 价目表计价
此前每任务只记一条 estimated(字符数/4,金额为空),注释称
host 协议不暴露真实 token 数。本轮实测推翻了该前提:
- account/usage/read 在中继上是死的(要求 ChatGPT 账号登录),
  不是来源,已在 cost.py docstring 写明死胡同
- app-server 每上游补全发 thread/tokenUsage/updated,last 是该次
  补全的用量;中继 5 调用回合实测 last 之和等于最后 total
  (input/output/cached/reasoning 四路都对得上)

实现:host.py run_turn 从事件窗口按 threadId+turnId 汇总用量,
重试/goal 各轮累加,dispatch/run JSON 与 goal 用量行透传;
sandbox 三路解析(dispatch JSON 解析改用完整 stdout,顺带修
了长 text 下 failed 被误判 completed 的潜 bug);worker 每任务
记一条 actual(重试/fallback 用量累加进同一条),无用量时回落
estimated;cost.py 价目表(默认 OpenAI 挂牌价,env 可覆盖,
无价模型 usd 记 None、报表进 unpriced_actual_tokens 而非 0 美元),
cost_records 加 cached_input_tokens 列(SQLite/PG 两支迁移);
报表与按天行新增 cached_input_tokens/unpriced_actual_tokens。
测试:host 32、cloud 483 全过。
2026-10-26 18:41:27 +08:00
host c114178463 Surface failed-turn reason: host.py recovers it from thread_history, dispatch prints it
Real bad-model turn: relay returns 404, codex retries sampling
5 times, turn fails, but app-server's turn/completed event
carries status=failed with NO error and NO text, so worker
never saw any fallbackable signal. The 404 actually lives in
codex's thread_history_1.sqlite (thread_turns.error_json).
host.py Server.run_turn now best-effort recovers that message
(read-only query by thread_id+turn_id, any failure -> None,
never breaks the turn itself), and dispatch CLI prints the
error field (run CLI already printed the full result). Worker
_attempt_error prefers the error field when text is empty, so
a double failure keeps the real reasons, not just status codes.
Tests: host TurnErrorFromHistoryTest (recovery + missing/corrupt
DB -> None), full host + cloud suites pass.
2026-10-26 06:13:27 +08:00
host cb7e67ba4a CI: also run cloud tests in host-verify workflow
Extend the verify job with a second step running cloud tests, so
CI covers both suites (host 27 + cloud 223, both passing locally
outside the model sandbox; the 40 socket failures reported inside
the sandbox were sandbox artifacts, not code failures).

Generator (host.py HOST_VERIFY_WORKFLOW) and generated workflow
were updated together; template matches the generated file.
2026-10-26 03:07:56 +08:00
host b97c34465f host: GitHub PR/CI line (github-workflow, pr, pr-checks, pr-merge with checks gate) 2026-10-26 01:12:04 +08:00
host 240eaacf50 host: phase2 daemon + health + auto-recovery (restore, recover, double-fork) 2026-10-26 00:57:01 +08:00
host e9735d1fad host: fill second-class gaps (merge verify, deps, review comments, reconnect, budget, notify, state version) 2026-10-26 00:48:27 +08:00
host 5731675153 host: real-backup 2026-10-26 00:16:54 +08:00
host b49b9cf1e7 initial real project snapshot 2026-10-26 00:11:40 +08:00