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 轮失败停转。
This commit is contained in:
host committed 2026-10-29 13:07:57 +08:00
1 parent 88fcfeebd3
commit ae3aff8602
4 files changed
+100 -6

No files matched your search

+27
View File
@@ -920,6 +920,33 @@ class GoalLoopLongTaskTest(unittest.TestCase):
saved = host.load_state(state_path)
self.assertEqual(saved["tasks"]["g"]["goal_rounds_done"], 2)
def test_goal_consecutive_failed_turns_stop_spinning(self):
# 不限轮次 + 瞬间失败轮次会无限空转(2026-10-29 实弹:
# 断点后 274 轮全失败);3 轮连续失败即中断出口。
class FakeServer:
def __init__(self):
self.calls = 0
def run_turn(self, thread_id, prompt, model=None, effort=None,
sandbox=None, timeout=None):
self.calls += 1
return {"status": "failed", "text": "",
"turn_id": "f%d" % self.calls}
with tempfile.TemporaryDirectory() as tmp:
state_path = os.path.join(tmp, "host-state.json")
state = {"tasks": {}, "automations": []}
task = self._task(tmp)
state["tasks"]["g"] = task
srv = FakeServer()
result = host.run_goal_loop(srv, state, state_path,
task, "make it", None, 0, "m",
"low", None, cfg={}, timeout=600)
self.assertEqual(srv.calls, 3)
self.assertTrue(result["interrupted"])
self.assertEqual(task["status"], "interrupted")
self.assertIn("连续 3 轮", task["goal_interrupted_reason"])
def test_goal_resume_continues_from_checkpoint(self):
class FakeServer:
def __init__(self):