Use shared host registry for project setup choices

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-11 12:34:00 -07:00
co-authored by Orca
parent aa5a0b3b1e
commit 16cbd773e3
8 changed files with 166 additions and 36 deletions
@@ -510,6 +510,8 @@ Already partially landed:
metadata from hosts that have no setup
- workspace creation target resolution reports `setup-not-ready` when a
selected host has non-ready setup metadata
- project settings setup-host choices now use the shared execution host
registry, including hydrated runtime hosts that are not currently focused
Not complete yet:
@@ -514,6 +514,8 @@ Already partially landed:
metadata from hosts that have no setup
- workspace creation target resolution reports `setup-not-ready` when a
selected host has non-ready setup metadata
- project settings setup-host choices now use the shared execution host
registry, including hydrated runtime hosts that are not currently focused
Not complete yet:
@@ -432,6 +432,8 @@ Already partially landed:
metadata from hosts that have no setup
- workspace creation target resolution reports `setup-not-ready` when a
selected host has non-ready setup metadata
- project settings setup-host choices now use the shared execution host
registry, including hydrated runtime hosts that are not currently focused
Still needed:
+6 -1
View File
@@ -794,6 +794,10 @@ Landed so far:
- Updated workspace creation target resolution so selecting a host with
non-ready setup metadata reports `setup-not-ready` instead of
`project-not-set-up-on-host`.
- Broadened project settings setup-host choices to use the shared execution
host registry. The setup flow can now offer hydrated runtime hosts even when
they are not the currently focused Orca server, and host labels stay aligned
with sidebar rename overrides.
- Added tests for local repos, SSH repos, same-provider multi-host grouping,
no-identity same-name non-grouping, selector cache behavior, persistence
backfill, repo mutation synchronization, renderer hydration, runtime RPC
@@ -829,7 +833,8 @@ Important limitation:
Remaining end-to-end work:
- broaden setup-on-host flows beyond known local, SSH, and active runtime hosts
- continue broadening setup-on-host flows beyond the shared known-host registry
toward bulk setup and newly added host onboarding
- finish SSH clone streamed-progress parity, provisioning, and bulk setup-on-host
flows
- add actual provisioning execution APIs instead of only recording independent
@@ -320,4 +320,78 @@ describe('RepositoryHostSetupsSection', () => {
setupMethod: 'provisioned'
})
})
it('offers inactive runtime hosts discovered from hydrated runtime status', async () => {
const createProjectHostSetup = vi.fn().mockResolvedValue({
project: makeProject({ id: 'github:stablyai/orca' }),
setup: makeSetup({
id: 'gpu-setup',
projectId: 'github:stablyai/orca',
repoId: '',
hostId: 'runtime:gpu',
path: '',
setupState: 'not-set-up',
setupMethod: 'provisioned'
})
})
const localRepo = makeRepo({
id: 'local-repo',
displayName: 'Orca',
path: '/Users/alice/orca'
})
useAppStore.setState({
repos: [localRepo],
projects: [makeProject({ id: 'github:stablyai/orca' })],
projectHostSetups: [
makeSetup({
id: 'local-repo',
projectId: 'github:stablyai/orca',
repoId: 'local-repo',
hostId: 'local',
path: '/Users/alice/orca'
})
],
settings: { activeRuntimeEnvironmentId: null } as never,
runtimeStatusByEnvironmentId: new Map([
[
'gpu',
{
checkedAt: 1,
appVersion: '1.8.0',
status: {
runtimeId: 'runtime-gpu',
rendererGraphEpoch: 1,
graphStatus: 'ready',
authoritativeWindowId: 1,
liveTabCount: 0,
liveLeafCount: 0,
runtimeProtocolVersion: 1,
minCompatibleRuntimeClientVersion: 1,
capabilities: ['project-host-setup.v1']
}
}
]
]),
createProjectHostSetup
})
renderSection(localRepo)
expect(container.textContent).toContain('gpu')
const trackButton = Array.from(container.querySelectorAll('button')).find(
(button) => button.textContent === 'Track setup'
)
await act(async () => {
trackButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
})
expect(createProjectHostSetup).toHaveBeenCalledWith({
projectId: 'github:stablyai/orca',
hostId: 'runtime:gpu',
displayName: 'Orca',
setupState: 'not-set-up',
setupMethod: 'provisioned'
})
})
})
@@ -1,11 +1,11 @@
import { useState } from 'react'
import { useMemo, useState } from 'react'
import { getExecutionHostLabel } from '../../../../shared/execution-host'
import { type ExecutionHostId } from '../../../../shared/execution-host'
import {
LOCAL_EXECUTION_HOST_ID,
toRuntimeExecutionHostId,
toSshExecutionHostId,
type ExecutionHostId
} from '../../../../shared/execution-host'
buildExecutionHostRegistry,
type ExecutionHostRegistryEntry
} from '../../../../shared/execution-host-registry'
import { getHostDisplayLabelOverrides } from '../../../../shared/host-setting-overrides'
import type { ProjectHostSetup, ProjectHostSetupState, Repo } from '../../../../shared/types'
import { useAppStore } from '../../store'
import { getProjectHostSetupProjectionFromState } from '../../store/selectors'
@@ -58,35 +58,18 @@ function getSetupStateLabel(setupState: ProjectHostSetupState): string {
function buildSetupHostOptions({
projectHostSetups,
sshTargetLabels,
activeRuntimeEnvironmentId
hostOptions
}: {
projectHostSetups: ProjectHostSetup[]
sshTargetLabels: Map<string, string>
activeRuntimeEnvironmentId: string | null | undefined
hostOptions: readonly ExecutionHostRegistryEntry[]
}): SetupHostOption[] {
const setupHostIds = new Set(projectHostSetups.map((setup) => setup.hostId))
const options: SetupHostOption[] = []
if (!setupHostIds.has(LOCAL_EXECUTION_HOST_ID)) {
options.push({
id: LOCAL_EXECUTION_HOST_ID,
label: getExecutionHostLabel(LOCAL_EXECUTION_HOST_ID)
})
}
for (const [targetId, label] of sshTargetLabels) {
const id = toSshExecutionHostId(targetId)
if (!setupHostIds.has(id)) {
options.push({ id, label })
}
}
const runtimeEnvironmentId = activeRuntimeEnvironmentId?.trim()
if (runtimeEnvironmentId) {
const id = toRuntimeExecutionHostId(runtimeEnvironmentId)
if (!setupHostIds.has(id)) {
options.push({ id, label: runtimeEnvironmentId })
}
}
return options
return hostOptions
.filter((host) => !setupHostIds.has(host.id))
.map((host) => ({
id: host.id,
label: host.label || getExecutionHostLabel(host.id)
}))
}
export function RepositoryHostSetupsSection({
@@ -100,9 +83,30 @@ export function RepositoryHostSetupsSection({
const setupProjectExistingFolder = useAppStore((state) => state.setupProjectExistingFolder)
const createProjectHostSetup = useAppStore((state) => state.createProjectHostSetup)
const deleteProjectHostSetup = useAppStore((state) => state.deleteProjectHostSetup)
const repos = useAppStore((state) => state.repos)
const sshTargetLabels = useAppStore((state) => state.sshTargetLabels)
const activeRuntimeEnvironmentId = useAppStore(
(state) => state.settings?.activeRuntimeEnvironmentId
const sshConnectionStates = useAppStore((state) => state.sshConnectionStates)
const settings = useAppStore((state) => state.settings)
const runtimeStatusByEnvironmentId = useAppStore((state) => state.runtimeStatusByEnvironmentId)
const hostLabelOverrides = useMemo(() => getHostDisplayLabelOverrides(settings), [settings])
const hostOptions = useMemo(
() =>
buildExecutionHostRegistry({
repos,
settings,
sshTargetLabels,
sshConnectionStates,
runtimeStatusByEnvironmentId,
hostLabelOverrides
}),
[
repos,
settings,
sshTargetLabels,
sshConnectionStates,
runtimeStatusByEnvironmentId,
hostLabelOverrides
]
)
const projectHostSetupProjection = useAppStore((state) =>
getProjectHostSetupProjectionFromState(state)
@@ -117,8 +121,7 @@ export function RepositoryHostSetupsSection({
: []
const setupHostOptions = buildSetupHostOptions({
projectHostSetups,
sshTargetLabels,
activeRuntimeEnvironmentId
hostOptions
})
const [selectedSetupHostId, setSelectedSetupHostId] = useState<ExecutionHostId | null>(null)
const [setupPath, setSetupPath] = useState('')
@@ -162,4 +162,43 @@ describe('execution host registry', () => {
{ id: 'runtime:env-2', kind: 'runtime', label: 'env-2', health: 'available' }
])
})
it('includes runtime hosts from hydrated status even when they are not focused', () => {
const hosts = buildExecutionHostRegistry({
repos: [],
settings: { activeRuntimeEnvironmentId: null },
runtimeStatusByEnvironmentId: new Map([
[
'gpu',
{
appVersion: '1.8.0',
status: {
runtimeId: 'runtime-gpu',
rendererGraphEpoch: 1,
graphStatus: 'ready',
authoritativeWindowId: 1,
liveTabCount: 0,
liveLeafCount: 0,
runtimeProtocolVersion: RUNTIME_PROTOCOL_VERSION,
minCompatibleRuntimeClientVersion: 1,
capabilities: ['project-host-setup.v1'],
hostPlatform: 'linux'
}
}
]
])
})
expect(hosts).toMatchObject([
{ id: 'local', health: 'local' },
{
id: 'runtime:gpu',
kind: 'runtime',
label: 'gpu',
health: 'available',
capabilities: ['project-host-setup.v1'],
platform: 'linux'
}
])
})
})
+3
View File
@@ -161,6 +161,9 @@ export function buildExecutionHostRegistry(args: {
args.runtimeStatusByEnvironmentId
)
}
for (const environmentId of args.runtimeStatusByEnvironmentId?.keys() ?? []) {
addRuntimeHost(hosts, environmentId, environmentId, args.runtimeStatusByEnvironmentId)
}
const focusedHost = getSettingsFocusedExecutionHostId(args.settings)
const parsedFocusedHost = parseExecutionHostId(focusedHost)