Files
codex-app-server/docs/thread-wait-ops.md
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

10 KiB
Raw Permalink Blame History

线程等待与状态读取:与 Codex app 的对照

本批实现把 Codex app 的 8 个线程/等待类工具落到本控制面。 原则:只读的绝不写(不重跑、不批准、不合并),等待的绝不 创建任务或发指令;所有“状态”都来自已有记录(任务行、 trace、审批表、后续表、git 目录),没有的字段明说没有, 不编造。

三类

类 官方工具 本端点 说明
管理并行 Agent wait_threads POST /tasks/{id}/wait 等一批线程的首个重要变化
管理并行 Agent read_thread GET /tasks/{id}/thread/read 深入查看单个线程的摘要
管理并行 Agent send_message_to_thread POST /tasks/{id}/thread/message 向已授权线程发后续指令
读取任务状态 get_handoff_status GET /tasks/{id}/handoff 移交到另一主机后的进度
读取任务状态 get_worktree_creation_status GET /tasks/{id}/worktree worktree 准备状态
读取任务状态 read_pull_request_check_diagnostics GET /tasks/{id}/pr/checks PR 的 CI 诊断
持续监控/等待 automation_update GET/PATCH /projects/{id}/automations/{aid} 长期自动化的查看与修改
持续监控/等待 clock.sleep POST /tasks/{id}/sleep,host sleep 一次性等待后继续

配套:POST /tasks/{id}/handoff 与 POST /tasks/{id}/handoff/{hid}/report 是移交的登记与上报口(官方是“先发起 handoff 再轮询”, 本批把发起与上报也补齐,否则 GET 无数据可轮询)。

对照表

wait_threads — POST /tasks/{id}/wait

  • 入参:task_ids(可选,缺省取锚任务的 coordinator 子任务), timeout_seconds(缺省 30,上限 120),after_cursor(可选)。
  • 一次最多 8 个线程,超了回 400(不静默截断)。
  • 只返回第一个发生重要变化的线程:完成/失败等终态、 需要用户输入(elicitation 审批)、需要审批(其它审批)、 需要继续处理(待后续消息)。变化判断按 task_ids 顺序, 不是按真实时间戳排序。
  • 无变化时超时回当前进度(progress 全量 + timed_out=true)。
  • after_cursor:上一次返回的 cursor 只含各任务的状态计数, 不含正文;带它再等时,已报过的变化不再重复报。
  • 它不创建任务、不发指令,只等和收集状态。
  • 诚实边界:变化检测只读数据库(任务行、审批表、后续表), 不轮询 trace 文件;跨任务顺序是调用方给的顺序。

read_thread — GET /tasks/{id}/thread/read

  • 返回:状态映射(queued→waiting,completed/merged→completed, failed/merge-failed→failed,interrupted→interrupted)、 线程摘要、最近活动(trace 末 10 条)、改动文件(trace 里 kind=file 的路径去重,上限 50)、执行命令(kind=exec 的 命令与退出码,末 20 条)、待审批、待后续、Agent 链、 阻塞/需要决策/需要处理/需要指令的 attention 汇总。
  • 线程取不到时 thread=null + note,仍回 200(与 /thread 的 409/502 不同:读摘要不该因为线程缺失而全单失败)。
  • test_results 恒为 null + 说明:trace 里没有独立的测试 结果记录,测试只能从命令退出码与 Agent 消息里看,不编 “测试通过”。
  • docker 执行器任务没有本机线程,thread=null(这是预期, 不是故障)。

send_message_to_thread — POST /tasks/{id}/thread/message

  • 入参:body(必填,上限 20000 字),mode(queued|direct, 缺省 queued)。
  • 授权模型:API 侧要求 API key + 项目 create_task 权限, 并记审计 thread_message_sent;“用户在聊天里让我协调” 本身不是 API 权限,调用方必须持 key。
  • 排队/运行中任务:写入 task_followups(pending),direct 模式同时追加一条直接 trace。
  • 已完成/失败/中断的任务:不是 409,而是复活——用 CAS 把状态改回 queued、清空旧结果与旧错误、写入 resume_message; 若已有待复活消息或状态已变,回 409(不双重复活)。
  • 已合并任务回 409:worktree 已删,应 fork 新任务而不是复活。
  • 目标任务(goal/verify)回 409:目标任务不接受后续消息。
  • 发给其它任务的消息需要用户明确授权;本任务内按用户指令 协调。跨项目发消息不在本端点(走 peer_messages 的 /messages,跨项目会被拒绝)。

get_handoff_status — GET /tasks/{id}/handoff

  • 移交状态:pending / accepted / running / completed / failed; 终态不可再变,同态重报只算进度更新;进度 0–100,不因 终态强制改成 100。
  • 同一任务同一时间只能有一个未关闭移交,否则建移交回 409。
  • 响应含 current_host 与 arrived(当前主机是否等于目标主机): 这是“到了”的证据。
  • 诚实边界:服务端只登记意图与轮询上报,不执行 SSH 迁移、 不搬文件;实际迁移由目标主机的 worker 执行并回 report。

get_worktree_creation_status — GET /tasks/{id}/worktree

  • 状态:remote(worktree 在别机)/ ready(目录 + .git)/ present_no_git / creating(排队或运行中且目录未建)/ missing(终态且目录未建;已合并任务删 worktree 是设计, 不是丢失)/ unknown。
  • 本机且已就绪时附带当前分支(git branch --show-current, 失败则 null,不阻塞整单)。
  • 诚实边界:worktree 创建在本系统是同步的,没有可观测的 “接收对象 / 更新文件”阶段流,不编百分比。

read_pull_request_check_diagnostics — GET /tasks/{id}/pr/checks

  • 只读:不重跑检查、不批准、不合并。
  • 两层取数:gh pr checks --json(workflow、link、description 即哪个 Action 失败与失败摘要),JSON 失败时回落制表符解析; 有仓库 + head sha 且有失败项时,再走 check-runs API 拿 失败日志原文(output.text,截 4000 字)与文件行号注释 (annotations:path / start_line / message,上限 20 条, 失败 run 上限 5 个)。
  • check-runs 一层失败时不 502 整单:checks 本身已够回答 “哪个检查失败”,此时 check_runs 回 available=False + note。
  • 无 PR 时 409;worktree 在别机时 409(gh 必须在 worktree 所在主机跑;诊断是只读拉取,不排队等对方 worker)。

automation_update — GET/PATCH /projects/{id}/automations/{aid}

  • GET 回定义 + 近期运行(运行取不到时 runs=[] + note, 不 502 定义本身)。
  • PATCH 只接受 name / prompt / interval_seconds / repo_url, 其它键静默忽略;无可更新字段回 400;已取消的自动化 回 409(取消是终态,更新不能复活);同项目同名冲突回 400。
  • 更新不重置 last_run_at:改间隔只影响下一次到期,绝不 触发一次立即运行。
  • 诚实边界:“变化才通知”未实现:每次运行终态仍按现有 worker 推送通道通知,没有“与上次输出比对、无变化静默” 的抑制逻辑。

clock.sleep — POST /tasks/{id}/sleep 与 host sleep

  • API:read 权限;seconds 缺省 5,只接受 0–60 的数,超出 回 400(API 层不静默截断);睡的是调用方的等待,不暂停、 不加速、不改动任务本身;返回时重读任务状态。
  • host:host.py sleep --seconds N,0–60,超了退出码 1; Ctrl-C 打印已睡秒数 + interrupted=true,退出码 130。

10 步协调流程(映射到端点)

  1. 主 Agent 明确需求、影响客户端、验收标准 → 写进 POST /tasks 的 prompt(目标任务用 verify_command)。
  2. 为 Android、iOS、macOS、Web 或后端分别派发子任务 → POST /tasks,parent_task_id 指向主任务(coordinator)。
  3. 每个子任务在独立 worktree 或明确分支中开发 → 任务 worktree 由 worker 分配;GET /tasks/{id}/worktree 查。
  4. 使用 wait_threads 等待任务完成或遇到阻塞 → POST /tasks/{主}/wait(缺省即等 coordinator 子任务)。
  5. 使用 read_thread 查看详细结果 → GET /tasks/{子}/thread/read。
  6. 对失败任务使用 send_message_to_thread 要求修复 → POST /tasks/{子}/thread/message(失败任务会复活续跑)。
  7. 汇总各客户端改动并检查共享 API、事件和数据结构 → 各子任务的 read_thread 摘要 + result_text 人工/主 Agent 对照。
  8. 运行跨客户端构建、测试和集成验证 → 新建验证任务(或目标任务的 verify_command),再走 4–6。
  9. 读取 PR 的 CI 诊断并继续修复 → POST /tasks/{id}/pr 建 PR 后,GET /tasks/{id}/pr/checks, 失败项用 thread/message 发回修复。
  10. 最后由主 Agent 汇总变更、证据和剩余风险 → 各任务 result_text + trace + 本对照表的诚实边界。

派发模板(映射到 POST /tasks 字段)

  • 任务:prompt 首行写清任务名与一句话目标。
  • 客户端:prompt 内写明(Android / iOS / macOS / Web / 后端), 本系统没有独立的客户端字段,不要另建字段。
  • 仓库:项目 repo_url(建项目时定);子任务不另设仓库。
  • 基线提交:写进 prompt(如“基线 abc123”),或 environment 的分支字段;没有独立的基线字段。
  • 要求:写进 prompt(复用现有组件、不新建导航页等约束)。
  • 验收:写进 prompt;目标任务另用 verify_command 做自动验收。
  • 返回:任务完成时 result_text + trace;read_thread 再给出 改动文件、命令与退出码、待处理项。主 Agent 按“改动文件 / 执行命令与结果 / 未解决问题”三项核对,不只看一句“已做完”。

未实现 / 未宣称

  • worktree 创建阶段流(接收对象、更新文件百分比):无。
  • automation 的“无变化静默”抑制:无。
  • 测试结果独立记录:无(只有命令退出码与 Agent 消息)。
  • handoff 的实际迁移执行:服务端不做,靠目标主机 worker 上报。
  • wait 的 trace 级变化检测:无,只有数据库级变化。
  • 自动注入身份/记忆上下文到任务:不在本批(见 identity-memory)。