import { useEffect, useState, useSyncExternalStore } from "react"; import { Button } from "@ace/ui"; import type { HelperAction, HelperState } from "@ace/desktop/protocol"; import type { Diagnostics } from "@ace/host/protocol"; import { desktop } from "./desktop"; import { host } from "./host"; import { Disclosure, Group, Problem, Row, Status } from "./settings-layout"; const tailnet = { missing: "Tailscale is not installed", offline: "Tailscale is disconnected", running: "Tailscale is connected", error: "Tailscale could not be checked", }; export function HostStatus({ active }: { active: boolean }) { const status = useSyncExternalStore(host.subscribe, () => host.status); const [retry, setRetry] = useState(0); const [checks, setChecks] = useState<{ value?: Diagnostics; error?: string }>({}); const [helper, setHelper] = useState<{ value?: HelperState; error?: string }>({}); const [action, setAction] = useState<{ busy?: HelperAction; error?: string }>({}); const [confirm, setConfirm] = useState(false); // Re-read when the section is shown again; results from an action in flight still land here. useEffect(() => { if (!active) return; let live = true; if (desktop) { desktop.helper("status").then( (value) => { if (live) setHelper({ value }); }, (error: Error) => { if (live) setHelper({ error: error.message }); }, ); } if (status === "open") { host.request({ op: "diagnostics" }).then( (value) => { if (live) setChecks({ value }); }, (error: Error) => { if (live) setChecks({ error: error.message }); }, ); } return () => { live = false; }; }, [active, status, retry]); async function control(command: HelperAction) { if (!desktop) return; setAction({ busy: command }); setConfirm(false); try { setHelper({ value: await desktop.helper(command) }); setAction({}); setRetry((value) => value + 1); } catch (error) { setAction({ error: (error as Error).message }); } } const current = helper.value; const terminal = current?.running && !current.managed; const available = !!current && !terminal && !action.busy; const checked = checks.value; return (
{desktop && ( {terminal ? "Using a command-line host. Stop ace serve before managing Ace Helper here." : current?.running ? "Running in the background. It starts at login and keeps channels available after you quit Ace." : current?.service === "approval" ? "macOS approval required. Allow Ace in Login Items to run Ace Helper in the background." : current ? "Stopped. Ace Helper starts at login and keeps channels available after you quit Ace." : "Checking Ace Helper…"} } footer={ <> {confirm && (

This host's channels and tools will go offline. Work resumes when you start Ace Helper. Background hosting will stay off until you enable it again.

)} {action.busy && Updating Ace Helper…} {(action.error || helper.error) && ( {action.error || helper.error} )} } > {current?.service === "approval" && !current.running && ( )} {!current?.running ? ( ) : ( <> )}
)} {status !== "open" ? Connect to Ace Helper to check tools and team connectivity. : checked ? ( <> {checked.tailscale.error}} /> {checked.directory.error}} /> {checked.tools.map((tool) => ( {tool.error}} > {tool.error ? "Needs attention" : "Available"} ))} ) : !checks.error && Checking this host…} {checks.error && status === "open" && {checks.error}} {checked && (
{checked.tools.map((tool) => (
{tool.name}
{[tool.version, tool.path].filter(Boolean).join(" · ") || "Not found"}
))}
Directory
{checked.directory.url || "Not configured"}
)}
{desktop && ( <> )}
); }