Persist automation run context from UI saves

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-12 23:42:01 -07:00
co-authored by Orca
parent d41f22cbcc
commit 386bd268c3
4 changed files with 122 additions and 0 deletions
@@ -192,6 +192,7 @@ Done means: reviewers and future agents can understand the vision, implementatio
- [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: Updated the Automations UI save path to persist WorkspaceRunContext for created/edited Orca automations while keeping legacy projectId as repo-id compatibility data; verified with focused run-context 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.
@@ -89,6 +89,7 @@ import { AutomationRunPageFrame } from './AutomationRunPageFrame'
import { AutomationRunHistory } from './AutomationRunHistory'
import { getAutomationTemplates, type AutomationTemplate } from './automation-templates'
import { getAutomationTargetAvailability } from './automation-target-availability'
import { buildAutomationRunContextForRepo } from './automation-run-context'
import {
getExternalAutomationActionDisabledMessage,
getExternalAutomationSourceAvailability,
@@ -262,6 +263,7 @@ async function waitForAutomationRerunPendingVisibility(pendingStartedAt: number)
export default function AutomationsPage(): React.JSX.Element {
const repos = useAppStore((s) => s.repos)
const projectHostSetups = useAppStore((s) => s.projectHostSetups)
const worktreesByRepo = useAppStore((s) => s.worktreesByRepo)
const unifiedTabsByWorktree = useAppStore((s) => s.unifiedTabsByWorktree)
const activeWorktreeId = useAppStore((s) => s.activeWorktreeId)
@@ -1062,6 +1064,20 @@ export default function AutomationsPage(): React.JSX.Element {
? Math.max(0, rawMissedRunGraceMinutes)
: 720
const precheck = buildDraftPrecheck(draft)
const runContext = buildAutomationRunContextForRepo({
repoId: draft.projectId,
repos,
projectHostSetups
})
if (!runContext) {
toast.error(
translate(
'auto.components.automations.AutomationsPage.32534e7c9c',
'Choose an available workspace before saving.'
)
)
return
}
let currentAutomation = editingAutomationId
? (automations.find((automation) => automation.id === editingAutomationId) ?? null)
: null
@@ -1080,6 +1096,7 @@ export default function AutomationsPage(): React.JSX.Element {
prompt: draft.prompt,
precheck,
agentId: draft.agentId,
runContext,
projectId: draft.projectId,
workspaceMode: draft.workspaceMode,
workspaceId: draft.workspaceId,
@@ -1103,6 +1120,7 @@ export default function AutomationsPage(): React.JSX.Element {
prompt: draft.prompt,
precheck,
agentId: draft.agentId,
runContext,
projectId: draft.projectId,
workspaceMode: draft.workspaceMode,
workspaceId: draft.workspaceId,
@@ -0,0 +1,74 @@
import { describe, expect, it } from 'vitest'
import type { ProjectHostSetup, Repo } from '../../../../shared/types'
import { buildAutomationRunContextForRepo } from './automation-run-context'
function repo(id: string, path = `/repos/${id}`): Repo {
return {
id,
path,
displayName: id,
badgeColor: '#000000',
addedAt: 1
}
}
function setup(overrides: Partial<ProjectHostSetup> = {}): ProjectHostSetup {
return {
id: 'setup-builder',
projectId: 'github:stablyai/orca',
hostId: 'ssh:builder',
repoId: 'repo-builder',
path: '/remote/orca',
displayName: 'orca',
setupState: 'ready',
setupMethod: 'cloned',
createdAt: 1,
updatedAt: 1,
...overrides
}
}
describe('buildAutomationRunContextForRepo', () => {
it('persists logical project and host setup identity for the selected run repo', () => {
expect(
buildAutomationRunContextForRepo({
repoId: 'repo-builder',
repos: [repo('repo-local', '/local/orca'), repo('repo-builder', '/remote/orca')],
projectHostSetups: [
setup({
id: 'setup-local',
hostId: 'local',
repoId: 'repo-local',
path: '/local/orca'
}),
setup()
]
})
).toEqual({
kind: 'workspace-run',
projectId: 'github:stablyai/orca',
hostId: 'ssh:builder',
projectHostSetupId: 'setup-builder',
repoId: 'repo-builder',
path: '/remote/orca'
})
})
it('does not build a run context for missing or not-ready setups', () => {
expect(
buildAutomationRunContextForRepo({
repoId: 'repo-builder',
repos: [repo('repo-builder')],
projectHostSetups: [setup({ setupState: 'setting-up' })]
})
).toBeNull()
expect(
buildAutomationRunContextForRepo({
repoId: 'repo-builder',
repos: [],
projectHostSetups: [setup()]
})
).toBeNull()
})
})
@@ -0,0 +1,29 @@
import {
buildWorkspaceRunContext,
type WorkspaceRunContext
} from '../../../../shared/task-source-context'
import type { ProjectHostSetup, Repo } from '../../../../shared/types'
export function buildAutomationRunContextForRepo(args: {
repoId: string
repos: readonly Repo[]
projectHostSetups: readonly ProjectHostSetup[]
}): WorkspaceRunContext | null {
const setup = args.projectHostSetups.find(
(candidate) => candidate.repoId === args.repoId && candidate.setupState === 'ready'
)
if (!setup) {
return null
}
const repo = args.repos.find((candidate) => candidate.id === setup.repoId)
if (!repo) {
return null
}
return buildWorkspaceRunContext({
projectId: setup.projectId,
hostId: setup.hostId,
projectHostSetupId: setup.id,
repoId: setup.repoId,
path: setup.path || repo.path
})
}