feat: add plugin runtime and cross-device task orchestration

This commit is contained in:
chenmingxuan committed 2026-10-14 20:21:44 +08:00
1 parent 3ee4a5f7a8
commit 6663233ab8
82 files changed
+4147 -124

No files matched your search

+38 -1
View File
@@ -181,6 +181,33 @@ async def _process_action(
outcome = _ActionOutcome(action_item=action_item)
attempts_log = []
from artemis.plugins.runtime import get_plugin_runtime
from artemis.runtime.task_control import TaskControl
plugins = get_plugin_runtime(node.ctx)
if plugins and action_name in plugins.bindings:
operation = plugins.execute(action_name, action_item.get("args", {}), str(uuid.uuid4()))
control = getattr(node.ctx, "task_control", None)
if isinstance(control, TaskControl):
interrupted, result = await control.run_interruptibly(
operation,
on_pause=getattr(node.ctx, "on_task_pause", None),
on_resume=getattr(node.ctx, "on_task_resume", None),
)
if interrupted:
outcome.success = False
outcome.error_msg = "插件操作已打断,结果不确定;继续前必须重新观察。"
return outcome
else:
result = await operation
outcome.success = result.status == "success"
outcome.error_msg = "" if outcome.success else result.text_summary
outcome.action_item.update(result.metadata)
outcome.action_item["result"] = result.text_summary
if result.screenshot_bytes:
outcome.post_screenshot_b64 = base64.b64encode(result.screenshot_bytes).decode()
return outcome
validation_passed = True
if not burst:
# 1. Pre-execution validation (Safety Net)
@@ -338,6 +365,8 @@ def _model_facing_action(ctx, action_item: dict) -> dict:
The live item carries the controller's pixels; every phrase rendered for
a model is normalized first, and the space marker makes that idempotent.
"""
if action_item.get("plugin_id"):
return action_item
width = getattr(getattr(ctx, "device", None), "device_width", None) or 1080
height = getattr(getattr(ctx, "device", None), "device_height", None) or 2400
return normalize_action_dict(action_item, width, height)
@@ -419,11 +448,19 @@ async def run_validation_loop(node, state: State) -> dict:
execution.append(outcome.action_item)
post_image_name = None
if outcome.post_screenshot_b64:
plugin_action = bool(outcome.action_item.get("plugin_id"))
mixed_plugin_action = (
plugin_action and getattr(ctx, "execution_target", "android") == "mixed"
)
if outcome.post_screenshot_b64 and not mixed_plugin_action:
post_image_name = _absorb_post_screenshot(ctx, state, outcome.post_screenshot_b64)
last_screenshot_b64 = outcome.post_screenshot_b64
if post_image_name:
last_screenshot_name = post_image_name
elif mixed_plugin_action:
# Keep the Android step observation on its own resource. Plugin
# images remain attached to the tool/action trace and its exports.
post_image_name = outcome.action_item.get("post_image_name")
_record_action_end(
ctx,