Files
Artemis-Eapil/mcp_server/tools/exploration.py
T

111 lines
4.6 KiB
Python

"""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-terra/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"}
)