"""Reconcile saved exploration metadata with the existing queue and worker owners.""" import json import time from apps.admin_console.core.state import state from apps.admin_console.database.repositories.session_repository import SessionRepository from artemis.exploration.store import ExplorationConflict, ExplorationStore, explorations from artemis.runtime.process_probe import pid_is_alive from artemis.runtime.task_control import read_state from artemis.runtime.task_result import read_worker_outcome from artemis.runtime.case_result import read_case_result def reconcile_explorations(repository: ExplorationStore = explorations) -> None: for kind in ("exploration_generation", "exploration_discussion"): with repository._transaction() as connection: for row in connection.execute( "SELECT id, payload FROM automation_records WHERE kind=? AND json_extract(payload,'$.status')='running'", (kind,), ).fetchall(): job = json.loads(row[1]) if ( not pid_is_alive(job.get("owner_pid")) or job.get("lease_until", 0) < time.time() ): repository._put( connection, kind, row[0], { **job, "status": "failed", "lease_until": 0, "error": "生成进程已中断;已有结果保留,可重试。", }, ) with repository.database.connection() as connection: rows = connection.execute( "SELECT payload FROM automation_records WHERE kind='exploration' " "AND json_extract(payload,'$.active_run_id') IS NOT NULL" ).fetchall() history = SessionRepository(repository.database.path) queued = { str(item.get("session_id")) for item in state.queue_items if isinstance(item, dict) and item.get("status") in {"pending", "running", "queued"} } for row in rows: session = json.loads(row[0]) run_id = session["active_run_id"] run = repository.run(run_id) if not run: continue outcome = read_worker_outcome(run_id) if outcome: repository.finish_run(run_id, {**outcome, "status": outcome["task_status"]}) continue control = read_state(run_id) if run["kind"] == "validation" and pid_is_alive(control.get("pid")): observed = {"running": "running", "task_paused": "paused", "resuming": "queued"}.get( control.get("status") ) if observed and observed != run["status"]: try: repository.set_run_state(run_id, observed, reason=control.get("pause_reason")) except ExplorationConflict: continue # The owner finalized while this tick read its control file. if run_id in queued or run_id in state.active_runs: continue if control.get("pid") and pid_is_alive(control["pid"]): continue execution = history.get_session_by_id(run_id) or {} if execution.get("pid") and history.process_is_alive(execution["pid"]): continue job = repository.database.job(run_id) or {} terminal = execution.get("status") or job.get("terminal_status") or job.get("status") if terminal in {"completed", "failed", "cancelled", "interrupted", "skipped"}: repository.finish_run( run_id, { "status": terminal, "error": execution.get("error") or job.get("error") or "", "test_result": execution.get("test_result") or read_case_result(run_id, terminal), }, ) elif time.time() - run["created_at"] > 300: repository.finish_run( run_id, { "status": "interrupted", "error": "探索工作进程已结束;原证据已保留,可继续探索并重新观察当前设备。", }, )