feat(core): consolidate config hub, unify daemon scheduling across all entrypoints, and fix step replay

- Unified configuration and paths under artemis.config as single source of truth
- Consolidated execution across CLI, SDK, MCP, and Admin Console to default to Artemis Daemon
- Fixed frontend step replay to preserve failure steps with proper sequential numbering
- Implemented task deduplication, debouncing, and multi-device lock isolation
- Added comprehensive end-to-end multi-port integration tests for live phone and emulator
This commit is contained in:
somew1nd committed 2026-08-27 10:21:40 -07:00
1 parent e984d17635
commit 5f1ee18bc1
38 files changed
+1426 -203

No files matched your search

+10
View File
@@ -22,8 +22,13 @@ from artemis.runtime.device_lock import (
from artemis.runtime.device_pool import DevicePool, DeviceStatus, device_pool
from artemis.runtime.daemon_client import (
ensure_daemon_running,
get_daemon_session,
get_daemon_status,
is_daemon_running,
stop_task_on_daemon,
submit_batch_to_daemon,
submit_task_to_daemon,
wait_for_daemon_task,
)
from artemis.runtime.awake_service import (
ensure_device_awake,
@@ -54,6 +59,8 @@ __all__ = [
"ensure_daemon_running",
"ensure_device_awake",
"find_server_pids",
"get_daemon_session",
"get_daemon_status",
"get_server_status",
"is_daemon_running",
"is_port_in_use",
@@ -65,6 +72,9 @@ __all__ = [
"shutdown_awake_service",
"start_awake_service",
"stop_server",
"stop_task_on_daemon",
"submit_batch_to_daemon",
"submit_task_to_daemon",
"wait_for_daemon_task",
"write_server_info",
]
+151 -1
View File
@@ -162,7 +162,7 @@ def submit_task_to_daemon(
ingress: str = "client",
conversation_id: str | None = None,
base_url: str | None = None,
timeout: float = 5.0,
timeout: float = 15.0,
) -> dict[str, Any] | None:
"""Submit a task to the running Daemon via REST API.
@@ -197,3 +197,153 @@ def submit_task_to_daemon(
logger.debug(f"Failed to submit task to Daemon at {url}: {exc}")
return None
return None
def stop_task_on_daemon(
session_id: str,
*,
base_url: str | None = None,
timeout: float = 5.0,
) -> bool:
"""Request the running Artemis Daemon to stop a task by session ID."""
url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/stop"
payload = {"session_id": session_id}
try:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
url,
data=data,
headers={"Content-Type": "application/json", "User-Agent": "Artemis-Client"},
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout) as response:
if response.status == 200:
resp_data = json.loads(response.read().decode("utf-8"))
return resp_data.get("status") == "stopped"
except Exception as exc:
logger.debug(f"Failed to request stop from Daemon at {url}: {exc}")
return False
return False
def get_daemon_session(
session_id: str,
*,
base_url: str | None = None,
timeout: float = 5.0,
) -> dict[str, Any] | None:
"""Fetch details of a single session from the Artemis Daemon."""
url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/sessions/{session_id}"
try:
req = urllib.request.Request(
url,
headers={"User-Agent": "Artemis-Client"},
method="GET",
)
with urllib.request.urlopen(req, timeout=timeout) as response:
if response.status == 200:
return json.loads(response.read().decode("utf-8"))
except Exception as exc:
logger.debug(f"Failed to get session {session_id} from Daemon at {url}: {exc}")
return None
return None
def get_daemon_status(
*,
base_url: str | None = None,
timeout: float = 5.0,
) -> dict[str, Any] | None:
"""Fetch global scheduler and device status from the Artemis Daemon."""
url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/status"
try:
req = urllib.request.Request(
url,
headers={"User-Agent": "Artemis-Client"},
method="GET",
)
with urllib.request.urlopen(req, timeout=timeout) as response:
if response.status == 200:
return json.loads(response.read().decode("utf-8"))
except Exception as exc:
logger.debug(f"Failed to get status from Daemon at {url}: {exc}")
return None
return None
def submit_batch_to_daemon(
goals: list[str],
*,
profile: str = "flash",
device_serial: str | None = None,
ingress: str = "cli",
base_url: str | None = None,
timeout: float = 15.0,
) -> dict[str, Any] | None:
"""Submit a batch of goals to the running Daemon."""
url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/run"
payload = {
"goals": goals,
"profile": profile,
"device_serial": device_serial,
"ingress": ingress,
}
try:
data = json.dumps(payload).encode("utf-8")
req = urllib.request.Request(
url,
data=data,
headers={"Content-Type": "application/json", "User-Agent": "Artemis-Client"},
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout) as response:
if response.status in (200, 201):
return json.loads(response.read().decode("utf-8"))
except Exception as exc:
logger.debug(f"Failed to submit batch to Daemon at {url}: {exc}")
return None
return None
def wait_for_daemon_task(
session_id: str,
*,
base_url: str | None = None,
timeout: float = 600.0,
poll_interval: float = 1.0,
on_status_update: Any = None,
) -> dict[str, Any]:
"""Poll the Daemon until the task reaches a terminal status (completed, failed, cancelled)."""
started = time.monotonic()
last_status = None
while time.monotonic() - started < timeout:
sess = get_daemon_session(session_id, base_url=base_url)
if sess:
status = sess.get("status")
if status != last_status:
last_status = status
if callable(on_status_update):
on_status_update(sess)
if status in ("completed", "success", "failed", "cancelled"):
return sess
else:
# Check if task is queued in daemon status
st = get_daemon_status(base_url=base_url)
if st:
queue = st.get("queue") or []
is_queued = any(
isinstance(item, dict) and str(item.get("session_id")) == str(session_id)
for item in queue
)
current_st = "queued" if is_queued else "launching"
if current_st != last_status:
last_status = current_st
if callable(on_status_update):
on_status_update({"status": current_st, "session_id": session_id})
time.sleep(poll_interval)
return {"session_id": session_id, "status": "timeout", "error": f"Timed out after {timeout}s"}