"""Durable manual-demonstration commands, sharing the automation transaction.""" import time from uuid import uuid4 from artemis.exploration.store import ExplorationConflict, ExplorationStore TERMINAL = {"completed", "failed", "cancelled", "timed_out"} class Demonstrations: def __init__(self, repository: ExplorationStore): self.repository = repository def get(self, demo_id: str) -> dict: value = self.repository.database.get("exploration_demo", demo_id) if value is None: raise KeyError("人工示范不存在") return value def list(self, session_id: str) -> list[dict]: self.repository.get(session_id) with self.repository.database.connection() as connection: rows = connection.execute( "SELECT payload FROM automation_records WHERE kind='exploration_demo' " "AND json_extract(payload,'$.exploration_id')=? ORDER BY updated_at", (session_id,), ).fetchall() import json return [json.loads(row[0]) for row in rows] def request(self, session_id: str, source: str, request_id: str) -> dict: repo = self.repository with repo._transaction() as connection: session = repo._require(connection, session_id) def create(): run_id = session["active_run_id"] run = repo._get(connection, "exploration_run", run_id) if run_id else None if ( not run or run["kind"] != "exploration" or run["status"] not in {"running", "waiting_user"} ): raise ExplorationConflict("请等待探索开始执行,再请求人工示范") if session["active_demonstration_id"]: raise ExplorationConflict("已有进行中的人工示范") value = { "id": str(uuid4()), "exploration_id": session_id, "run_id": run_id, "source": source, "status": "requested", "command": None, "created_at": time.time(), "deadline": None, "media": [], "error": None, "timeout_seconds": session["config"]["demonstration_timeout_seconds"], "warning_seconds": session["config"]["demonstration_warning_seconds"], } repo._put(connection, "exploration_demo", value["id"], value) session["active_demonstration_id"] = value["id"] repo._save_session(connection, session) return value return repo._operation(connection, f"{session_id}:demo", request_id, source, create) def command( self, session_id: str, demo_id: str, command: str, request_id: str, *, seconds: int = 0 ) -> dict: if command not in {"finish", "cancel", "extend"}: raise ValueError("不支持的示范操作") if command == "extend" and not 60 <= seconds <= 86400: raise ValueError("延长时长必须在 60–86400 秒之间") repo = self.repository with repo._transaction() as connection: session = repo._require(connection, session_id) demo = repo._get(connection, "exploration_demo", demo_id) if not demo or demo["exploration_id"] != session_id: raise KeyError("人工示范不存在") def update(): if session["active_demonstration_id"] != demo_id or demo["status"] not in { "requested", "recording", }: raise ExplorationConflict("示范已结束或正在分析") if command != "cancel" and demo["status"] != "recording": raise ExplorationConflict("请等待录像就绪") if demo.get("command"): raise ExplorationConflict("示范已请求结束") if demo.get("deadline") and time.time() >= demo["deadline"]: raise ExplorationConflict("示范已到时,请等待保存录像") if command == "extend": demo["deadline"] += seconds else: demo["command"] = command return repo._put(connection, "exploration_demo", demo_id, demo) return repo._operation( connection, f"{demo_id}:command", request_id, [command, seconds], update ) def transition(self, demo_id: str, status: str, **fields) -> dict: repo = self.repository with repo._transaction() as connection: demo = repo._get(connection, "exploration_demo", demo_id) if not demo: raise KeyError("人工示范不存在") session = repo._require(connection, demo["exploration_id"]) if demo["status"] in TERMINAL: return demo if session["active_run_id"] != demo["run_id"]: raise ExplorationConflict("示范所属执行已结束") demo.update(status=status, **fields) if status == "recording": demo.update(started_at=time.time(), deadline=time.time() + demo["timeout_seconds"]) if status in TERMINAL: demo["finished_at"] = time.time() session["active_demonstration_id"] = None repo._save_session(connection, session) return repo._put(connection, "exploration_demo", demo_id, demo)