Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
4401e30
feat(server): add /runtimes/* route surface
DaniAkash May 8, 2026
983e433
feat(ui): add useRuntime / useRuntimeAction / useRuntimeLogs hooks
DaniAkash May 8, 2026
8eb911d
feat(ui): RuntimeStatusBar + RuntimeControlPanel components
DaniAkash May 8, 2026
c099a35
refactor(ui): wire RuntimeStatusBar + RuntimeControlPanel on AgentsPa…
DaniAkash May 8, 2026
8f68d12
chore: merge feat/openclaw-runtime — picks up bundled-Lima fallback fix
DaniAkash May 8, 2026
ab63827
fix(openclaw): sync runtime state from existing container at boot; re…
DaniAkash May 8, 2026
4ccb7ac
fix(openclaw): reconcile drifted gateway host port from live container
DaniAkash May 8, 2026
830eeba
fix(openclaw): stop stale gateway before re-allocating port on auth m…
DaniAkash May 8, 2026
349c374
fix(openclaw): seed empty .env in runtime so direct Start works on a …
DaniAkash May 8, 2026
d6440bd
refactor(openclaw): derive legacy gateway status from runtime state
DaniAkash May 11, 2026
7392244
refactor(openclaw): delete duplicated service-level lifecycle methods
DaniAkash May 11, 2026
4806eb4
refactor(openclaw): drop /claw/status, getStatus, and the gateway block
DaniAkash May 11, 2026
fdc6b80
refactor(openclaw): move gateway port ownership into the runtime
DaniAkash May 11, 2026
9632b60
refactor(openclaw): delete legacy UI helpers + types
DaniAkash May 11, 2026
b6172a4
feat(agents): per-runtime install/start controls via RuntimesSection
DaniAkash May 11, 2026
e770310
fix(container): poll readiness probe within descriptor budget on start
DaniAkash May 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export const AgentCommandHome: FC = () => {
// from the layout context (handles legacy /claw/agents entries that
// haven't yet been backfilled into the harness store). The Recent
// Agents grid below reads the richer harness payload directly.
const { agents: legacyAgents, status } = useAgentCommandData()
const { agents: legacyAgents, openClawReady } = useAgentCommandData()
const { harnessAgents } = useHarnessAgents()
const { adapters } = useAgentAdapters()
const [selectedAgentId, setSelectedAgentId] = useState<string | null>(null)
Expand Down Expand Up @@ -146,10 +146,13 @@ export const AgentCommandHome: FC = () => {
(agent) => agent.agentId === selectedAgentId,
)
const selectedAgentReady = selectedAgent
? selectedAgent.source === 'agent-harness' || status?.status === 'running'
? selectedAgent.source === 'agent-harness' || openClawReady
: false
const selectedAgentStatus =
selectedAgent?.source === 'agent-harness' ? 'running' : status?.status
const selectedAgentStatus = selectedAgent
? selectedAgent.source === 'agent-harness' || openClawReady
? 'running'
: 'stopped'
: undefined
const selectedAgentName =
selectedAgent?.name ?? orderedAgents[0]?.name ?? 'your agent'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,25 @@
import type { FC } from 'react'
import { Outlet, useOutletContext } from 'react-router'
import { useHarnessAgents } from '@/entrypoints/app/agents/useAgents'
import type {
AgentEntry,
OpenClawStatus,
} from '@/entrypoints/app/agents/useOpenClaw'
import {
useOpenClawAgents,
useOpenClawStatus,
} from '@/entrypoints/app/agents/useOpenClaw'
import type { AgentEntry } from '@/entrypoints/app/agents/useOpenClaw'
import { useOpenClawAgents } from '@/entrypoints/app/agents/useOpenClaw'
import { useRuntime } from '@/entrypoints/app/agents/useRuntime'

interface AgentCommandContextValue {
agents: AgentEntry[]
agentsLoading: boolean
status: OpenClawStatus | null
statusLoading: boolean
openClawReady: boolean
openClawReadyLoading: boolean
}

export const AgentCommandLayout: FC = () => {
const { status, loading: statusLoading } = useOpenClawStatus(5000)
const openClawEnabled =
status?.status === 'running' && status.controlPlaneStatus === 'connected'
const { data: runtime, isLoading: runtimeLoading } = useRuntime('openclaw')
const openClawReady = runtime?.status.state === 'running'
const { agents: openClawAgents, loading: openClawAgentsLoading } =
useOpenClawAgents(openClawEnabled)
useOpenClawAgents(openClawReady)
const { agents: harnessAgents, loading: harnessAgentsLoading } =
useHarnessAgents()
const visibleOpenClawAgents = openClawEnabled ? openClawAgents : []
const visibleOpenClawAgents = openClawReady ? openClawAgents : []
// Dual-created OpenClaw agents appear in both `/claw/agents` (gateway
// record) and `/agents` (harness record) under the same id. Prefer the
// harness entry so the chat panel can route through the harness path
Expand All @@ -43,10 +37,10 @@ export const AgentCommandLayout: FC = () => {
agents,
agentsLoading:
harnessAgentsLoading ||
statusLoading ||
(openClawEnabled && openClawAgentsLoading),
status,
statusLoading,
runtimeLoading ||
(openClawReady && openClawAgentsLoading),
openClawReady,
openClawReadyLoading: runtimeLoading,
} satisfies AgentCommandContextValue
}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Loader2 } from 'lucide-react'
import { Loader2, Terminal as TerminalIcon } from 'lucide-react'
import { type FC, useMemo, useState } from 'react'
import { useNavigate } from 'react-router'
import { Button } from '@/components/ui/button'
import { useLlmProviders } from '@/lib/llm-providers/useLlmProviders'
import { AgentList } from './AgentList'
import { AgentsHeader } from './AgentsHeader'
Expand All @@ -19,26 +20,15 @@ import {
DEFAULT_HARNESS_ADAPTER,
} from './agents-page-types'
import {
canManageOpenClawAgents,
getAgentsLoading,
getControlPlaneCopyForStatus,
getGatewayUiState,
getInlineError,
getLifecycleBanner,
getRecoveryDetail,
getVisibleOpenClawAgents,
shouldShowControlPlaneDegraded,
toHarnessListItem,
toOpenClawListItem,
} from './agents-page-utils'
import { GatewayStatusBar } from './GatewayStatusBar'
import { NewAgentDialog } from './NewAgentDialog'
import {
ControlPlaneAlert,
GatewayStateCards,
InlineErrorAlert,
LifecycleAlert,
} from './OpenClawControls'
import { InlineErrorAlert } from './OpenClawControls'
import { RuntimesSection } from './runtime-controls/RuntimesSection'
import { SetupOpenClawDialog } from './SetupOpenClawDialog'
import {
useAgentAdapters,
Expand All @@ -48,6 +38,7 @@ import {
useUpdateHarnessAgent,
} from './useAgents'
import { useOpenClawAgents, useOpenClawMutations } from './useOpenClaw'
import { useRuntime } from './useRuntime'

export const AgentsPage: FC = () => {
const navigate = useNavigate()
Expand All @@ -58,19 +49,15 @@ export const AgentsPage: FC = () => {
error: adaptersError,
} = useAgentAdapters()

// The harness listing now carries the gateway lifecycle snapshot
// alongside the agents — one polling source for everything the
// agents page renders. The legacy `/claw/status` poll is dead from
// this surface; the chat-panel layout still uses it for now.
const {
harnessAgents,
gateway: status,
loading: harnessAgentsLoading,
error: harnessAgentsError,
} = useHarnessAgents()
const { data: openClawRuntime } = useRuntime('openclaw')
const openClawRunning = openClawRuntime?.status.state === 'running'

const openClawAgentsEnabled =
status?.status === 'running' && status.controlPlaneStatus === 'connected'
const openClawAgentsEnabled = openClawRunning
const {
agents: openClawAgents,
loading: openClawAgentsLoading,
Expand All @@ -83,15 +70,9 @@ export const AgentsPage: FC = () => {
setupOpenClaw,
createAgent: createOpenClawAgent,
deleteAgent: deleteOpenClawAgent,
startOpenClaw,
restartOpenClaw,
reconnectOpenClaw,
actionInProgress,
settingUp,
creating: creatingOpenClawAgent,
deleting: deletingOpenClawAgent,
reconnecting,
pendingGatewayAction,
} = useOpenClawMutations()

const [setupOpen, setSetupOpen] = useState(false)
Expand Down Expand Up @@ -153,12 +134,10 @@ export const AgentsPage: FC = () => {
setHarnessReasoningEffort,
})

const lifecyclePending = pendingGatewayAction !== null
const gatewayUiState = useMemo(() => getGatewayUiState(status), [status])
const openClawManageable = canManageOpenClawAgents(
gatewayUiState,
lifecyclePending,
)
// Can the user create / modify OpenClaw agents? Yes when the runtime
// is running. The legacy gatewayUiState/controlPlaneStatus gating is
// gone — runtime state is the source of truth.
const openClawManageable = openClawRunning
const visibleOpenClawAgents = getVisibleOpenClawAgents(
openClawAgentsEnabled,
openClawAgents,
Expand Down Expand Up @@ -211,7 +190,7 @@ export const AgentsPage: FC = () => {
return map
}, [harnessAgents])
const inlineError = getInlineError({
lifecyclePending,
lifecyclePending: false,
pageError,
openClawAgentsError,
adaptersError,
Expand All @@ -232,31 +211,30 @@ export const AgentsPage: FC = () => {
setHarnessReasoningEffort(descriptor?.defaultReasoningEffort ?? '')
}

const { handleCreate, handleDelete, handleSetup, runWithPageErrorHandling } =
createAgentPageActions({
createProviderId,
createRuntime,
createHermesProviderId,
harnessModelId,
harnessReasoningEffort,
navigate,
newName,
selectableOpenClawProviders,
selectableHermesProviders,
setupProviderId,
createHarnessAgent: createHarnessAgent.mutateAsync,
createOpenClawAgent,
deleteHarnessAgent: deleteHarnessAgent.mutateAsync,
deleteOpenClawAgent,
setCliAuthModalOpen,
setCreateError,
setCreateOpen,
setDeletingAgentKey,
setNewName,
setPageError,
setSetupOpen,
setupOpenClaw,
})
const { handleCreate, handleDelete, handleSetup } = createAgentPageActions({
createProviderId,
createRuntime,
createHermesProviderId,
harnessModelId,
harnessReasoningEffort,
navigate,
newName,
selectableOpenClawProviders,
selectableHermesProviders,
setupProviderId,
createHarnessAgent: createHarnessAgent.mutateAsync,
createOpenClawAgent,
deleteHarnessAgent: deleteHarnessAgent.mutateAsync,
deleteOpenClawAgent,
setCliAuthModalOpen,
setCreateError,
setCreateOpen,
setDeletingAgentKey,
setNewName,
setPageError,
setSetupOpen,
setupOpenClaw,
})

if (showTerminal) {
return <AgentTerminal onBack={() => setShowTerminal(false)} />
Expand All @@ -274,84 +252,59 @@ export const AgentsPage: FC = () => {

// First-paint loader: until the harness listing has resolved at
// least once we don't know which adapters / agents to render.
if (harnessAgentsLoading && !status) {
if (harnessAgentsLoading && !openClawRuntime) {
return (
<div className="flex items-center justify-center py-20">
<Loader2 className="size-6 animate-spin text-muted-foreground" />
</div>
)
}

const showControlPlaneDegraded = shouldShowControlPlaneDegraded(
gatewayUiState,
lifecyclePending,
)
const lifecycleBanner = getLifecycleBanner(pendingGatewayAction)
const recoveryDetail = status ? getRecoveryDetail(status) : null
const controlPlaneCopy = getControlPlaneCopyForStatus(status)

// Bar only makes sense when the gateway is meaningfully alive 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 =
status?.status === 'running' &&
(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
// runtime isn't ready, so a fresh user sees both Install + Configure
// affordances. A future server endpoint can tell us "is setup done".
const showSetupCta = !openClawRunning

return (
<div className="min-h-full bg-background px-6 py-8">
<div className="fade-in slide-in-from-bottom-5 mx-auto flex w-full max-w-5xl animate-in flex-col gap-6 duration-500">
<AgentsHeader onCreateAgent={() => setCreateOpen(true)} />

{lifecycleBanner ? <LifecycleAlert message={lifecycleBanner} /> : null}

{inlineError ? (
<InlineErrorAlert
message={inlineError}
onDismiss={() => setPageError(null)}
/>
) : null}

{status && showControlPlaneDegraded ? (
<ControlPlaneAlert
actionInProgress={actionInProgress}
controlPlaneBusy={gatewayUiState.controlPlaneBusy}
controlPlaneCopy={controlPlaneCopy}
reconnecting={reconnecting}
recoveryDetail={recoveryDetail}
status={status}
onReconnect={() => {
void runWithPageErrorHandling(reconnectOpenClaw)
}}
onRestart={() => {
void runWithPageErrorHandling(restartOpenClaw)
}}
/>
) : null}

<GatewayStateCards
actionInProgress={actionInProgress}
status={status}
onOpenSetup={() => setSetupOpen(true)}
onRestart={() => {
void runWithPageErrorHandling(restartOpenClaw)
}}
onStart={() => {
void runWithPageErrorHandling(startOpenClaw)
<RuntimesSection
extras={{
openclaw: {
panelExtras: showSetupCta ? (
<Button
size="sm"
variant="outline"
onClick={() => setSetupOpen(true)}
>
Configure provider…
</Button>
) : null,
statusBarExtraActions: (
<Button
variant="ghost"
size="sm"
onClick={() => setShowTerminal(true)}
>
<TerminalIcon className="mr-1.5 h-3.5 w-3.5" />
Terminal
</Button>
),
},
}}
/>

{showGatewayStatusBar ? (
<GatewayStatusBar
status={status}
actionInProgress={actionInProgress}
onOpenTerminal={() => setShowTerminal(true)}
onRestart={() => {
void runWithPageErrorHandling(restartOpenClaw)
}}
/>
) : null}

<AgentList
agents={agentListItems}
activity={agentActivity}
Expand All @@ -375,7 +328,6 @@ export const AgentsPage: FC = () => {
})
}}
/>

<SetupOpenClawDialog
defaultProviderId={defaultProviderId}
open={setupOpen}
Expand All @@ -387,7 +339,6 @@ export const AgentsPage: FC = () => {
onProviderChange={setSetupProviderId}
onSetup={() => void handleSetup()}
/>

<NewAgentDialog
adapters={adapters}
canManageOpenClaw={openClawManageable}
Expand Down
Loading
Loading