mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 00:03:09 +00:00
Show automation source context in details
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 (
|
||||
<div className="min-w-0">
|
||||
<div className="text-[11px] font-medium uppercase text-muted-foreground">{label}</div>
|
||||
<div className="mt-1 break-words text-sm font-medium">{value}</div>
|
||||
<div className="mt-1 break-words text-sm font-medium" title={title}>
|
||||
{value}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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 ? (
|
||||
<DetailMetric
|
||||
label={translate('auto.components.automations.AutomationDetail.29baf8f4c2', 'Source')}
|
||||
value={sourceDisplay.label}
|
||||
title={sourceDisplay.title}
|
||||
/>
|
||||
) : null}
|
||||
<DetailMetric
|
||||
label={translate('auto.components.automations.AutomationDetail.620b22145e', 'Grace')}
|
||||
value={formatGrace(automation.missedRunGraceMinutes)}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { TaskSourceContext } from '../../../../shared/task-source-context'
|
||||
import { getAutomationSourceDisplay } from './automation-source-display'
|
||||
|
||||
describe('automation source display', () => {
|
||||
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()
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user