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 { NativeStatus } from "./native-settings"; const tailnet = { missing: "Tailscale is not installed", offline: "Tailscale is disconnected", running: "Tailscale is connected", error: "Tailscale could not be checked", }; export function HostStatus() { 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); useEffect(() => { let active = true; if (desktop) { desktop.helper("status").then( (value) => { if (active) setHelper({ value }); }, (error: Error) => { if (active) setHelper({ error: error.message }); }, ); } if (status === "open") { host.request({ op: "diagnostics" }).then( (value) => { if (active) setChecks({ value }); }, (error: Error) => { if (active) setChecks({ error: error.message }); }, ); } return () => { active = false; }; }, [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; return (
{desktop && (

Ace Helper

{terminal ? "Using a command-line host" : current?.running ? "Running in the background" : current?.service === "approval" ? "macOS approval required" : current ? "Stopped" : "Checking Ace Helper…"}

{terminal ? "Stop ace serve before managing Ace Helper here." : "Ace Helper starts at login and keeps channels available after you quit Ace."}

{!current?.running ? ( ) : ( <> )}
{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}

)}
)} {desktop && } {status !== "open" ?

Connect to Ace Helper to check tools and team connectivity.

: checks.value ? ( <>

Tools

{checks.value.tools.map((tool) => (

{tool.name}: {tool.error ? "Needs attention" : tool.version || "Available"}

{tool.path &&

{tool.path}

} {tool.error &&

{tool.error}

}
))}

Team connectivity

{tailnet[checks.value.tailscale.state]} {checks.value.tailscale.name && ` as ${checks.value.tailscale.name}`}

{checks.value.tailscale.error && (

{checks.value.tailscale.error}

)}

Tailscale lets teammates reach this host. Restart Ace Helper after connecting or changing Tailscale.

Team directory: {checks.value.directory.url || "Not configured"}

{checks.value.directory.error ?

{checks.value.directory.error}

: checks.value.directory.url && (

{checks.value.directory.synced ? `Last synced ${ new Date(checks.value.directory.synced).toLocaleTimeString() }` : "Waiting for the first sync…"}

)}
) : !checks.error &&

Checking this host…

} {checks.error && status === "open" && (

{checks.error}

)}
); }