feat: extract in-process MCP action layer, concurrent task dispatch, and spawn/reconcile hardening
- Extract device action execution into artemis/mcp action layer: typed ActionResult, capability manifest with startup validation, AdbActuator as the single physical implementation, in-memory ActionSession (replaces stdio subprocess MCP in validator), and McpActionExecutor preserving the legacy executor contract. adb_server keeps its external stdio/SSE surface unchanged and now reuses the actuator implementation; import-time side effects moved into configure_stdio_mode(). - Capability-gated prompt assembly (prompt_assembly.py): tool enums and teaching segments render per available backend tools; full-set output is byte-identical to the previous static text (locked by tests). - Admin console task queue: serial worker replaced by a per-device concurrent dispatcher (ARTEMIS_CONCURRENCY_MODE=global restores serial semantics), per-session cancellation, strict device binding on enqueue, unified stdio piping with tee to trace stdout.log, and shutdown cleanup of all active runs. Fix _pid_exists exception order so PermissionError is treated as alive. - mcp_server reliability: spawn watchdog kills runners stuck before interpreter startup and releases device reservations; task state reconcile uses psutil liveness, prefers runner-reported pids, and adds a 45s startup grace to stop false failure reports on Windows; conversation_id is now optional end-to-end; explicit device_serial is validated at submission. - CLI install now writes ~/.claude/rules/artemis.md for Claude Code and migrates/removes the legacy CLAUDE.md injection block; ~/.claude.json is never rewritten when it fails to parse. - Tests: Windows-portable stdio lifecycle and path assertions; new suites for action layer, prompt assembly, spawn watchdog, and task reconcile (895 passed, 3 skipped).
This commit is contained in:
1 parent
60312abbb0
commit
cbdca1497c
52 files changed
+5735
-1649
No files matched your search
@@ -0,0 +1,70 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Mock actuator for tests: real actuator code paths over an in-memory device.
|
||||
|
||||
Subclasses :class:`AdbActuator` deliberately, so tests exercise the same coordinate
|
||||
conversion and controller dispatch as production -- only the driver is in-memory.
|
||||
``capabilities``/``extensions`` are overridable to model partial and extended
|
||||
backends.
|
||||
"""
|
||||
|
||||
from artemis.context import ArtemisContext, DeviceContext, DevicePlatform
|
||||
from artemis.drivers.mock.mock_driver import MockDeviceDriver
|
||||
from artemis.mcp.action_manifest import DEVICE_ACTIONS, ExtensionTool
|
||||
from artemis.mcp.actuators.adb import AdbActuator
|
||||
from artemis.platform import platform
|
||||
|
||||
__all__ = ["MockActuator"]
|
||||
|
||||
|
||||
class MockActuator(AdbActuator):
|
||||
"""An :class:`AdbActuator` bound to a :class:`MockDeviceDriver`."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
width: int = 1080,
|
||||
height: int = 2400,
|
||||
capabilities: frozenset[str] | None = None,
|
||||
extensions: list[ExtensionTool] | None = None,
|
||||
):
|
||||
driver = MockDeviceDriver(width=width, height=height)
|
||||
ctx = ArtemisContext(
|
||||
trace_id="mock-actuator",
|
||||
device=DeviceContext(
|
||||
host_platform=platform.os_type.name,
|
||||
mobile_platform=DevicePlatform.ANDROID,
|
||||
device_id=driver.device_id,
|
||||
device_width=width,
|
||||
device_height=height,
|
||||
),
|
||||
)
|
||||
# get_driver(ctx) returns a cached `_active_driver` when present
|
||||
# (artemis/drivers/factory.py), the established test seam.
|
||||
ctx._active_driver = driver
|
||||
super().__init__(ctx)
|
||||
self.driver = driver
|
||||
self._capabilities = capabilities if capabilities is not None else DEVICE_ACTIONS
|
||||
self._extensions = list(extensions or [])
|
||||
|
||||
def capabilities(self) -> frozenset[str]:
|
||||
return self._capabilities
|
||||
|
||||
def extensions(self) -> list[ExtensionTool]:
|
||||
return self._extensions
|
||||
|
||||
@property
|
||||
def action_history(self) -> list[dict]:
|
||||
"""The mock driver's recorded actions, for assertions."""
|
||||
return self.driver.action_history
|
||||
Reference in new issue
Block a user