feat: add multi-device concurrency, server lifecycle CLI, and unified execution stream

- Implement DevicePool and DeviceExecutionLock concurrency modes (per-device FIFO vs global serialization)
- Extend ArtemisClient SDK and CLI with explicit device targeting and concurrency strategy configuration
- Add server lifecycle management (artemis restart, stop, status) with cross-platform PID discovery
- Update MCP server tools to support multi-device dispatch, trace tagging, and per-device execution
- Enhance Showcase UI with step-by-step visual replay, playback speed controls, and device serial badges
- Add comprehensive unit test coverage and edge-case verification scripts
This commit is contained in:
somew1nd committed 2026-08-26 15:53:14 -07:00
1 parent a36a66ce6d
commit c2c90a6378
84 files changed
+5612 -645

No files matched your search

+1
View File
@@ -84,6 +84,7 @@ AGENT_CONFIG_FILENAME = "agent_config.json"
DATA_ENGINE_DB_FILENAME = "data_engine.db"
IPC_PORT_FILENAME = ".artemis_ipc_port"
LS_ADDRESS_FILENAME = ".jetski_ls_address"
SERVER_INFO_FILENAME = ".artemis_server.json"
DOTENV_FILENAME = ".env"
+9
View File
@@ -26,6 +26,7 @@ from artemis.config.constants import (
ENV_ARTEMIS_USE_USER_DIR,
IPC_PORT_FILENAME,
LS_ADDRESS_FILENAME,
SERVER_INFO_FILENAME,
)
from artemis.platform import platform
@@ -110,6 +111,14 @@ def get_ls_address_file() -> Path:
return ROOT_DIR / LS_ADDRESS_FILENAME
def get_server_info_file() -> Path:
"""Returns the location of the Artemis server metadata file."""
app_dir_file = get_app_dir() / SERVER_INFO_FILENAME
if app_dir_file.exists():
return app_dir_file
return ROOT_DIR / SERVER_INFO_FILENAME
def get_config_path(filename: str, default_bundled_path: Path | None = None) -> Path:
"""Resolve a configuration file path across env vars, config dir, project dir, app dir, and bundles.
+60 -20
View File
@@ -21,8 +21,11 @@ import time
from artemis.config.constants import (
ENV_ANTIGRAVITY_LS_ADDRESS,
ENV_ARTEMIS_IPC_PORT,
IPC_PORT_FILENAME,
)
from artemis.config.paths import (
ROOT_DIR,
get_app_dir,
get_ipc_port_file,
get_ls_address_file,
get_temp_dir,
@@ -33,19 +36,43 @@ logger = get_logger(__name__)
def read_ipc_port() -> int | None:
"""Read the active ARTEMIS IPC port from environment or temporary state file."""
"""Read the active ARTEMIS IPC port from environment, state files, or local server API."""
env_port = os.getenv(ENV_ARTEMIS_IPC_PORT)
if env_port and env_port.strip().isdigit():
return int(env_port.strip())
port_file = get_ipc_port_file()
if port_file.exists():
try:
content = port_file.read_text(encoding="utf-8").strip()
if content.isdigit():
return int(content)
except Exception as e:
logger.warning(f"Failed to read IPC port from {port_file}: {e}")
candidate_paths = [
get_temp_dir() / "artemis-ipc-port",
get_app_dir() / IPC_PORT_FILENAME,
ROOT_DIR / IPC_PORT_FILENAME,
get_ipc_port_file(),
]
for p in candidate_paths:
if p.exists():
try:
content = p.read_text(encoding="utf-8").strip()
if content.isdigit():
return int(content)
except Exception:
pass
# Dynamic fallback: check local admin console status endpoint
try:
import urllib.request
import json
req = urllib.request.Request(
"http://127.0.0.1:8000/api/status",
headers={"User-Agent": "Artemis-IPC-Discovery"},
)
with urllib.request.urlopen(req, timeout=0.2) as resp:
if resp.status == 200:
data = json.loads(resp.read().decode("utf-8"))
ipc_port = data.get("ipc_port")
if ipc_port and str(ipc_port).isdigit():
return int(ipc_port)
except Exception:
pass
return None
@@ -54,23 +81,36 @@ def write_ipc_port(port: int) -> Path:
"""Save active IPC port to environment and temporary synchronization file."""
os.environ[ENV_ARTEMIS_IPC_PORT] = str(port)
port_file = get_ipc_port_file()
try:
port_file.parent.mkdir(parents=True, exist_ok=True)
port_file.write_text(str(port), encoding="utf-8")
except Exception as e:
logger.warning(f"Failed to write IPC port file to {port_file}: {e}")
all_targets = [
port_file,
get_temp_dir() / "artemis-ipc-port",
get_app_dir() / IPC_PORT_FILENAME,
ROOT_DIR / IPC_PORT_FILENAME,
]
for target in all_targets:
try:
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(str(port), encoding="utf-8")
except Exception as e:
logger.debug(f"Could not write IPC port to {target}: {e}")
return port_file
def clear_ipc_port() -> None:
"""Remove IPC port synchronization state from the process and filesystem."""
os.environ.pop(ENV_ARTEMIS_IPC_PORT, None)
port_file = get_ipc_port_file()
if port_file.exists():
try:
port_file.unlink(missing_ok=True)
except Exception as e:
logger.warning(f"Failed to remove IPC port file {port_file}: {e}")
all_targets = [
get_ipc_port_file(),
get_temp_dir() / "artemis-ipc-port",
get_app_dir() / IPC_PORT_FILENAME,
ROOT_DIR / IPC_PORT_FILENAME,
]
for target in all_targets:
if target.exists():
try:
target.unlink(missing_ok=True)
except Exception as e:
logger.debug(f"Failed to remove IPC port file {target}: {e}")
def read_ls_address() -> str | None: