Commit Graph
201 Commits
Author SHA1 Message Date
host 700641fa37 fix(worker): 无 result_text_full 列的老库/手工表写回回落,不拖垮终局
CI 失败根因:P0 取消守卫测试 mock 掉 _ensure_result_columns
并手工建表(无新列),带新列的 UPDATE 直接
OperationalError。修法:带新列写回失败且错误含
result_text_full 时回滚并用旧 SQL 重写(摘要照写、完整版
丢弃),其它错误原样重抛。
2026-10-30 02:46:37 +08:00
host 92b7f8e8ff feat(tasks): 完整结果另存 result_text_full,详情不再只剩 2000 字
旧链路三层截断:sandbox stdout 截 2000 字、worker 写回截
4000 字,长答案尾部在详情里永远查不到(只把上限调大是
把截断点后移,不是修掉)。修法:截断版保持不变(列表/
摘要用),完整版另存:
- sandbox _result_from_runner/_parse_host_result(local 与
  docker dispatch 路)回包新增 stdout_full(上限 200000 字,
  只防无界增长);goal 路结论在尾部、tail 已保结论,不动。
- worker _write_execution_result 新增 result_text_full 列
  (_ensure_result_columns + server migrate_tasks_table 幂等
  补列),终局与取消分支均透传 stdout_full。
- server:任务 dict 加 result_full_available(不直接塞全文
  进列表负载),新增 GET /tasks/{id}/result-full(read 权限
  同款校验,无完整版时 404,不回摘要冒充完整)。
单测:test_queue_sandbox 45 项(含新增 stdout_full 1 项)、
test_marker_tools 30 项。
2026-10-30 02:45:51 +08:00
openqj 276d433da6 Merge pull request #166 from openqj/feat-console-pagination-tabs
console: 任务列表服务端分页 + 控制台分页面签
2026-10-30 02:38:35 +08:00
host e5b6c45c83 console: 任务列表服务端分页 + 控制台分页面签
任务列表分页(服务端 + 前端):
- GET /tasks 新增 limit(1..100,非法回 400)、offset、
  order=desc、status/executor/q 服务端过滤;回 total(过滤后
  计数)与 status_counts(全项目统计,不受过滤与分页影响,
  数据卡片不再把当前页当成全项目);无 limit 时旧行为不变
  (全量、正序,协调子任务与旧调用不受影响)。
- 前端每页 20 条、最新在前,上一页/下一页 + 页码;筛选与
  搜索改服务端过滤(搜索防抖 300ms),切筛选/切项目/派发
  后回第 1 页,10 秒刷新与刷新按钮停在当前页;新测试
  test_task_list_pagination.py 5 项。

分页面签:
- 顶栏锚点导航改成 5 个页签:工作台(派发 + 任务列表 +
  对话 + 详情)、项目(项目 + 成员)、环境与 Workers、
  技能与自动化(技能 + 自动化 + 分诊)、管理与 Key;
  数据卡片仍在页签上方常驻。选中任务存在 App 层 store
  里,切页不丢,回来仍是同一任务、10 秒刷新续同步;
  页签同步到 location.hash(#tab-workbench 等),刷新与
  分享链接落在同一页签;窄屏工作台仍保持派发在前的顺序。
- 工作台页签仍是左(任务)右(派发/对话/详情)两栏,
  沿用等高内部滚动规则;其它页签单列堆叠。
2026-10-30 02:25:48 +08:00
openqj 6a5a837aac Merge pull request #165 from openqj/feat-doc-tools-result-cap
文档/图片工具进任务镜像 + 结果截断改为提取 text 上限 20000 字
2026-10-30 02:19:46 +08:00
host c1d25d11b1 feat: 文档/图片工具进任务镜像 + 结果截断从截断 JSON 改为提取 text 上限 20000 字 2026-10-30 02:03:14 +08:00
host d5a602f7d3 feat(mcp): 云端 MCP 工具从 8 个扩到 12 个
#159 只包了读 + 建 + 取消,日常闭环还缺:读产物文件、
读完整结果、重试、合并。新增 get_task_files(path 为
worktree 内相对路径,服务端 _safe_worktree_path 防逃逸,
MCP 层不展开绝对路径)、get_task_result_full(依赖 #162
的 /result-full 端点,未部署前该工具回 404 isError,不炸)、
retry_task、merge_task(写操作,服务端状态机校验,非法
转换 409 原样透传,MCP 层不绕过)。
单测 test_mcp_server 10 项(含新增翻译 1 项)。
2026-10-30 02:00:58 +08:00
host 8afcd3253e fix(automations): 审计行不再显式插 TEXT id,PG identity 下不再丢审计
旧 _audit_row 显式插 uuid 当 audit_log.id:SQLite 下能跑,
生产 PG 的 id 是 BIGINT GENERATED BY DEFAULT AS IDENTITY,
插 TEXT 直接报错、审计行全丢(best-effort 吞掉后无声无息,
#156 留下的已知局限)。修法:INSERT 一律不带 id,让两端
自增/identity 生成;表不存在时先按 PG 口径建、再按 SQLite
口径建、逐个重试——automations 按设计不 import 其它 cloud
模块,所以用先插、失败再按口径重试做方言探测。
单测:test_automation_run_due 4 项(含新增 PG-like 假连接
1 项:只接受不带 id 的 INSERT,旧实现在此应失败)。
2026-10-30 01:55:59 +08:00
openqj 9a11ef27b8 Merge pull request #159 from openqj/feat-cloud-mcp-server
feat(cloud): MCP 云端任务入口 + 上游 0.164 对齐配置
2026-10-30 01:55:43 +08:00
openqj 0c458cb2ce Merge pull request #158 from openqj/feat-repo-access-check
feat(repos): 仓库接入预检 + codexsvc deploy key 配给脚本
2026-10-30 01:53:52 +08:00
openqj aa5bf7da72 Merge pull request #157 from openqj/feat-final-audit-detail
fix(worker): 终态审计带上 error,复发卡死可定位
2026-10-30 01:53:39 +08:00
host 972203c38f feat(cloud): MCP 云端任务入口 + 上游 0.164 对齐配置
- cloud/mcp_server.py:stdio MCP 服务,把云端任务 REST 翻成
  tools/list / tools/call(health、list_projects、list_tasks、
  get_task、create_task、cancel_task、get_task_cost、
  get_task_trace),底层只走已有 REST,不另开任务逻辑;
  鉴权只认调用方自己的 CODEX_CLOUD_API_KEY(缺了启动失败,
  绝不打印),服务端权限 403 原样透传;create_task 只传已知
  字段,未知字段丢弃;REST 报错时 isError=true。
  单测 test_mcp_server 9 项(假 transport,不联网、不碰真 key)。
- 上游对齐 #80 的可合部分:config.example.toml 加
  [features] daemon_auto_start = false(防 managed daemon 绕过
  版本 pin)+ docs/upstream-164-alignment.md 对齐表;换稳定版
  二进制是另一次主机侧部署动作,本批不做、不谎称已升级。
2026-10-30 01:41:34 +08:00
host 1ff8906aee feat(repos): 仓库接入预检 + codexsvc deploy key 配给脚本
本机 https 到 GitHub 常被出口策略挡、ssh 才通(2026-10-30
实测),而新项目填 https 私有仓会在 worker 里花几分钟超时
才失败。控制面不存仓库凭证的设计不动,缺的是建项目前的
快速预检与按仓库通用化的配 key 流程:

- cloud.repos.check_repo_access:只跑 git ls-remote HEAD,
  不 clone、不写盘、不改库;失败返回 (False, 原因),https
  失败时追加改走 git@ 形的提示;CLI python3 -m cloud.repos
  check <url>,退出码 0/1,只打印结果与原因。
- deploy/provision_codexsvc_key.sh:主机上以 root 跑,为
  codexsvc 生成/复用 ed25519 key(绝不覆盖已有 key),只
  打印公钥(私钥绝不打印),--check 时以 codexsvc 身份跑
  ls-remote,通了才算配好。
- 内层沙箱边界诚实说明:Docker 任务内层本就是
  danger-full-access(见 sandbox.py 注释),外层 Docker/gVisor
  才是防线;宿主级 bwrap workspace-write 的 fresh proc 在嵌套
  pidns 下是内核 EPERM、/tmp 不隔离——这些是上游 codex 二进制
  与内核的边界,本批不谎称在仓库代码里修掉,只把仓库接入
  这一档做成可重复流程。
单测:test_project_repo 20 项全过(新增 check 5 项)。
2026-10-30 01:39:46 +08:00
host f4fb4000e1 fix(worker): 终态审计带上 error,复发卡死可定位
2026-10-29 任务 A:认领后 61 秒 interrupted,但终态审计只记
task_id、自动续跑又把 error_text 覆盖掉,事后无法区分取消、
配额、host 返回 interrupted 还是其它失败;容器 --rm 后日志
也没了,根因至今无法从留存证据证明(不是 sweep:阈值 300
秒;不是外部取消:该时段无取消审计;是 worker 自己写回
interrupted,但第一轮的 error 已被第二轮 failed 覆盖)。

本批不谎称找到根因,只补可定位性:run_one 终态审计在
interrupted/failed 时 detail 带 error(截 500 字、去换行),
完成态仍只记 task_id;分段审计 worker_exec_start/error/done
已在现 main,复发时最后一段 + 终态 error 即能定位。
单测 test_final_audit_detail_carries_error:失败派发后
worker_failed 审计 detail 以 'A:' 开头且含 error 文本。
2026-10-30 01:38:01 +08:00
host 3a06205d8b feat: 跨 worker 真 kill(kill_requests 意图表 + 收殓器)与自动化触发入闸
机制 A 移植自旧 PR #101(分支 feat-residual-b,头 895e004),
不合旧 PR:旧基线 6e3eb7b 已 CONFLICTING,本批重做到现 main
6d4f87e(#151 合并后)上。

- kill_requests 意图表:cloud/db.py POSTGRES_SCHEMA + migrations
  清单补表;server.ensure_kill_requests_table / request_kill
  (同一任务只留一条 pending,写失败返回 False);init_db 的
  SQLite 分支补建表(PG 分支由 POSTGRES_SCHEMA 全量建表覆盖)。
- 取消/sweep 写请求 best-effort:_cancel_task 在 commit 后调
  request_kill(失败不挡取消);sweep_stale_tasks 在同一事务
  内为被扫任务写 system 请求。
- worker 收殓器 _reap_kill_requests:owner 活着(心跳停滞未超
  300 秒,COALESCE(last_heartbeat, updated_at))绝不抢杀;只
  有 owner 已死且任务仍 interrupted 时,按任务派生容器名
  (executor.container_name,codex-<安全字符任务 id>)在本机
  真杀,绝不按前缀扫、绝不杀自由传入的名字;本机无容器时
  请求照常终结;任务行失踪或已不在 interrupted 时请求直接
  终结;单条异常只记日志。run loop 每轮调一次,放在 sweep
  块外(旧 PR 只在 sweep 块内,sweep 关掉时请求会积压)。
- 自动化入闸只补两条旁路:worker._coord_sync_children 建子任
  务前过 _quota_exceeded(超限跳过建任务、记审计,同一 worker
  内同一分工只记一次);automations.run_due_automation
  (claim 抢单 -> 配额门 -> 建任务 -> 记产出,超限记
  skipped_quota 不进失败重试)。triage/webhook/续跑/后续/
  分叉未动。
- 单测:旧 PR 三份单测原样移植(Worker/Server 签名与现 main
  兼容,未改用例语义)。
2026-10-30 01:32:35 +08:00
host 71e31b22a0 websearch: MCP env 写进租户 config,不只靠 docker -e
2026-10-30 实测:Codex bash 里有 SEARXNG_URL + proxy,
MCP 链却只有 duckduckgo、DNS 瞬间失败——Codex 的 MCP
子进程不继承 Codex 进程 env;给租户 config 加
[mcp_servers.websearch.env] 后同一任务立刻返回真实
SearXNG 结果。修法:worker 在 _docker_task_context
算出网关后、容器启动前,用 ensure_websearch_mcp_env
把 proxy + 搜索 env 写进租户 config(网关此时才已知,
播种时写不死;写失败只记日志不阻塞)。
测试:新增 test_websearch_mcp_env 4 项,相关共 33 项全过。
2026-10-30 01:16:49 +08:00
host 08d56b15ce websearch: SearXNG 默认只查 bing,360search
默认 general 类别在 Docker 任务容器里要 20.0 秒才回
(wikipedia/wikidata/brave/duckduckgo/google 引擎全超时,
只有 bing+360search 回),撞 MCP 默认 20 秒超时;随后
DuckDuckGo 经代理也超时,整条链失败。代理本身无问题
(Tinyproxy 白名单早已放行 searx 与 duckduckgo)。
修法:_provider_searxng 默认 engines=bing,360search,
SEARXNG_ENGINES 可覆盖、空值恢复旧行为;worker 搜索
env 透传补 SEARXNG_ENGINES。容器实测:钉住后 0.26 秒
回 3 条(首条北京天气),auto 链 0.15 秒成功;旧 20.0 秒。
测试:新增 test_websearch_searxng 3 项,MCP/worker 共
21 项全过。生产未部署。
2026-10-30 01:16:49 +08:00
host 378cddfb1e align: 修正技能播种路径:codex-home/skills/ 才是用户技能目录
2026-10-30 主机实测(临时 codex-home,生产未动)纠正:
先播 .agents/skills/ 时 skills/list 只返回 5 个系统技能,
3 个自有技能一个没出现;改播 skills/ 后返回 8 个,
3 个自有技能以 scope=user 出现。系统技能本身就在
skills/.system/ 下。
- skill_seed DEST_SUBDIR 由 .agents/skills 改为 skills,
  模块文档与 worker 注释同步修正。
- 测试期望路径同步改为 skills/。
- 对照表补实测结论与附带发现:Codex 自带系统技能
  imagegen,图片生成在 Codex 本体里不是无技能。

验证:skill_seed + chatgpt skills + websearch worker
共 14 项全过。主机探针文件已全部清理,生产未动。
2026-10-30 01:16:49 +08:00
host cf9d17f035 align: 第二批:把技能包播种进租户 codex-home
仿 websearch MCP 播种,默认关:
- cloud/skill_seed.py:cloud/skills/ 的 SKILL.md 拷进租户
  codex-home/.agents/skills/(个人级技能路径,见模块
  文档的路径依据与诚实边界);幂等,租户改过的文件
  绝不覆盖(上报 differs),缺包不阻塞任务。
- cloud/worker.py:新增 --seed-skills,_tenant_dirs 里
  调用播种;老租户下一次任务自动合并;播种失败只记
  日志、不阻塞任务(技能是可选流程说明)。
- cloud/tests/test_skill_seed_worker.py:播种助手 + worker
  开关,仿 test_websearch_worker.py 的结构。
- docs/chatgpt-codex-alignment.md:补第二批说明与实测
  待办(真实 Docker 任务 skills/list + $live-info 调用,
  通过前不宣称生产任务已能用这些技能)。

验证:skill_seed + chatgpt skills + websearch worker
共 14 项全过。本批不改生产配置、不部署,autoscaler
加 --seed-skills 是部署时另行的主机步骤。
2026-10-30 01:16:49 +08:00
host 8a26d1973c align: ChatGPT Codex 对齐第一批:技能包 + 对照表
对照用户提供的 ChatGPT Codex 工具目录,区分可对齐与
不照搬:核心编程工作流已对齐,ChatGPT 托管专属
(CUA、宠物、Docs/Sites 连接器、图片生成模型)不在
开源宿主编排层范围,不凭空宣称对齐。

第一批只补技能层,不改 worker、不改生产行为:
- cloud/skills/live-info:天气/时区/股票/体育,查一级
  来源、带来源和快照时间,绝不编数字。
- cloud/skills/document-tools:LaTeX/Word/PDF/表格/
  演示文稿,先核查工具、缺了明说、产出后重读验证。
- cloud/skills/image-workflow:已有图片处理 + 生成工具
  已配置时才生成,未配置时明说、绝不顶替。
- docs/chatgpt-codex-alignment.md:逐项对照表与不照搬
  的诚实边界,方向为自动化编程任务平台。
- cloud/tests/test_chatgpt_codex_skills.py:校验技能
  frontmatter、名字与目录一致、正文非空、无密钥占位。

验证:新测试 2 项全过。播种进租户 codex-home、 worker
改动、CUA/连接器/图片模型均不在本批,另行评审。
2026-10-30 01:16:49 +08:00
openqj fd9fe5f254 Merge pull request #153 from openqj/feat-auth-unknown-role
fix(auth): unknown project role denies instead of KeyError 500
2026-10-30 00:55:54 +08:00
host c823e32336 fix(auth): unknown project role denies instead of KeyError 500
check_permission now returns False for roles not in _ROLE_LEVEL
(e.g. a directly-written 'member' row, which set_member itself
rejects but legacy/direct DB rows can still carry), so the failure
mode is PermissionError/403, never an unhandled 500. Regression test
test_unknown_role_denied_not_500 fails with KeyError on old code.
2026-10-30 00:49:03 +08:00
host 0f7162934b console: 任务详情也显示首字、总耗时、token
列表行与详情共用同一口径:新抽 api/taskMeta.ts(首字/
总耗时/token 计算),TaskListSection 改 import 它(逻辑
原样不动),TaskDetailPanel 在状态行下方加一行:首字
方块 + 耗时 + token,title 里写口径。
详情的数据缺口一并补上:单任务接口 _get_task 此前走
_task_to_dict 默认 total_tokens=None,详情 token 只能
显示占位;现与列表一样用 _task_tokens_map 一次聚合
(单任务只多一条查询),详情直接出真数。
types.ts 里过期的“列表不回 token”注释同步更新。
新测试:单任务 GET /tasks/{id} 聚合回 total_tokens。
2026-10-30 00:39:13 +08:00
host 4cbdc41e20 fix: task files endpoint resolves tenant docker worktrees
GET /tasks/{id}/files previously returned 409 for all docker tasks
on the grounds that tenant docker shares /app/work per project.
Production inspection shows the worker creates a per-task
host-worktrees/<task-id> subdirectory under the tenant work dir
(62 such dirs under the wwww tenant), so per-task reads are
attributable after all: the endpoint now resolves tenant docker
tasks via tenants/<project>/work/host-worktrees/<task-id> (local
worktree as fallback), never serves the shared /app/work mount
itself, and still 409s cloud/cross-host tasks. Docs and the
harness2_tasks boundary notes are updated to match.
2026-10-29 23:16:01 +08:00
host 9eedee5a3d server: 任务列表一次聚合回 total_tokens
_list_tasks 在取附件的同时用 _task_tokens_map 一次查
cost_records 按 task_id 求和(GROUP BY),一屏只多一条
查询,不按任务逐个调成本接口。无记录计 0、表不存在时
全计 0 且列表不失败;_task_to_dict 新增 total_tokens
参数,默认 None(非列表调用方不把“未算”写成 0)。
前端 TaskListSection 的 token 栏位此前已接好 total_tokens,
补上后列表行直接显示真数,不再显示 token —;前端注释
与占位文案同步更新。
新测试 test_task_list_tokens.py 3 项:HTTP 列表聚合
(actual+estimated 加总、无记录为 0)、缺表不炸、
_task_to_dict 默认 None。
2026-10-29 18:27:53 +08:00
host 6f0eec71b6 worker: cover goal docker paths with prestart stage logging
PR #145 added docker_run_* lines only to _run_in_container, but
goal/goal-resume/goal-resume-breakpoint call _call_runner directly
(to keep full stdout for goal conclusions), so the production live
test (a goal task) produced zero docker_run_ lines. Extract a
_docker_call_staged helper and route all four task paths through it;
error lines log the class name then re-raise so each caller's
kill+failed handling is unchanged.

Also fix audit attribution: audit_log has no task_id column, and
worker_exec_done/error details carried no task id, so per-task
queries missed them (the rows existed). All three exec details now
start with task_id=. Tests: 2 new goal-path stage cases + a done
detail task-id assertion (8 total in test_prestart_stages).
2026-10-29 17:46:12 +08:00
host 9d90d97046 worker: prestart stage logging for dispatch stalls
Claim-to-dispatch stalls (cf. 2026-10-29 task A: 61s to interrupted
with zero detail) were invisible because docker run blocks with
captured output and watchdog kills were silent. Add three layers:
- worker_exec_start/done/error audit rows in _run_attempt (PG,
  survives --rm), start/done pairing shows whether an attempt died
  inside the executor call;
- docker_run_start/done/error log lines in DockerExecutor (container
  + image only, never the inner command/prompt);
- stdout tail (last 2000 chars) to the worker log on failed or
  interrupted results, and a log line on both watchdog kill paths.
Tests: cloud/tests/test_prestart_stages.py, 6 cases incl. a
no-prompt-in-logs assertion.
2026-10-29 17:07:53 +08:00
openqj 0129b0149a Merge pull request #144 from openqj/feat-neverstart-fresh
worker: 从未启动的任务自动续跑回落全新跑
2026-10-29 16:51:42 +08:00
host e74f20608b worker: auto-resume falls back to fresh goal for never-started tasks
2026-10-29 live test: task A was interrupted 61s after claim but had
never written anything (no host-state entry, no worktree, no trace,
no checkpoint); auto-resume ran goal-resume, which host rightly
refused, and the task failed. Refusal only makes sense for tasks
with progress but a lost breakpoint (those go through the P0
checkpoint rebuild); for a never-started task the only meaningful
outcome is a fresh run, identical to its first attempt (same task
id, same goal, worktree does not exist yet so nothing is
overwritten).

Change (claim path only, via _resolve_breakpoint_resume):
- rebuild from checkpoint first, unchanged;
- if rebuild fails AND _task_never_started holds (state entry,
  checkpoint row, worktree dir, trace file all absent), clear the
  breakpoint flag so the run takes the fresh goal path, with a
  worker_fresh_restart audit event;
- any evidence of prior execution, or any check failure,
  keeps the old refusal path (fail-closed); the auto-resume
  count cap and no-progress stall guard still bound retries.

Tests: cloud/tests/test_neverstart_fresh.py, 10 items (fallback
holds, entry/checkpoint/worktree/trace presence each block it,
corrupt state blocks it, rebuild still wins when a checkpoint
exists); test_goal_resume's breakpoint-routing test now sets up
a task with a checkpoint so it still exercises goal-resume.
Full suites: cloud 1178 (skipped 4), host 104, deploy 39, green.
2026-10-29 16:45:07 +08:00
host c20192bce8 harness2: per-role models, sweep batch, task file assets endpoint
- Role models: run_improvement_via_tasks gains role_models
  (probe/editor/selector/reflector) plus --model-<role> CLI flags;
  missing/empty falls back to shared --model, both empty means
  server default, so default behavior is unchanged.
- Sweep: run_sweep + examples/harness2_sweep.py manifest CLI with
  shared library_root (library restore), per-run failure isolation
  and sweep-report.json summary; preflight gates task creation.
- Deliverable assets: GET /tasks/{id}/files for local-executor
  tasks (safe path resolution, UTF-8 text <=256KB, listing <=200;
  docker/cloud/cross-host 409 with explicit reason, since tenant
  Docker shares one /app/work per project and per-task attribution
  would misattribute); probe rollout deliverable_files from the
  endpoint plus DELIVERABLE FILE reply blocks (endpoint wins on
  collision, bad blocks dropped), surfaced in
  deliverables/<label>/files/ context and full_result summary.
- Docs: harness2.md comparison table updated for all three items.
2026-10-29 16:32:58 +08:00
openqj 7425f2e778 Merge pull request #141 from openqj/feat-harness2-gaps
harness2: upstream gap fill — trivial fix, session budget, tag validation, preflight
2026-10-29 15:40:09 +08:00
host 4ed1479d7a harness2: upstream gap fill — trivial selection fix, session budget, run_tag validation, preflight check
- select_final trivial condition now k==1 and single-candidate pool,
  regardless of whether a selector is provided (previously gated on
  selector is None, so wired runs always paid for a useless selector
  task at k=1)
- Budget gains max_sessions gate (probe + stage-attempt sessions;
  fmt2 corrections and selector/reflector sessions exempt), the
  backstop for unpriced models where the USD gate never binds
- validate_run_tag (charset, no leading '-', no s<N> seed prefix)
  enforced at run_improvement_via_tasks and the CLI
- CLI --check preflight (base harness, source dir, prompt, tag,
  control-plane /health probe) without creating tasks
- ModeResult.to_dict now includes rollout summaries so a lost probe
  is visible in full_result instead of only on the server
- CLI plan counts the selector only when k>1
2026-10-29 15:31:58 +08:00
host 8c719c7e62 断点镜像状态标签回落:host-state 条目写 status 不是 goal_status
生产实弹:极小 goal 任务的 timeline 里 checkpoint goal_status 为
None——host.py 的任务条目只有 status(goal-running/goal-met),
没有 goal_status 键,镜像原样读 goal_status 永远是空。修法:
entry.get('goal_status') or entry.get('status')。轮次、thread_id、
state_sha 等关键字段不受影响(熔断只看轮次),纯标签 fidelity
修复;单测加 status-only 条目镜像断言。
2026-10-29 15:31:07 +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
openqj d56bd3a99e Merge pull request #138 from openqj/feat-harness2
harness2: recursive harness optimization wired to real codex tasks
2026-10-29 14:28:44 +08:00
host 42abe3cc5b harness2: recursive harness optimization wired to real codex tasks
Port Google Research's Harness^2 method layer (model weights frozen,
only the surrounding harness is optimized): Propose-Probe-Compose with
INV/DOM/AGG editors, parallel/sequential modes, per-domain experience
library, anonymous-product selection, k<=3 hard cap and budget gate.
Execution goes only through the existing control-plane API (upload ->
task with idempotency key -> poll -> trace/cost -> cancel on timeout);
no new server endpoints, no codex flags composed, tasks inherit the
normal executor/sandbox/quota/audit path.

Wiring lessons from four real rounds against production (project
wwww, ledger task): HttpTaskClient retries transient disconnects and
502/503/429 (3 attempts); editors get a worked file-block example, an
explicit <no-change> marker and one feedback retry (.fmt2) instead of
silently accepting notes-only replies; run_task unwraps the server's
result_text JSON envelope (text field holds the real reply) — without
this, line-anchored file-block parsing could never see blocks the
agents were actually emitting. Final round (real4) fully green:
all stages landed, selection non-fallback, experience入库, probe
delivers the correct signed total 85.15.

Tests: 35 harness tests (17 method + 18 wiring incl. fake-control-plane
full round, idempotent replay, retry, format retry, envelope round).
Deliberate deviations from upstream are documented in docs/harness2.md
(no network-unrestricted/sandbox-disabled defaults, auto_apply off,
no benchmark/scorer, probe deliverable = final reply text).
2026-10-29 14:21:18 +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 5b2bdc9bbf fix: Docker 断点续跑透传密钥环境(SUB2API_API_KEY 缺失空转修法)
Docker goal_resume_breakpoint_task 是唯一漏传
env=self._docker_cli_env(extra_env) 的 goal 方法:密钥只按名
进 docker run,值靠 CLI 环境带;漏传导致容器内 key 为空,
断点后每轮瞬间报 Missing SUB2API_API_KEY、无限空转
(2026-10-29 长任务实弹:274 轮全失败)。

单测:断言断点方法调用 runner 时 CLI 环境含密钥值,
命令里密钥只按名出现不带值。
2026-10-29 12:42:19 +08:00
host c9cfe22a36 feat: 长任务中断自动恢复,仅用户暂停才暂停,不限时
- 新增 tasks 列 paused_by_user/interrupted_reason/auto_resume_count/no_time_limit(幂等迁移,server+worker 双侧)
- POST /tasks/{id}/pause:用户暂停标 interrupted + paused_by_user=1;/cancel 同款标记;人工 /retry、/goal-resume 清标记
- sweep 标 sweep、配额标 quota、协调员孤儿标 coordinator_orphan、worker 退出标 worker_shutdown
- worker.run_loop 每轮后 auto_resume_interrupted:只接系统中断的 goal 任务,CAS 回 queued + goal_resume_pending=1(G10 断点续跑),用户暂停/配额/孤儿/非 goal 不接,上限 50 次防无限烧额度
- no_time_limit=1 的任务 worker/Docker 级不限时(其它任务仍默认 1800 秒;host 单轮 600 秒仍是单轮护栏)
- 前置 cherry-pick:G10 断点续跑透传 model_provider(否则 Docker 断点续跑在 sub2api 下必死,自动恢复无法实证;本 PR  supersedes #127,勿另行合并 #127)
2026-10-29 11:37:06 +08:00
host a19b1567bb fix: G10 断点续跑透传 model_provider(sub2api 续跑失败修法)
断点续跑容器不带 --model-provider 时 Server 的 provider 表里
没有 sub2api,thread/resume 报 provider not found(probe15 实弹)。
Docker/Local 断点方法加可选 model_provider 并转发,worker 两处
调用点传 gateway_provider;补单测断言断点命令含
--model-provider sub2api(旧代码下两新测试 1 失败 1 报错)。
2026-10-29 11:28:39 +08:00
host f02c513729 chat: 持久化继续消息,chat-history 交替历史
基线 main 811912c(PR #125 Muse 风格之后):服务端与单测与
9538825 相同;React 侧在 Muse 风格 ContinuePanel 上重放
chat-history 集成(新版输入框占位文案保持 Muse 风格的“消息”),
console.html 由新版 src 重新构建。

- 新表 task_continue_messages:POST /continue 的用户消息与状态改回
  queued 同一事务落盘,409 被拒不留 phantom 消息
- GET /tasks/{id}/chat-history:prompt + 继续 + 后续消息 + 链路按
  时间交错拼好返回;部署前继续以 legacy_continue_count 如实标注
- React ContinuePanel 优先用 chat-history,旧服务端自动回退
2026-10-29 07:01:57 +08:00
host ebc5624893 console: React 生产级重写(左列表 + 中对话 + 右详情)
旧单页 console.html 换成 Vite + React + TypeScript 单文件构建产物
(309525 字节,sha 90345ec8…,重建字节一致),源码在
cloud/console-react/,改页面只改 src 再 npm run build:console,
产物不手改。布局:左侧列表(模块导航 + 任务列表)、中间标准对话
(实时链路时间线 + 继续会话聊天框,共用 Chat 组件)、右侧详情
(元数据、操作、评审、审批、协调子任务、子代理、线程)。
API 与后端语义未改,key 只存浏览器 localStorage。
6 个 console 标记单测随实现改名更新(意图不变:壳确实调了
这些接口、turn 信息仍在详情里、旧 goal 输入仍不存在);
P1 标记单测未动且通过。
2026-10-29 06:32:38 +08:00
host ebfae7b992 worker: always start capacity pool threads in run_all
run_all previously sized its pool from the ready snapshot at round start (min(capacity, ready_count)). If a round started with only 1 ready task (e.g. a just-claimed PM), only 1 thread ran; that thread entered a marker wait (up to 3600s) after its model turn, and children created/retried mid-round had no free thread to claim them. run_loop is blocked joining that round, so no new round starts; if the other worker had already scaled down, the queued child waited out the full wait timeout unclaimed. Fix: always start capacity threads. Idle threads exit immediately when no task is ready and nothing is active, and poll every 0.2s while something is active, so mid-round arrivals are claimed. Test: run_all with capacity=2, 1 initial task whose mocked run_one creates a child then polls for it; without the fix the PM poll times out in 5s (1 result), with the fix both tasks dispatch in the same round (0.2s).
2026-10-29 05:41:35 +08:00
host 14757f15c4 feat: 子任务默认模型/强度(child_model/child_effort)
主任务可显式声明派生默认,或在 prompt 里写“子任务用 gpt-6-luna max”
由服务端解析填入;marker task-create 与协调员分工未显式写模型/强度时
按 块/分工 > 派生默认 > 父任务模型/强度 继承,fork 复制派生默认,
continue 可改派生默认;console 新建/继续表单与任务详情同步展示。
2026-10-29 05:27:22 +08:00
host ccb22c960d worker: heartbeat parent task during marker poll/wait loops
Marker waits run after _run_attempt has cleared _running, so the run_loop heartbeat thread no longer refreshes the waiting parent. sweep_stale_tasks (default 300s) then kills a parent waiting up to the 3600s marker timeout, as seen in the marker live test (PM2 swept mid wait-threads). Poll and wait loops now heartbeat the parent each iteration (interval clamped to <=60s, well under the 300s threshold), best-effort, without touching _running, so no new capacity leak and cancel still works (empty _running entry has no proc/container to kill). Tests: 2 new sweep-survival tests (stale heartbeat at -299s, 2s wait, sweep must spare the parent); both fail 2/2 on pre-fix code and pass with the fix.
2026-10-29 04:40:47 +08:00
host b411a97774 worker: release running slot after coordinator completes
Coordinator tasks bypass _run_attempt, so their _claim placeholder in _running was never cleared: each completed coordinator permanently reduced the worker process effective capacity until it could claim nothing, while heartbeat stayed fresh and the capacity gate returned silently. This caused the 2026-10-28 40-person stall (autoscale-1 had one leaked slot, then claimed coordinator A and filled up) and the old 18-person C-6 never-claim (autoscale-1 leaked both A and B). Fix: wrap the run_one coordinator branch in try/finally _running_clear, so normal and exception paths both release. Also log capacity-gate refusals (worker_claim_refused_capacity) throttled to once per 30s; throttling applies to the log only, refusal always returns False. Tests: coordinator completion releases slot, next claim succeeds, capacity-log throttle; both leak tests fail on pre-fix code.
2026-10-29 02:42:52 +08:00
host 37b47cadc6 autoscaler: guard scale-down against live coordinators and busy victims
Two veto layers in reconcile(), worker.py untouched:
- Global live-coordinator veto: a running task whose
  coordinator_parts parse (via cloud.coordinator.parse_parts)
  to a non-empty list and whose last_heartbeat is within
  120s blocks all scale-down this round; low_streak is
  frozen, not reset, so scale-down resumes on the next
  low-depth round once the veto lifts.
- Per-victim busy veto: a LIFO victim whose workers row
  is alive with running>0 is skipped, but only when the
  DB also has a live running task (ghost registry rows
  with no live DB task do not block).
- Coordinator-veto query failure fails closed (no stop,
  guard error recorded); victim-veto read failure fails
  open for that layer only.
- A missing/stale workers row is treated as idle.

Tests: 7 new ScaleDownGuardTestCase cases (live/stale
coordinator, non-coordinator, invalid parts, busy/idle
victims, ghost registry, streak freeze); cloud suite
1077 tests OK (4 skipped).
2026-10-29 00:19:30 +08:00
host 5d8403e756 fix(db): register auto-memory tables in _OR_REPLACE_PK
Postgres 翻译层对未登记主键的表把 INSERT OR REPLACE 退化为
ON CONFLICT DO NOTHING,致使 memory_settings、task_memory_flags、
memory_extraction_log 首写之后的所有更新静默丢弃(2026-10-28
生产实测:提取日志永远停在首写原因)。补三张表主键登记,并
在翻译层加回归测试(SQLite 原生 OR REPLACE 测不出来)。
2026-10-28 22:49:06 +08:00
host d7f0f1be96 fix: pass search env into Docker task containers (SEARXNG_URL was lost)
Docker tasks do not inherit the worker process environment, they only
get extra_env (-e). SEARXNG_URL in the worker unit EnvironmentFile
therefore never reached codex -> MCP inside the container, so the
auto chain skipped SearXNG and only DuckDuckGo was tried (live e2e:
'all search providers failed: duckduckgo (DNS)').

_docker_task_context now merges any set SEARXNG_URL /
PERPLEXITY_API_KEY / EXA_API_KEY / TAVILY_API_KEY / BRAVE_API_KEY
from the worker environment into extra_env (values never logged;
paid keys follow the same extra_env path as gateway keys).

Tests: 2 new docker-env tests; websearch + tenant isolation = 25
passing; full cloud suite 1038 passing (4 skipped).
2026-10-28 22:17:19 +08:00