diff --git a/backend/agenteval/web/app.py b/backend/agenteval/web/app.py index 59fc057..4227c62 100644 --- a/backend/agenteval/web/app.py +++ b/backend/agenteval/web/app.py @@ -42,6 +42,25 @@ def _has_pending_task() -> bool: session.close() +def _has_planning_eval() -> bool: + """True if any intelligent eval is waiting in ``planning`` (needs the OpenClaw planner).""" + from sqlmodel import select + + from agenteval.intelligent_eval.models import IntelligentEvalStatus + from agenteval.storage.db import IntelligentEvalDB + + session = get_session() + try: + ev = session.exec( + select(IntelligentEvalDB).where( + IntelligentEvalDB.status == IntelligentEvalStatus.PLANNING.value + ) + ).first() + return ev is not None + finally: + session.close() + + def _supplement_decision_logs(session) -> int: """Platform audit backfill for decision logs. @@ -206,13 +225,65 @@ async def _trigger_intelligent_worker() -> bool: return True +async def _trigger_intelligent_planner() -> bool: + """Trigger OpenClaw's headless agent to run the planner skill. + + 方案③只自动化了 executing(worker)→ completed(analyst),**缺少 planning + 阶段的 planner 触发**——新建或被打回的评估会永远停在 planning。这里对 + planning 状态评估触发 `agenteval-intelligent-planner` skill:planner 自会 + 拉取 planning 评估列表、读取四件套、产出粗计划并 PUT /plan 提交(planner + skill 定义见 OpenClaw workspace skills)。处理完评估离开 planning 后不再触发。 + + Returns: + True 若确实触发了 agent(存在 planning 评估)。 + """ + if not _has_planning_eval(): + return False + + import subprocess + + _logger = logging.getLogger("agenteval") + # 同 worker:`openclaw agent` 无 cron state,须带"立即完成"语义,否则 planner + # 会"决策后等下一拍"而死锁。 + planner_msg = ( + "执行 agenteval-intelligent-planner skill,立即完成当前任务,不要等待下一节拍:" + "为 planning 状态的智能评估读取输入、产出粗计划并提交平台审批。" + ) + try: + proc = await asyncio.to_thread( + subprocess.run, + [ + "docker", + "exec", + "openclaw-eval", + "openclaw", + "agent", + "--agent", + "main", + "-m", + planner_msg, + "--json", + ], + capture_output=True, + text=True, + timeout=300, + ) + _logger.info("Planner 触发完成 exit=%s", proc.returncode) + if proc.returncode != 0: + _logger.warning("Planner 触发 stderr: %s", proc.stderr[-300:]) + except Exception as exc: + _logger.warning("Planner 触发失败(忽略): %s", exc) + return True + + async def _intelligent_eval_scan_loop() -> None: - """Scan executing intelligent evals, enqueue tasks, and trigger the worker. + """Scan intelligent evals, enqueue tasks, and trigger planner/worker. v1.1.0 缺陷修复:`scan_and_enqueue_tasks` 此前没有调度点,OpenClaw Worker - 每分钟唤醒却永远取不到任务。平台启动后每 60s:① 扫描 executing 的评估入队; - ② 若有 pending 任务则触发 OpenClaw agent 执行 worker skill(方案③,免外部 - channel)。失败不阻断(下次循环继续)。 + 每分钟唤醒却永远取不到任务。平台启动后每 60s: + ① 有 planning 评估则触发 OpenClaw planner skill 产出粗计划(planning→待审批); + ② 扫描 executing 的评估入队;③ 若有 pending 任务则触发 worker skill 执行会话/ + 分析(方案③,免外部 channel)。失败不阻断(下次循环继续)。 """ _logger = logging.getLogger("agenteval") while True: @@ -242,6 +313,10 @@ async def _intelligent_eval_scan_loop() -> None: await _trigger_intelligent_worker() except Exception as exc: logging.getLogger("agenteval").warning("Worker 触发失败(忽略): %s", exc) + try: + await _trigger_intelligent_planner() + except Exception as exc: + logging.getLogger("agenteval").warning("Planner 触发失败(忽略): %s", exc) await asyncio.sleep(60) diff --git a/tests/integration/test_intelligent_eval_scan_scheduler.py b/tests/integration/test_intelligent_eval_scan_scheduler.py index ccd36f9..84040bd 100644 --- a/tests/integration/test_intelligent_eval_scan_scheduler.py +++ b/tests/integration/test_intelligent_eval_scan_scheduler.py @@ -111,6 +111,53 @@ def test_trigger_worker_msg_has_execute_semantics(monkeypatch): assert "agenteval-intelligent-analyst" in joined +def test_trigger_planner_skips_when_no_planning(monkeypatch): + """No planning eval → no docker exec invocation.""" + import asyncio + import subprocess + from unittest.mock import MagicMock + + import agenteval.web.app as app_mod + + calls: list = [] + monkeypatch.setattr(app_mod, "_has_planning_eval", lambda: False) + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return MagicMock(returncode=0, stderr="") + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert asyncio.run(app_mod._trigger_intelligent_planner()) is False + assert not calls, "should not invoke docker exec when no planning eval" + + +def test_trigger_planner_calls_docker_exec(monkeypatch): + """Planning eval present → invoke `docker exec openclaw-eval openclaw agent` planner skill.""" + import asyncio + import subprocess + from unittest.mock import MagicMock + + import agenteval.web.app as app_mod + + calls: list = [] + monkeypatch.setattr(app_mod, "_has_planning_eval", lambda: True) + + def fake_run(cmd, **kwargs): + calls.append(cmd) + return MagicMock(returncode=0, stderr="") + + monkeypatch.setattr(subprocess, "run", fake_run) + + assert asyncio.run(app_mod._trigger_intelligent_planner()) is True + assert calls, "docker exec should be invoked" + joined = " ".join(calls[0]) + assert "docker" in joined and "openclaw" in joined + assert "agenteval-intelligent-planner" in joined + assert "立即完成当前任务" in joined + assert "不要等待下一节拍" in joined + + def test_supplement_execute_session_log(monkeypatch, db_session): """Executing eval with deficit and no execute_session log → platform backfills.""" import agenteval.web.app as app_mod