mirror of
https://github.com/stablyai/orca.git
synced 2026-10-16 08:03:09 +00:00
Disable automation runs for stale host setup
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -267,3 +267,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-12: Completed Settings ownership/account rows by adding a shared provider Host scope control with a Change Host action that opens Settings > Active Server from GitHub/GitLab/Linear/Jira account surfaces and GitHub/GitLab API budget panels. Existing ownership diagnostics still distinguish desktop-client versus remote-server credentials, and rate-limit panels show the budget Host scope. Verified with settings ownership, RuntimeEnvironmentsPane, provider account, source-control card, task-tracker card, Jira card, and rate-limit panel tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Completed Tasks account/host metadata by showing repo-backed account labels in source summaries when GitHub/GitLab accounts differ by Host, while preserving host labels, provider identities, Linear workspace names, Jira site names, and unavailable-host status in labels/titles. Verified with source-summary and repo picker tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Continued the automation project-id migration by changing CLI automation show output to foreground explicit `runContext` fields (`runProjectId`, `runHostId`, `projectHostSetupId`, `runRepoId`, `runPath`) and label the old repo id as `legacyRepoId`. The checklist row remains partial because the shared `Automation.projectId` compatibility field still exists. Verified with CLI format/index tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Aligned Automations manual-run disabled states with the backend run-target resolver so saved run contexts now disable Run Now when the project-host setup is missing or not ready, instead of failing after dispatch. Verified with focused automation availability tests, targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
|
||||
@@ -497,6 +497,7 @@ export default function AutomationsPage(): React.JSX.Element {
|
||||
automation: selected,
|
||||
repo: selectedRepo,
|
||||
workspace: selectedWorktree,
|
||||
projectHostSetups,
|
||||
sshConnectionStates
|
||||
})
|
||||
: null
|
||||
@@ -1248,6 +1249,7 @@ export default function AutomationsPage(): React.JSX.Element {
|
||||
automation,
|
||||
repo,
|
||||
workspace,
|
||||
projectHostSetups,
|
||||
sshConnectionStates
|
||||
})
|
||||
if (!availability.canRunNow) {
|
||||
@@ -1830,6 +1832,7 @@ export default function AutomationsPage(): React.JSX.Element {
|
||||
automation,
|
||||
repo: automationRepo,
|
||||
workspace: automationWorktree,
|
||||
projectHostSetups,
|
||||
sshConnectionStates
|
||||
})
|
||||
const workspaceLabel =
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Automation } from '../../../../shared/automations-types'
|
||||
import type { Repo, Worktree } from '../../../../shared/types'
|
||||
import type { ProjectHostSetup, Repo, Worktree } from '../../../../shared/types'
|
||||
import { getAutomationTargetAvailability } from './automation-target-availability'
|
||||
|
||||
function makeAutomation(overrides: Partial<Automation> = {}): Automation {
|
||||
@@ -53,6 +53,22 @@ function makeWorkspace(overrides: Partial<Worktree> = {}): Worktree {
|
||||
} as Worktree
|
||||
}
|
||||
|
||||
function makeProjectHostSetup(overrides: Partial<ProjectHostSetup> = {}): ProjectHostSetup {
|
||||
return {
|
||||
id: 'setup-1',
|
||||
projectId: 'project-1',
|
||||
hostId: 'local',
|
||||
repoId: 'repo-1',
|
||||
path: '/repo',
|
||||
displayName: 'Repo',
|
||||
setupState: 'ready',
|
||||
setupMethod: 'legacy-repo',
|
||||
createdAt: 1,
|
||||
updatedAt: 1,
|
||||
...overrides
|
||||
}
|
||||
}
|
||||
|
||||
describe('automation target availability', () => {
|
||||
it('allows local automations with an available existing workspace', () => {
|
||||
expect(
|
||||
@@ -60,6 +76,7 @@ describe('automation target availability', () => {
|
||||
automation: makeAutomation(),
|
||||
repo: makeRepo(),
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [],
|
||||
sshConnectionStates: new Map()
|
||||
})
|
||||
).toEqual({ canRunNow: true, reason: 'available', message: null })
|
||||
@@ -71,6 +88,7 @@ describe('automation target availability', () => {
|
||||
automation: makeAutomation(),
|
||||
repo: null,
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [],
|
||||
sshConnectionStates: new Map()
|
||||
}).reason
|
||||
).toBe('missing-project')
|
||||
@@ -80,6 +98,7 @@ describe('automation target availability', () => {
|
||||
automation: makeAutomation(),
|
||||
repo: makeRepo(),
|
||||
workspace: null,
|
||||
projectHostSetups: [],
|
||||
sshConnectionStates: new Map()
|
||||
}).reason
|
||||
).toBe('missing-workspace')
|
||||
@@ -100,11 +119,48 @@ describe('automation target availability', () => {
|
||||
}),
|
||||
repo: makeRepo(),
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [makeProjectHostSetup()],
|
||||
sshConnectionStates: new Map()
|
||||
}).reason
|
||||
).toBe('host-mismatch')
|
||||
})
|
||||
|
||||
it('blocks saved run contexts whose project host setup is missing or not ready', () => {
|
||||
const automation = makeAutomation({
|
||||
runContext: {
|
||||
kind: 'workspace-run',
|
||||
projectId: 'project-1',
|
||||
hostId: 'local',
|
||||
projectHostSetupId: 'setup-1',
|
||||
repoId: 'repo-1',
|
||||
path: '/repo'
|
||||
}
|
||||
})
|
||||
|
||||
expect(
|
||||
getAutomationTargetAvailability({
|
||||
automation,
|
||||
repo: makeRepo(),
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [],
|
||||
sshConnectionStates: new Map()
|
||||
}).reason
|
||||
).toBe('missing-project-host-setup')
|
||||
|
||||
expect(
|
||||
getAutomationTargetAvailability({
|
||||
automation,
|
||||
repo: makeRepo(),
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [makeProjectHostSetup({ setupState: 'error' })],
|
||||
sshConnectionStates: new Map()
|
||||
})
|
||||
).toMatchObject({
|
||||
reason: 'project-host-setup-not-ready',
|
||||
message: 'Project setup on the selected automation host is error.'
|
||||
})
|
||||
})
|
||||
|
||||
it('requires SSH hosts to be connected before manual runs', () => {
|
||||
const automation = makeAutomation({
|
||||
executionTargetType: 'ssh',
|
||||
@@ -125,6 +181,13 @@ describe('automation target availability', () => {
|
||||
automation,
|
||||
repo,
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({
|
||||
hostId: 'ssh:devbox',
|
||||
connectionId: 'devbox',
|
||||
executionHostId: 'ssh:devbox'
|
||||
})
|
||||
],
|
||||
sshConnectionStates: new Map([['devbox', { status: 'connected' }]])
|
||||
}).canRunNow
|
||||
).toBe(true)
|
||||
@@ -134,6 +197,13 @@ describe('automation target availability', () => {
|
||||
automation,
|
||||
repo,
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({
|
||||
hostId: 'ssh:devbox',
|
||||
connectionId: 'devbox',
|
||||
executionHostId: 'ssh:devbox'
|
||||
})
|
||||
],
|
||||
sshConnectionStates: new Map([['devbox', { status: 'disconnected' }]])
|
||||
}).reason
|
||||
).toBe('ssh-unavailable')
|
||||
@@ -143,6 +213,13 @@ describe('automation target availability', () => {
|
||||
automation,
|
||||
repo,
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({
|
||||
hostId: 'ssh:devbox',
|
||||
connectionId: 'devbox',
|
||||
executionHostId: 'ssh:devbox'
|
||||
})
|
||||
],
|
||||
sshConnectionStates: new Map([['devbox', { status: 'auth-failed' }]])
|
||||
}).reason
|
||||
).toBe('ssh-auth-needed')
|
||||
@@ -152,6 +229,13 @@ describe('automation target availability', () => {
|
||||
automation,
|
||||
repo,
|
||||
workspace: makeWorkspace(),
|
||||
projectHostSetups: [
|
||||
makeProjectHostSetup({
|
||||
hostId: 'ssh:devbox',
|
||||
connectionId: 'devbox',
|
||||
executionHostId: 'ssh:devbox'
|
||||
})
|
||||
],
|
||||
sshConnectionStates: new Map([['devbox', { status: 'reconnecting' }]])
|
||||
}).reason
|
||||
).toBe('ssh-connecting')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Automation } from '../../../../shared/automations-types'
|
||||
import { getRepoExecutionHostId, parseExecutionHostId } from '../../../../shared/execution-host'
|
||||
import type { SshConnectionState } from '../../../../shared/ssh-types'
|
||||
import type { Repo, Worktree } from '../../../../shared/types'
|
||||
import type { ProjectHostSetup, Repo, Worktree } from '../../../../shared/types'
|
||||
|
||||
export type AutomationTargetAvailability =
|
||||
| {
|
||||
@@ -13,6 +13,8 @@ export type AutomationTargetAvailability =
|
||||
canRunNow: false
|
||||
reason:
|
||||
| 'missing-project'
|
||||
| 'missing-project-host-setup'
|
||||
| 'project-host-setup-not-ready'
|
||||
| 'missing-workspace'
|
||||
| 'host-mismatch'
|
||||
| 'unsupported-host'
|
||||
@@ -26,6 +28,7 @@ type AutomationTargetAvailabilityArgs = {
|
||||
automation: Automation
|
||||
repo: Repo | null | undefined
|
||||
workspace: Worktree | null | undefined
|
||||
projectHostSetups: readonly ProjectHostSetup[]
|
||||
sshConnectionStates: ReadonlyMap<string, Pick<SshConnectionState, 'status'>>
|
||||
}
|
||||
|
||||
@@ -33,6 +36,7 @@ export function getAutomationTargetAvailability({
|
||||
automation,
|
||||
repo,
|
||||
workspace,
|
||||
projectHostSetups,
|
||||
sshConnectionStates
|
||||
}: AutomationTargetAvailabilityArgs): AutomationTargetAvailability {
|
||||
if (!repo) {
|
||||
@@ -46,7 +50,26 @@ export function getAutomationTargetAvailability({
|
||||
'This automation targets a remote server that this client cannot run manually yet.'
|
||||
)
|
||||
}
|
||||
const setup = projectHostSetups.find(
|
||||
(candidate) => candidate.id === automation.runContext?.projectHostSetupId
|
||||
)
|
||||
if (!setup) {
|
||||
return unavailable(
|
||||
'missing-project-host-setup',
|
||||
'Project is not set up on the selected automation host anymore.'
|
||||
)
|
||||
}
|
||||
if (setup.setupState !== 'ready') {
|
||||
return unavailable(
|
||||
'project-host-setup-not-ready',
|
||||
`Project setup on the selected automation host is ${setup.setupState}.`
|
||||
)
|
||||
}
|
||||
if (
|
||||
setup.projectId !== automation.runContext.projectId ||
|
||||
setup.hostId !== automation.runContext.hostId ||
|
||||
setup.repoId !== automation.runContext.repoId ||
|
||||
setup.path !== automation.runContext.path ||
|
||||
automation.runContext.repoId !== repo.id ||
|
||||
automation.runContext.path !== repo.path ||
|
||||
automation.runContext.hostId !== getRepoExecutionHostId(repo)
|
||||
|
||||
Reference in New Issue
Block a user