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
This commit is contained in:
host committed 2026-10-28 04:33:20 +08:00
1 parent 38c0797cb3
commit 755029f220
8 files changed
+2535

No files matched your search

+59
View File
@@ -324,6 +324,65 @@ def cancel_automation(conn, automation_id, now=None):
return True, automation_to_dict(row, now)
def update_automation(conn, automation_id, fields, now=None):
"""更新自动化定义(automation_update):fields 只认
name / prompt / interval_seconds / repo_url 四个键,
其它键一律忽略(不报错、不存入,避免调用方拼错字段
名后以为已改);四个键全没给时抛 ValueError(空更新
不是成功)。校验与创建同款(_validate_fields,名字
仍按“同项目 active 名字唯一”查重,改名撞名时 400)。
返回 (ok, auto):行不存在时 (False, None);已取消时
(False, 旧 dict)——取消是终态,改已取消的定义必须
重建,不能靠 update 复活(与 cancel 的软删除语义
一致)。成功时 commit。注意:更新不重置 last_run_at /
last_claimed_at——改间隔只影响下一次到期判断(next_run
由 last_run_at + 新间隔算),不立即触发一轮;想立即
跑一轮应走执行器的 claim 路径,不是改定义。"""
if now is None:
now = time.time()
ensure_automations_tables(conn)
row = conn.execute(
"SELECT * FROM automations WHERE id = ?", (automation_id,)
).fetchone()
if row is None:
return False, None
row = _row_dict(row, AUTOMATION_COLUMNS)
if row["status"] != ACTIVE:
return False, automation_to_dict(row, now)
if not isinstance(fields, dict):
raise ValueError("fields must be an object")
current = automation_to_dict(row, now)
name = fields.get("name", current["name"])
prompt = fields.get("prompt", current["prompt"])
interval = fields.get("interval_seconds",
current["interval_seconds"])
repo_url = fields.get("repo_url", current["repo_url"])
if not any(k in fields for k in (
"name", "prompt", "interval_seconds", "repo_url")):
raise ValueError("no updatable field given")
name, prompt, interval, repo_url = _validate_fields(
name, prompt, interval, repo_url)
if name != current["name"]:
clash = conn.execute(
"SELECT id FROM automations WHERE project_id = ?"
" AND name = ? AND status = ? AND id != ?",
(row["project_id"], name, ACTIVE, automation_id)
).fetchone()
if clash is not None:
raise ValueError(
"automation name already exists in this project")
conn.execute(
"UPDATE automations SET name = ?, prompt = ?,"
" interval_seconds = ?, repo_url = ? WHERE id = ?",
(name, prompt, interval, repo_url, automation_id))
conn.commit()
row = conn.execute(
"SELECT * FROM automations WHERE id = ?", (automation_id,)
).fetchone()
return True, automation_to_dict(row, now)
def due_automations(conn, project_id, now=None):
"""列出某项目当前到期的 active 定义(纯读,不抢单)。
SQL 层算到期:从未跑过(last_run_at 为空)或上次执行距