diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/agents/AgentsPage.tsx b/packages/browseros-agent/apps/agent/entrypoints/app/agents/AgentsPage.tsx index 27861255f..f347381c8 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/agents/AgentsPage.tsx +++ b/packages/browseros-agent/apps/agent/entrypoints/app/agents/AgentsPage.tsx @@ -28,8 +28,7 @@ import { } from './agents-page-utils' import { NewAgentDialog } from './NewAgentDialog' import { InlineErrorAlert } from './OpenClawControls' -import { RuntimeControlPanel } from './runtime-controls/RuntimeControlPanel' -import { RuntimeStatusBar } from './runtime-controls/RuntimeStatusBar' +import { RuntimesSection } from './runtime-controls/RuntimesSection' import { SetupOpenClawDialog } from './SetupOpenClawDialog' import { useAgentAdapters, @@ -261,13 +260,6 @@ export const AgentsPage: FC = () => { ) } - // Bar only makes sense when the gateway is running AND there's at - // least one OpenClaw agent in the merged list. Hide it for - // Claude/Codex-only setups so the page stays uncluttered. - const showGatewayStatusBar = - openClawRunning && - (visibleOpenClawAgents.length > 0 || - harnessAgents.some((agent) => agent.adapter === 'openclaw')) // Setup CTA appears when the runtime is healthy but the user has not // yet configured a provider (no openclaw.json on disk → runtime is // running but agent CRUD will fail). For now: surface it whenever the @@ -287,37 +279,32 @@ export const AgentsPage: FC = () => { /> ) : null} - setSetupOpen(true)} - > - Configure provider… - - ) : null - } + setSetupOpen(true)} + > + Configure provider… + + ) : null, + statusBarExtraActions: ( + + ), + }, + }} /> - {showGatewayStatusBar ? ( - setShowTerminal(true)} - > - - Terminal - - } - /> - ) : null} - > +} + +/** Renders one card per container-kind runtime (openclaw, hermes, …) + * with state-appropriate Install / Start / Restart controls and a + * status bar. Adapter-specific affordances slot in via `extras`. */ +export const RuntimesSection: FC = ({ extras }) => { + const { data, isLoading } = useRuntimes() + if (isLoading || !data) return null + + const containerRuntimes = data.filter( + (r) => r.descriptor.kind === 'container', + ) + if (containerRuntimes.length === 0) return null + + return ( +
+ {containerRuntimes.map((runtime) => ( + + ))} +
+ ) +} + +interface RuntimeCardProps { + runtime: RuntimeView + extras?: RuntimeAdapterExtras +} + +const RuntimeCard: FC = ({ runtime, extras }) => { + const adapter = runtime.descriptor.adapterId as RuntimeAdapterId + const showStatusBar = runtime.status.state === 'running' + + return ( +
+ + {showStatusBar && ( + + )} +
+ ) +} diff --git a/packages/browseros-agent/apps/agent/entrypoints/app/agents/useRuntime.ts b/packages/browseros-agent/apps/agent/entrypoints/app/agents/useRuntime.ts index df45f99ea..de133e3fb 100644 --- a/packages/browseros-agent/apps/agent/entrypoints/app/agents/useRuntime.ts +++ b/packages/browseros-agent/apps/agent/entrypoints/app/agents/useRuntime.ts @@ -56,6 +56,24 @@ export const RUNTIME_QUERY_KEYS = { logs: (adapter: RuntimeAdapterId) => ['runtime-logs', adapter] as const, } as const +export function useRuntimes(opts: { pollMs?: number } = {}) { + const rpcClient = useRpcClient() + return useQuery({ + queryKey: [RUNTIME_QUERY_KEYS.list], + queryFn: async () => { + const res = await rpcClient.runtimes.$get() + if (!res.ok) { + const body = (await res.json()) as { error?: string } + throw new Error(body.error ?? 'runtimes list fetch failed') + } + const { runtimes } = (await res.json()) as { runtimes: RuntimeView[] } + return runtimes + }, + refetchInterval: opts.pollMs ?? 5_000, + retry: false, + }) +} + export function useRuntime( adapter: RuntimeAdapterId, opts: { pollMs?: number; enabled?: boolean } = {},