Surface task source runtime capability gaps

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 23:37:14 -07:00
co-authored by Orca
parent 8feae89bff
commit d41f22cbcc
5 changed files with 110 additions and 35 deletions
@@ -191,6 +191,7 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [x] 2026-06-12: Scoped Jira Tasks drawer/list cache reconciliation by Jira task source context and site id so same-key issues from different hosts/accounts cannot replace each other; verified with focused cache selector tests, targeted oxlint, and full typecheck.
- [x] 2026-06-12: Seeded New Workspace run-target selection from the incoming task source context so Linear/Jira starts preselect the logical project/source host instead of the ambient active repo; verified with a focused composer host-boundary test, targeted oxlint, and full typecheck.
- [x] 2026-06-12: Added explicit disabled-reason copy for external automation actions when an SSH host is disconnected/connecting, another action is in flight, or the local/remote automation source tool is missing; verified with focused availability tests, targeted oxlint, git diff --check, and full typecheck.
- [x] 2026-06-12: Added Tasks source-host availability for remote servers that are still loading capabilities or lack task-source-context.v1, so older servers degrade with explicit update/checking copy instead of looking connected-but-empty; verified with focused TaskPage/source-summary tests, targeted oxlint, git diff --check, and full typecheck.
- [x] 2026-06-12: Fed Tasks source-context diagnostics from the shared execution-host registry so the source chip now reports SSH availability and blocked/incompatible remote-server hosts; verified with summary tests and web typecheck.
- [x] 2026-06-12: Extended Linear/Jira Tasks source diagnostics to include the account-owning host plus blocked remote-server availability, keeping account source separate from workspace run host; verified with summary tests and web typecheck.
- [x] 2026-06-12: Started the user-facing Host copy audit by replacing stale “remote project” and “Remote SSH” UI labels with project-on-host / SSH-host language; verified with web typecheck.
+47 -35
View File
@@ -128,7 +128,10 @@ import PullRequestPage from '@/components/PullRequestPage'
import GitLabItemDialog from '@/components/GitLabItemDialog'
import ProjectViewWrapper from '@/components/github-project/ProjectViewWrapper'
import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner'
import { buildExecutionHostRegistry } from '../../../shared/execution-host-registry'
import {
buildExecutionHostRegistry,
type ExecutionHostRegistryEntry
} from '../../../shared/execution-host-registry'
import { getHostDisplayLabelOverrides } from '../../../shared/host-setting-overrides'
import LinearIssueWorkspace from '@/components/LinearIssueWorkspace'
import {
@@ -152,6 +155,7 @@ import { buildLinearIssueLinkedWorkItem } from '@/lib/linear-linked-work-item'
import { isGitRepoKind } from '../../../shared/repo-kind'
import { getRepoExecutionHostId } from '../../../shared/execution-host'
import { projectHostSetupProjectionFromRepos } from '../../../shared/project-host-setup-projection'
import { TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY } from '../../../shared/protocol-version'
import {
getTaskSourceCacheScope,
getTaskSourceRuntimeSettings,
@@ -346,6 +350,37 @@ function getTaskPageRepoSourceContext(
})
}
function getTaskSourceHostAvailabilityForHost(
host: ExecutionHostRegistryEntry | null | undefined,
hostId: TaskSourceContext['hostId']
): TaskSourceHostAvailability | null {
if (!host) {
return null
}
if (host.kind === 'runtime') {
if (!host.capabilities) {
return {
hostId,
reason: 'checking-task-source-capability'
}
}
if (!host.capabilities.includes(TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY)) {
return {
hostId,
reason: 'missing-task-source-capability'
}
}
}
if (host.health === 'local' || host.health === 'available') {
return null
}
return {
hostId,
health: host.health,
status: host.connectionStatus
}
}
function getTaskPageRepoCacheInput(repo: Repo): {
id: string
path: string
@@ -2896,16 +2931,8 @@ export default function TaskPage(): React.JSX.Element {
taskSource === 'github' || taskSource === 'gitlab'
? taskSourceRepoContexts.flatMap((context) => {
const host = hostRegistryById.get(context.hostId)
if (!host || host.health === 'local' || host.health === 'available') {
return []
}
return [
{
hostId: context.hostId,
health: host.health,
status: host.connectionStatus
}
]
const availability = getTaskSourceHostAvailabilityForHost(host, context.hostId)
return availability ? [availability] : []
})
: [],
[hostRegistryById, taskSource, taskSourceRepoContexts]
@@ -2972,16 +2999,8 @@ export default function TaskPage(): React.JSX.Element {
return []
}
const host = hostRegistryById.get(accountBackedTaskSourceHostId)
if (!host || host.health === 'local' || host.health === 'available') {
return []
}
return [
{
hostId: host.id,
health: host.health,
status: host.connectionStatus
}
]
const availability = getTaskSourceHostAvailabilityForHost(host, accountBackedTaskSourceHostId)
return availability ? [availability] : []
}, [accountBackedTaskSourceHostId, hostRegistryById, taskSource])
const taskSourceAvailabilityNoticeByProvider = useMemo<
Partial<Record<TaskProvider, TaskSourceAvailabilityNotice>>
@@ -2991,22 +3010,15 @@ export default function TaskPage(): React.JSX.Element {
): TaskSourceHostAvailability[] =>
contexts.flatMap((context) => {
const host = hostRegistryById.get(context.hostId)
if (!host || host.health === 'local' || host.health === 'available') {
return []
}
return [{ hostId: context.hostId, health: host.health, status: host.connectionStatus }]
const availability = getTaskSourceHostAvailabilityForHost(host, context.hostId)
return availability ? [availability] : []
})
const accountHost = hostRegistryById.get(accountBackedTaskSourceHostId)
const accountAvailability =
!accountHost || accountHost.health === 'local' || accountHost.health === 'available'
? []
: [
{
hostId: accountHost.id,
health: accountHost.health,
status: accountHost.connectionStatus
}
]
const accountHostAvailability = getTaskSourceHostAvailabilityForHost(
accountHost,
accountBackedTaskSourceHostId
)
const accountAvailability = accountHostAvailability ? [accountHostAvailability] : []
const labelFor = (provider: TaskProvider): string =>
sourceOptions.find((source) => source.id === provider)?.label ?? provider
return {
@@ -27,4 +27,16 @@ describe('TaskPage source switching host boundary', () => {
expect(section).not.toContain('projectHostSetupId')
expect(section).not.toContain('workspaceRunContext')
})
it('treats missing remote task-source capability as source unavailable', () => {
const section = sourceBetween(
TASK_PAGE_SOURCE,
'function getTaskSourceHostAvailabilityForHost',
'function getTaskPageRepoCacheInput'
)
expect(section).toContain('TASK_SOURCE_CONTEXT_RUNTIME_CAPABILITY')
expect(section).toContain("reason: 'checking-task-source-capability'")
expect(section).toContain("reason: 'missing-task-source-capability'")
})
})
@@ -177,6 +177,49 @@ describe('task source context summary', () => {
)
})
it('shows remote-server task-source capability checks', () => {
const summary = getTaskSourceContextSummary({
provider: 'github',
providerLabel: 'GitHub',
selectedRepoCount: 1,
repoContexts: [
{
kind: 'task-source',
provider: 'github',
projectId: 'project-a',
hostId: 'runtime:old-server',
repoId: 'repo-a',
providerIdentity: { provider: 'github', owner: 'stablyai', repo: 'orca' }
}
],
hostAvailability: [
{ hostId: 'runtime:old-server', reason: 'checking-task-source-capability' }
]
})
expect(summary.label).toBe('GitHub · old-server · checking server capabilities · stablyai/orca')
expect(summary.title).toBe(
'GitHub · Host: old-server · Availability: old-server checking server capabilities · Source: stablyai/orca'
)
})
it('shows remote-server task-source capability version skew', () => {
expect(
getTaskSourceAvailabilityNotice({
providerLabel: 'GitHub',
sourceCount: 1,
hostAvailability: [
{ hostId: 'runtime:old-server', reason: 'missing-task-source-capability' }
]
})
).toEqual({
label: 'GitHub source unavailable: old-server server update needed for task sources',
title:
'Reconnect or update old-server server update needed for task sources to load this source.',
blocking: true
})
})
it('shows account-backed Linear and Jira sources', () => {
expect(
getTaskSourceContextSummary({
@@ -20,6 +20,7 @@ export type TaskSourceHostAvailability = {
hostId: ExecutionHostScope
status?: SshConnectionStatus
health?: ExecutionHostHealth
reason?: 'checking-task-source-capability' | 'missing-task-source-capability'
}
export function getTaskSourceContextSummary(args: {
@@ -205,6 +206,12 @@ function getUnavailableHosts(hostAvailability: readonly TaskSourceHostAvailabili
}
function getAvailabilityStatusLabel(availability: TaskSourceHostAvailability): string | null {
switch (availability.reason) {
case 'checking-task-source-capability':
return 'checking server capabilities'
case 'missing-task-source-capability':
return 'server update needed for task sources'
}
if (availability.status) {
return availability.status === 'connected' ? null : getSshStatusLabel(availability.status)
}