fix(dashboard): report a PM node failure on /api/pty; give E2E sandboxes node
The PTY route turned a missing node (SystemExit) into a visible reason, but _make_tui_argv now asks PM for node, and PM's InstallError went uncaught: the socket just closed on an empty screen. Report it through _pty_fail like the other launch failures; PM's own remedy names the fix. The E2E sandbox's prepared PM store carried only uv and python, so every PTY test hit exactly that. Carry node too when the runner's store has it.
This commit is contained in:
@@ -438,6 +438,7 @@ async def _pty_fail(ws: WebSocket, exc: BaseException) -> None:
|
||||
@router.websocket("/api/pty")
|
||||
async def pty_ws(ws: WebSocket) -> None:
|
||||
from hermes_cli.web_server_chat import PTY_REGISTRY, PtyBridge, PtyUnavailableError, _PTY_BRIDGE_AVAILABLE, _RESIZE_RE
|
||||
from pm.package import InstallError
|
||||
gate = await _ws_gate(ws, "pty")
|
||||
if gate is None:
|
||||
return
|
||||
@@ -503,6 +504,9 @@ async def pty_ws(ws: WebSocket) -> None:
|
||||
except SystemExit as exc: # _make_tui_argv sys.exit(1)s when node/npm is missing
|
||||
await _pty_fail(ws, exc)
|
||||
return
|
||||
except InstallError as exc: # PM could not provide node; its remedy names the fix
|
||||
await _pty_fail(ws, exc)
|
||||
return
|
||||
|
||||
attach_token = ws.query_params.get("attach") or None
|
||||
registry_resume = raw_resume
|
||||
|
||||
@@ -25,13 +25,16 @@ def _prepare_test_tools() -> tuple[Path, dict] | None:
|
||||
if not (source / "facts.json").is_file():
|
||||
return None # A system/Nix test interpreter has no selected PM tool store.
|
||||
facts = Facts(source / "facts.json", strict=True)
|
||||
records = {name: facts.get(name) for name in ("uv", "python")}
|
||||
# node runs the TUI behind the dashboard's /api/pty; carry it when the
|
||||
# runner's store has it, or PM in the sandbox reports it not installed.
|
||||
names = ("uv", "python") + (("node",) if facts.get("node") else ())
|
||||
records = {name: facts.get(name) for name in names}
|
||||
python = records["python"]
|
||||
if not python or (source / python["entry"]).resolve() != Path(sys.base_prefix).resolve():
|
||||
raise AssertionError(f"test interpreter is not from the selected PM store: {source}")
|
||||
target = current_target()
|
||||
lock = Lockfile(lockfile_path())
|
||||
verified_tools(("uv", "python"), source_store=source, target=target, lock=lock)
|
||||
verified_tools(names, source_store=source, target=target, lock=lock)
|
||||
|
||||
# The source may live under the real ~/.hermes. Per-test guards must never
|
||||
# read it, nor follow a sandbox tool symlink back into it. Copy the verified
|
||||
@@ -44,7 +47,7 @@ def _prepare_test_tools() -> tuple[Path, dict] | None:
|
||||
(prepared / "facts.json").write_text(
|
||||
json.dumps({"schema": 1, "packages": records}), encoding="utf-8"
|
||||
)
|
||||
verified_tools(("uv", "python"), source_store=prepared, target=target, lock=lock)
|
||||
verified_tools(names, source_store=prepared, target=target, lock=lock)
|
||||
except BaseException:
|
||||
shutil.rmtree(prepared, ignore_errors=True)
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user