diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index de831348e5a..300cc9d4ccf 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -219,6 +219,7 @@ Done means: reviewers and future agents can understand the vision, implementatio - [x] 2026-06-12: Tightened the project-first design doc so host-aware Add Project is the current path for browse/import, clone, and create, while bulk setup and cloud VM provisioning are future host-onboarding work. Re-verified shared host-model tests covering local/SSH/runtime host kinds, runtime capability gating, SSH reconnect/disconnect actions, host settings overrides, and task source host scopes. - [x] 2026-06-12: Tightened runtime project-host setup availability so remote servers must advertise both `project-host-setup.v1` and `workspace-run-context.v1` before users can choose them for project setup/workspace creation; verified with focused setup option tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`. - [x] 2026-06-13: Verified Linear Tasks in the live Electron app with the connected legacy Linear API-key workspace: the visible Linear list rendered 36 issues, source-scoped cache keys included both the legacy local account key and an explicit `linear:local:...` task-source cache key, and screenshot evidence was added to the dark verification report. Jira remains unverified because this profile has no connected Jira account. +- [x] 2026-06-13: Added Automations detail source-context display so saved automations show their provider source/account/host separately from run location; verified with focused source-display tests, targeted oxlint, `git diff --check`, and full `pnpm run typecheck`. - [x] 2026-06-12: Live-verified Add Project disconnected-host UX in Electron by simulating the SSH target as disconnected in renderer state: the Host picker defaulted to Local Mac and showed the SSH host as a disabled muted row; restored SSH to connected and added screenshot evidence to `notes/multihost-remote-verification-report.html`. - [x] 2026-06-12: Audited Settings ownership for Source Control AI defaults, repository Source Control AI overrides, agent launch defaults, quick commands, workspace directories, and provider accounts; added a tested settings-ownership helper and surfaced the ownership language in Settings while preserving the existing Windows/WSL account and agent-location pattern. Verified with focused settings tests and web typecheck. - [x] 2026-06-12: Improved remote-server Settings version-skew diagnostics by showing the exact compatibility explanation plus named runtime capabilities instead of only a capability count; verified with `RuntimeEnvironmentsPane` tests and web typecheck. diff --git a/src/renderer/src/components/automations/AutomationDetail.tsx b/src/renderer/src/components/automations/AutomationDetail.tsx index 1ab6f87c4fa..7c346aa27f4 100644 --- a/src/renderer/src/components/automations/AutomationDetail.tsx +++ b/src/renderer/src/components/automations/AutomationDetail.tsx @@ -14,6 +14,7 @@ import { summarizeAutomationRunUsage } from './automation-usage-model' import type { AutomationTargetAvailability } from './automation-target-availability' +import { getAutomationSourceDisplay } from './automation-source-display' import { translate } from '@/i18n/i18n' type AutomationDetailProps = { @@ -30,11 +31,21 @@ type AutomationDetailProps = { onDelete: (automation: Automation) => void } -function DetailMetric({ label, value }: { label: string; value: string }): React.JSX.Element { +function DetailMetric({ + label, + value, + title +}: { + label: string + value: string + title?: string +}): React.JSX.Element { return (
{label}
-
{value}
+
+ {value} +
) } @@ -118,6 +129,7 @@ export function AutomationDetail({ automation.workspaceMode === 'new_per_run' ? (automation.baseBranch ?? projectDefaultBaseRef ?? 'Project default') : workspaceName + const sourceDisplay = getAutomationSourceDisplay(automation.sourceContext) const runNowDisabled = runNowAvailability?.canRunNow === false return ( @@ -235,6 +247,13 @@ export function AutomationDetail({ label={translate('auto.components.automations.AutomationDetail.15ea446b93', 'Session')} value={automation.reuseSession ? 'Reuse live session' : 'Fresh each run'} /> + {sourceDisplay ? ( + + ) : null} { + it('summarizes repo-backed source context separately from run location', () => { + const sourceContext: TaskSourceContext = { + kind: 'task-source', + provider: 'github', + hostId: 'ssh:devbox', + projectId: 'github:stablyai/orca', + projectHostSetupId: 'setup-devbox', + repoId: 'repo-devbox', + accountLabel: 'dev@example.com', + providerIdentity: { + provider: 'github', + owner: 'stablyai', + repo: 'orca' + } + } + + expect(getAutomationSourceDisplay(sourceContext)).toEqual({ + label: 'GitHub · devbox · stablyai/orca', + title: 'GitHub source · Host: devbox · Account: dev@example.com · Source: stablyai/orca' + }) + }) + + it('uses account identity for Linear sources', () => { + const sourceContext: TaskSourceContext = { + kind: 'task-source', + provider: 'linear', + hostId: 'local', + projectId: 'repo-1', + projectHostSetupId: 'setup-local', + repoId: 'repo-1', + accountLabel: 'Linear API key', + providerIdentity: { + provider: 'linear', + workspaceId: 'legacy', + workspaceName: 'Saved Linear workspace' + } + } + + expect(getAutomationSourceDisplay(sourceContext)).toEqual({ + label: 'Linear · Local Mac · Saved Linear workspace', + title: + 'Linear source · Host: Local Mac · Account: Linear API key · Source: Saved Linear workspace' + }) + }) + + it('returns null when no source context is saved', () => { + expect(getAutomationSourceDisplay(null)).toBeNull() + }) +}) diff --git a/src/renderer/src/components/automations/automation-source-display.ts b/src/renderer/src/components/automations/automation-source-display.ts new file mode 100644 index 00000000000..5e1166e6d9b --- /dev/null +++ b/src/renderer/src/components/automations/automation-source-display.ts @@ -0,0 +1,62 @@ +import { getExecutionHostLabel } from '../../../../shared/execution-host' +import type { TaskSourceContext } from '../../../../shared/task-source-context' + +export type AutomationSourceDisplay = { + label: string + title: string +} + +export function getAutomationSourceDisplay( + sourceContext: TaskSourceContext | null | undefined +): AutomationSourceDisplay | null { + if (!sourceContext) { + return null + } + const providerLabel = getProviderLabel(sourceContext.provider) + const hostLabel = getExecutionHostLabel(sourceContext.hostId) + const identityLabel = getSourceIdentityLabel(sourceContext) + const label = [providerLabel, hostLabel, identityLabel] + .filter((part): part is string => Boolean(part)) + .join(' · ') + const title = [ + `${providerLabel} source`, + `Host: ${hostLabel}`, + sourceContext.accountLabel ? `Account: ${sourceContext.accountLabel}` : null, + identityLabel ? `Source: ${identityLabel}` : null + ] + .filter((part): part is string => Boolean(part)) + .join(' · ') + return { label, title } +} + +function getProviderLabel(provider: TaskSourceContext['provider']): string { + switch (provider) { + case 'github': + return 'GitHub' + case 'gitlab': + return 'GitLab' + case 'linear': + return 'Linear' + case 'jira': + return 'Jira' + } +} + +function getSourceIdentityLabel(sourceContext: TaskSourceContext): string | null { + const identity = sourceContext.providerIdentity + if (identity) { + switch (identity.provider) { + case 'github': + return `${identity.owner}/${identity.repo}` + case 'gitlab': + return identity.namespace && identity.project + ? `${identity.namespace}/${identity.project}` + : (identity.projectId ?? null) + case 'linear': + return identity.workspaceName ?? identity.workspaceId ?? null + case 'jira': + return identity.siteUrl ?? identity.siteId ?? null + } + } + return sourceContext.accountLabel ?? sourceContext.repoId ?? null +}