From d41f22cbcc8daf77cc19ef1882ac7829409d5e9a Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Fri, 12 Jun 2026 23:37:14 -0700 Subject: [PATCH] Surface task source runtime capability gaps Co-authored-by: Orca --- .../host-context-implementation-checklist.md | 1 + src/renderer/src/components/TaskPage.tsx | 82 +++++++++++-------- .../task-page-source-switch-boundary.test.ts | 12 +++ .../task-source-context-summary.test.ts | 43 ++++++++++ .../components/task-source-context-summary.ts | 7 ++ 5 files changed, 110 insertions(+), 35 deletions(-) diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index d14ad2635d4..32fa05074d2 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -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. diff --git a/src/renderer/src/components/TaskPage.tsx b/src/renderer/src/components/TaskPage.tsx index 675b22a1dc8..5902d85385f 100644 --- a/src/renderer/src/components/TaskPage.tsx +++ b/src/renderer/src/components/TaskPage.tsx @@ -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> @@ -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 { diff --git a/src/renderer/src/components/task-page-source-switch-boundary.test.ts b/src/renderer/src/components/task-page-source-switch-boundary.test.ts index 65fbab86c79..a83708dcb78 100644 --- a/src/renderer/src/components/task-page-source-switch-boundary.test.ts +++ b/src/renderer/src/components/task-page-source-switch-boundary.test.ts @@ -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'") + }) }) diff --git a/src/renderer/src/components/task-source-context-summary.test.ts b/src/renderer/src/components/task-source-context-summary.test.ts index a54be26949e..04204793e96 100644 --- a/src/renderer/src/components/task-source-context-summary.test.ts +++ b/src/renderer/src/components/task-source-context-summary.test.ts @@ -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({ diff --git a/src/renderer/src/components/task-source-context-summary.ts b/src/renderer/src/components/task-source-context-summary.ts index 5543d19645d..f4d17e623c0 100644 --- a/src/renderer/src/components/task-source-context-summary.ts +++ b/src/renderer/src/components/task-source-context-summary.ts @@ -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) }