Show host context in task source picker

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 22:16:47 -07:00
co-authored by Orca
parent ff4f278d10
commit 0ac0acef33
4 changed files with 50 additions and 8 deletions
@@ -248,3 +248,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [x] 2026-06-12: Added a reusable Tasks source availability notice and wired unavailable source-host/account states into the Tasks source chrome, so disconnected/auth-needed/blocked hosts show an inline status, fully unavailable sources disable their icon, and partial multi-host failures remain selectable with warning copy; verified with focused source-summary tests, targeted oxlint, and full `pnpm run typecheck`.
- [x] 2026-06-12: Added explicit remote-runtime Host model capability names for project setup, task source context, and workspace run context, then surfaced a Settings saved-server summary that says whether Host model support is ready, unknown, or requires a server update for specific missing capabilities; verified with focused RuntimeEnvironmentsPane tests, targeted oxlint, and full `pnpm run typecheck`.
- [x] 2026-06-12: Continued the Host copy audit by replacing the SSH disconnected dialog's stale "remote repository" message with SSH-host wording; verified with a stale-copy scan, targeted oxlint, and full `pnpm run typecheck`.
- [x] 2026-06-12: Improved the Tasks project/source picker by removing the repo-row `SSH` tag and showing host-aware project detail (`Host · path`) when the picker is fed host context, so same-project-on-multiple-hosts is distinguishable without implying the project itself is SSH; verified with focused combobox tests, targeted oxlint, and full `pnpm run typecheck`.
+11
View File
@@ -2882,6 +2882,15 @@ export default function TaskPage(): React.JSX.Element {
),
[repos, settings, sshConnectionStates, sshTargetLabels, runtimeStatusByEnvironmentId]
)
const getTaskPickerRepoHostLabel = useCallback(
(repo: Repo): string | null => {
const provider = taskSource === 'gitlab' ? 'gitlab' : 'github'
const context = getTaskPageRepoSourceContext(repo, provider)
const hostId = context?.hostId ?? repo.executionHostId ?? 'local'
return hostRegistryById.get(hostId)?.label ?? null
},
[hostRegistryById, taskSource]
)
const taskSourceHostAvailability = useMemo<TaskSourceHostAvailability[]>(
() =>
taskSource === 'github' || taskSource === 'gitlab'
@@ -7508,6 +7517,7 @@ export default function TaskPage(): React.JSX.Element {
<RepoMultiCombobox
repos={eligibleRepos}
selected={repoSelection}
getRepoHostLabel={getTaskPickerRepoHostLabel}
onChange={(next) => {
setRepoSelection(next)
void updateSettings({ defaultRepoSelection: [...next] }).catch(() => {
@@ -8215,6 +8225,7 @@ export default function TaskPage(): React.JSX.Element {
<RepoMultiCombobox
repos={eligibleRepos}
selected={repoSelection}
getRepoHostLabel={getTaskPickerRepoHostLabel}
onChange={(next) => {
setRepoSelection(next)
void updateSettings({ defaultRepoSelection: [...next] }).catch(() => {
@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest'
import type { Repo } from '../../../../shared/types'
import { getRepoMultiComboboxDetail } from './repo-multi-combobox'
function repo(overrides: Partial<Repo> = {}): Repo {
return {
id: 'repo-1',
path: '/Users/jinwoo/orca',
displayName: 'orca',
badgeColor: '#999999',
addedAt: 1,
...overrides
}
}
describe('getRepoMultiComboboxDetail', () => {
it('shows host context before the path when available', () => {
expect(getRepoMultiComboboxDetail(repo(), 'Local Mac')).toBe('Local Mac · /Users/jinwoo/orca')
expect(getRepoMultiComboboxDetail(repo({ path: '/home/orca/orca' }), 'openclaw 2')).toBe(
'openclaw 2 · /home/orca/orca'
)
})
it('keeps the existing path-only detail when no host label is provided', () => {
expect(getRepoMultiComboboxDetail(repo(), null)).toBe('/Users/jinwoo/orca')
expect(getRepoMultiComboboxDetail(repo(), ' ')).toBe('/Users/jinwoo/orca')
})
})
@@ -1,5 +1,5 @@
import React, { useCallback, useMemo, useState } from 'react'
import { Check, ChevronsUpDown, Server } from 'lucide-react'
import { Check, ChevronsUpDown } from 'lucide-react'
import { Button } from '@/components/ui/button'
import {
Command,
@@ -28,6 +28,7 @@ type RepoMultiComboboxProps = {
* signal, so the caller can persist `null` (sticky-all) rather than a
* frozen snapshot that would exclude repos added later. */
onSelectAll: () => void
getRepoHostLabel?: (repo: Repo) => string | null | undefined
triggerClassName?: string
}
@@ -63,11 +64,17 @@ function renderTriggerLabel(repos: Repo[], selected: ReadonlySet<string>): React
)
}
export function getRepoMultiComboboxDetail(repo: Repo, hostLabel?: string | null): string {
const trimmedHostLabel = hostLabel?.trim()
return trimmedHostLabel ? `${trimmedHostLabel} · ${repo.path}` : repo.path
}
export default function RepoMultiCombobox({
repos,
selected,
onChange,
onSelectAll,
getRepoHostLabel,
triggerClassName
}: RepoMultiComboboxProps): React.JSX.Element {
const [open, setOpen] = useState(false)
@@ -188,6 +195,7 @@ export default function RepoMultiCombobox({
{filteredRepos.map((repo) => {
const isSelected = selected.has(repo.id)
const isLastSelected = isSelected && selected.size <= 1
const detail = getRepoMultiComboboxDetail(repo, getRepoHostLabel?.(repo))
return (
<CommandItem
key={repo.id}
@@ -209,14 +217,8 @@ export default function RepoMultiCombobox({
color={repo.badgeColor}
className="max-w-full"
/>
{repo.connectionId && (
<span className="shrink-0 inline-flex items-center gap-0.5 rounded bg-muted px-1 py-0.5 text-[9px] font-medium leading-none text-muted-foreground">
<Server className="size-2.5" />
{translate('auto.components.ui.repo.multi.combobox.286ce70256', 'SSH')}
</span>
)}
</span>
<p className="mt-0.5 truncate text-[10px] text-muted-foreground">{repo.path}</p>
<p className="mt-0.5 truncate text-[10px] text-muted-foreground">{detail}</p>
</div>
</CommandItem>
)