xiaowei-system/scripts/kanban-opencode-dispatch.py

105 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
kanban-opencode-dispatch.py — opencode 任务调度器(分环节模式)
=============================================================
不 spawn 2B agent实测空转烧 GPU改为
1. 扫描看板 ready 状态的 opencode assignee 任务
2. 对每个任务跑 kanban-2b-worker.py分环节读任务→opencode+2B执行→验证→输出
3. 根据结果 kanban complete带证据/ block失败原因
4. 并发限制:同时只跑 1 个2B 单 slot
用法:
python3 kanban-opencode-dispatch.py # 跑一轮cron 调用)
python3 kanban-opencode-dispatch.py --once # 只处理 1 个任务
"""
import json
import os
import subprocess
import sys
HERMES = "/home/muc/bin/hermes"
WORKER = os.path.expanduser("~/.hermes/scripts/kanban-2b-worker.py")
def sh(cmd, timeout=60, cwd=None):
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout, cwd=cwd,
env=dict(os.environ, PATH="/home/muc/nodejs/node-v24.16.0-linux-x64/bin:" + os.environ.get("PATH", "")))
return r.returncode, (r.stdout + r.stderr)
except Exception as e:
return -1, str(e)[:300]
def list_ready_opencode():
rc, out = sh([HERMES, "kanban", "list", "--json"])
if rc != 0:
print(json.dumps({"ok": False, "error": f"list failed: {out[:200]}"}))
return []
try:
d = json.loads(out)
tasks = d if isinstance(d, list) else d.get("tasks", [])
ready = [t for t in tasks
if t.get("assignee") == "opencode" and t.get("status") == "ready"]
return ready
except Exception as e:
print(json.dumps({"ok": False, "error": f"parse: {e}"}))
return []
def run_worker(task_id):
"""跑分环节 worker 脚本"""
rc, out = sh([sys.executable, WORKER, task_id], timeout=320)
return rc, out
def finish_task(task_id, result_text, ok):
if ok:
rc, out = sh([HERMES, "kanban", "complete", task_id,
"--summary", result_text[:1500]])
else:
rc, out = sh([HERMES, "kanban", "block", task_id, "--reason", result_text[:800]])
return rc, out
def main():
once = "--once" in sys.argv
tasks = list_ready_opencode()
print(json.dumps({"ok": True, "found_ready": len(tasks),
"tasks": [t.get("id") for t in tasks]}, ensure_ascii=False))
if not tasks:
return
batch = tasks[:1] if once else tasks
for t in batch:
tid = t["id"]
title = (t.get("title") or "")[:80]
print(f"\n=== 处理 {tid}: {title} ===")
# claim防并发
sh([HERMES, "kanban", "claim", tid], timeout=30)
rc, out = run_worker(tid)
# 提取 OPENCODE_RESULT 之后的摘要worker 输出的结果段)
summary = ""
if "=== OPENCODE_RESULT ===" in out:
summary = out.split("=== OPENCODE_RESULT ===")[-1].strip()[:600]
else:
lines = [l for l in out.splitlines() if l.strip()]
summary = (lines[-3] if lines else "")[:600]
ok = False
# 从 worker 输出找 verified注意 json.dumps 会转义引号,用宽松匹配)
try:
import re
if re.search(r'verified["\']?\s*[:=]\s*true', out, re.IGNORECASE):
ok = True
except Exception:
pass
print(f"worker exit={rc}, ok={ok}")
f_rc, f_out = finish_task(tid, summary, ok)
print(f"finish rc={f_rc}")
if once:
break
if __name__ == "__main__":
main()