"""Guided exploration through the same HTTP service used by the workspace.""" from typing import Any, Literal from mcp_server.base import mcp from mcp_server.tools.automation import _id, _request @mcp.tool() async def mobile_exploration( action: Literal[ "list", "deletion_preview", "delete", "get", "create", "start", "message", "pause", "continue", "stop", "demonstrate", "demo_finish", "demo_cancel", "demo_extend", "case", "save_case", "generate", "validate", "batch", ], exploration_id: str | None = None, request_id: str | None = None, payload: dict[str, Any] | None = None, demonstration_id: str | None = None, after: int = 0, version: int | None = None, user_confirmed: bool = False, ) -> Any: """Explore ONE complex test with user guidance and produce a reusable case. deletion_preview lists affected conversation runs; delete requires user_confirmed=true after the user requests deletion. Independent validation runs are preserved. Read-only list/get/case never execute tasks. create stores {goal,config?,task_config?}; start/continue explicitly enqueue or resume Pro exploration. Independent defaults: gpt-6.1-sol/high, 1800 active seconds; timeout pauses, never fails by itself. message payload={text}; answer pending questions or guide without discarding history. demonstrate payload={source:'android'|'computer'|'both'} pauses at a safe point and records while retaining the device/plugin lease. Wait for recording before the user operates. demo_finish analyzes the SAVED clip; demo_extend payload={seconds:900}; demo_cancel preserves evidence. Default 900s, warning 60s before expiry, auto-pause. save_case payload={expected_version,case:{name,goal,preconditions?,evidence?}}; generate payload={source_run_id,expected_version} starts evidence-based synthesis, poll get.generations for completion. validate payload={version,profile,task_config?} explicitly runs the saved version in Flash/Pro; batch additionally accepts name and creates a DRAFT batch, never starts it. Exploration pass and rerun pass are separate. Use stable request_id for every mutation except pause/stop/delete, reusing it on transport retries. No API keys; configure secrets in the Web UI. mobile_config(section='exploration') controls non-secret defaults. After errors call mobile_diagnose before resubmitting. """ base = "/api/explorations" if action in {"deletion_preview", "delete"}: path = f"{base}/{_id(exploration_id)}" if action == "deletion_preview": return await _request("GET", path + "/deletion-preview") if not user_confirmed: raise ValueError("删除探索需要用户明确要求;先用 deletion_preview 核对影响范围") return await _request("DELETE", path) if action in {"list", "get", "case"}: if payload or request_id: raise ValueError("查询不接受写入内容") if action == "list": return await _request("GET", base) path = f"{base}/{_id(exploration_id)}" if action == "get": if after < 0: raise ValueError("after 不能小于零") return await _request("GET", path, params={"after": after}) return await _request( "GET", path + "/case", params={"version": version} if version is not None else {} ) if action not in {"pause", "stop"} and (not request_id or not request_id.strip()): raise ValueError("写入探索需提供稳定的 request_id") body = dict(payload or {}) if "request_id" in body: raise ValueError("request_id 请使用单独参数") if request_id: body["request_id"] = request_id if action == "create": return await _request("POST", base, json=body, params={"source": "mcp"}) suffix = { "start": "start", "message": "messages", "pause": "pause", "continue": "continue", "stop": "stop", "demonstrate": "demonstrations", "save_case": "case", "generate": "generate", "validate": "validate", "batch": "batch", }.get(action) if action.startswith("demo_"): suffix = f"demonstrations/{_id(demonstration_id)}/{action.removeprefix('demo_')}" if suffix is None: raise ValueError("不支持的探索操作") return await _request( "POST", f"{base}/{_id(exploration_id)}/{suffix}", json=body, params={"source": "mcp"} )