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

+77 -7
View File
@@ -73,12 +73,47 @@ def main() -> None:
)
parser.add_argument("-p", "--port", type=int, default=8000, help="Port to run UI on")
parser.add_argument("-H", "--host", type=str, default="0.0.0.0", help="Host address to bind")
parser.add_argument("--no-open", action="store_true", help="Do not automatically open browser")
parser.add_argument(
"--reload", action="store_true", help="Enable uvicorn auto-reload for development"
"-r",
"--restart",
action="store_true",
help="Restart the server by terminating any existing instance on the port first",
)
parser.add_argument(
"-s",
"--stop",
action="store_true",
help="Stop running Artemis server on the specified port",
)
parser.add_argument(
"--status",
action="store_true",
help="Display status of running Artemis server on the specified port",
)
args, unknown = parser.parse_known_args()
# Ensure project root is on sys.path for lifecycle utility imports
if str(ROOT_DIR) not in sys.path:
sys.path.insert(0, str(ROOT_DIR))
from artemis.runtime.server_lifecycle import get_server_status, stop_server
if args.status:
st = get_server_status(args.port)
if st["running"]:
pid_info = f" (PID: {', '.join(map(str, st['pids']))})" if st["pids"] else ""
print(f"\033[1;32m● Artemis Server is RUNNING on port {args.port}{pid_info}\033[0m")
print(f" URL: {st['url']}")
else:
print(f"\033[1;30m○ Artemis Server is STOPPED (Port {args.port} is free)\033[0m")
return
if args.stop:
print(f"\033[1;33m🛑 Stopping Artemis server on port {args.port}...\033[0m")
ok, msg, pids = stop_server(args.port)
print(f"\033[1;32m✓ {msg}\033[0m")
return
ui_url = f"http://localhost:{args.port}"
admin_url = f"http://localhost:{args.port}/admin"
@@ -86,15 +121,50 @@ def main() -> None:
print("\033[1;36m ✨ Artemis Autonomous Mobile Agent UI \033[0m")
print("\033[1;36m" + "=" * 56 + "\033[0m\n")
# Check if restart requested
if args.restart:
print(f"\033[1;33m🔄 Restart requested. Recycling port {args.port}...\033[0m")
ok, msg, pids = stop_server(args.port)
if pids:
print(f"\033[1;32m✓ {msg}\033[0m\n")
else:
print(f"Port {args.port} is clear.\n")
# Check if server is already running
if is_port_in_use(args.port):
elif is_port_in_use(args.port):
if is_artemis_ui_responsive(ui_url):
print(f"\033[1;32m✓ Artemis UI is already running at:\033[0m \033[1;36m{ui_url}\033[0m")
print(f"\033[1;35m🛠️ Admin Console:\033[0m \033[1;36m{admin_url}\033[0m\n")
if not args.no_open:
print("🌐 Opening browser...")
webbrowser.open(ui_url)
return
# In interactive terminal, allow user to restart or open browser
if sys.stdin.isatty():
print("Server is active in another session/terminal.")
print(" [1] Open browser (default)")
print(" [2] Restart server (stop existing and start here)")
print(" [3] Stop server")
try:
choice = input("Select [1-3, default 1]: ").strip()
except (KeyboardInterrupt, EOFError):
choice = "1"
if choice == "2":
print(f"\n\033[1;33m🔄 Restarting Artemis server on port {args.port}...\033[0m")
stop_server(args.port)
elif choice == "3":
print(f"\n\033[1;33m🛑 Stopping Artemis server on port {args.port}...\033[0m")
ok, msg, _ = stop_server(args.port)
print(f"\033[1;32m✓ {msg}\033[0m")
return
else:
if not args.no_open:
print("🌐 Opening browser...")
webbrowser.open(ui_url)
return
else:
if not args.no_open:
print("🌐 Opening browser...")
webbrowser.open(ui_url)
return
else:
print(f"\033[1;33m⚠️ Port {args.port} is in use by another process.\033[0m")
print("Attempting to connect or start on specified port...")