feat: prepare 2.2.0 with UI and execution fixes
Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
This commit is contained in:
1 parent
83bcfd54d5
commit
fac4ccb760
146 files changed
+5689
-2033
No files matched your search
@@ -58,6 +58,8 @@ from artemis.utils.coordinates import (
|
||||
parse_swipe_parameters,
|
||||
)
|
||||
from artemis.utils.logger import get_logger
|
||||
from artemis.utils.action_target import element_semantics, observation_anchor
|
||||
from artemis.utils.input_state import verify_input
|
||||
from artemis.utils.image_hash import dhash_hex, hamming_distance_hex
|
||||
from artemis.utils.notes import (
|
||||
format_list_notes_failure,
|
||||
@@ -287,6 +289,7 @@ class McpActionExecutor:
|
||||
target_semantics: dict[str, Any] = {}
|
||||
target_coordinates: list[int] | None = None
|
||||
observation_settled: bool | None = None
|
||||
input_verification: str | None = None
|
||||
with span:
|
||||
try:
|
||||
session = await self._session_or_start()
|
||||
@@ -298,15 +301,24 @@ class McpActionExecutor:
|
||||
# coordinates of an index target (the model named an index,
|
||||
# not a point).
|
||||
target_coordinates = wire_args.get("target")
|
||||
target_semantics.update(
|
||||
getattr(self, "decision_anchor", None)
|
||||
or observation_anchor(state, index_elements)
|
||||
)
|
||||
extension = raw_name not in (REQUIRED_ACTIONS | OPTIONAL_ACTIONS)
|
||||
|
||||
if extension:
|
||||
raw_result = await session.call_raw(wire_name, wire_args)
|
||||
message = next(
|
||||
(b.text for b in raw_result.content if getattr(b, "type", "") == "text"),
|
||||
f"Extension tool '{raw_name}' completed.",
|
||||
)
|
||||
res = ActionResult.success(raw_name, message)
|
||||
extension_failed = getattr(raw_result, "isError", False) is True
|
||||
res = ActionResult(
|
||||
ok=not extension_failed,
|
||||
code=ActionCode.DEVICE_ERROR if extension_failed else ActionCode.OK,
|
||||
action=raw_name,
|
||||
message=message,
|
||||
)
|
||||
else:
|
||||
res = await session.call(wire_name, wire_args)
|
||||
message = finalize(res) if finalize else res.message
|
||||
@@ -324,6 +336,14 @@ class McpActionExecutor:
|
||||
if obs.hierarchy_ok:
|
||||
state.indexed_points = [el["center"] for el in obs.elements]
|
||||
state.indexed_elements = obs.elements
|
||||
if raw_name == "input_text" and res.ok:
|
||||
input_verification = verify_input(obs.elements, target_semantics)
|
||||
if input_verification != "present":
|
||||
message += (
|
||||
f" Input value verification: {input_verification}. "
|
||||
"Input dispatch is not proof of a filled field; observe and "
|
||||
"confirm the real value before submitting. Do not repeat input blindly."
|
||||
)
|
||||
if shot_path:
|
||||
try:
|
||||
img_bytes = Path(shot_path).read_bytes()
|
||||
@@ -341,7 +361,9 @@ class McpActionExecutor:
|
||||
if observation_settled is False:
|
||||
message = f"{message} Post-action screen is still changing; observe again before concluding."
|
||||
|
||||
status = "success" if res.ok else "error"
|
||||
status = (
|
||||
"success" if res.ok and input_verification not in {"empty", "unknown"} else "error"
|
||||
)
|
||||
post_image_name = None
|
||||
if shot_path:
|
||||
post_image_name = Path(shot_path).stem
|
||||
@@ -371,6 +393,15 @@ class McpActionExecutor:
|
||||
"target_semantics": target_semantics,
|
||||
"target_coordinates": target_coordinates,
|
||||
"observation_settled": observation_settled,
|
||||
"input_verification": input_verification,
|
||||
"execution": "not_started"
|
||||
if res.code
|
||||
in {ActionCode.INVALID_ARGS, ActionCode.UNSUPPORTED, ActionCode.PACKAGE_NOT_FOUND}
|
||||
else ("completed" if res.ok else "unknown"),
|
||||
"side_effect": "none"
|
||||
if res.code
|
||||
in {ActionCode.INVALID_ARGS, ActionCode.UNSUPPORTED, ActionCode.PACKAGE_NOT_FOUND}
|
||||
else "possible",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -603,6 +634,8 @@ class McpActionExecutor:
|
||||
"target_bounds": norm_bounds,
|
||||
"target_resource_id": element.get("resource_id"),
|
||||
"target_class": element.get("class"),
|
||||
**element_semantics(element, index),
|
||||
**observation_anchor(state, elements),
|
||||
}
|
||||
return [_norm_x(center[0]), _norm_y(center[1])], recorded
|
||||
|
||||
|
||||
@@ -1,12 +1,39 @@
|
||||
"""Keep ARTEMIS tool descriptions stable across supported Python versions."""
|
||||
|
||||
import json
|
||||
from textwrap import dedent
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.fastmcp.exceptions import ToolError
|
||||
from mcp.types import TextContent
|
||||
|
||||
|
||||
class ArtemisFastMCP(FastMCP):
|
||||
"""Normalize docstring indentation before FastMCP publishes tool metadata."""
|
||||
"""Publish strict inputs and preserve structured legacy JSON results."""
|
||||
|
||||
def __init__(self, *args, strict_inputs=False, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.strict_inputs = strict_inputs
|
||||
|
||||
async def call_tool(self, name, arguments):
|
||||
tool = self._tool_manager.get_tool(name)
|
||||
if tool is not None and self.strict_inputs:
|
||||
unknown = set(arguments) - set(tool.parameters.get("properties", {}))
|
||||
if unknown:
|
||||
raise ToolError(
|
||||
f"未知参数 {sorted(unknown)};请按 {name} 的输入 schema 提交,未执行工具。"
|
||||
)
|
||||
result = await super().call_tool(name, arguments)
|
||||
if isinstance(result, list) and len(result) == 1 and isinstance(result[0], TextContent):
|
||||
try:
|
||||
value = json.loads(result[0].text)
|
||||
except (ValueError, TypeError):
|
||||
return result
|
||||
if isinstance(value, dict):
|
||||
return result, value
|
||||
if isinstance(value, list):
|
||||
return result, {"items": value}
|
||||
return result
|
||||
|
||||
def add_tool(
|
||||
self,
|
||||
@@ -35,3 +62,6 @@ class ArtemisFastMCP(FastMCP):
|
||||
meta=meta,
|
||||
structured_output=structured_output,
|
||||
)
|
||||
tool = self._tool_manager.get_tool(name or fn.__name__)
|
||||
if tool is not None and self.strict_inputs:
|
||||
tool.parameters["additionalProperties"] = False
|
||||
Reference in new issue
Block a user