From 386bd268c3daeac8eea8071e427567bbd642b844 Mon Sep 17 00:00:00 2001 From: Jinwoo-H Date: Fri, 12 Jun 2026 23:42:01 -0700 Subject: [PATCH] Persist automation run context from UI saves Co-authored-by: Orca --- .../host-context-implementation-checklist.md | 1 + .../automations/AutomationsPage.tsx | 18 +++++ .../automation-run-context.test.ts | 74 +++++++++++++++++++ .../automations/automation-run-context.ts | 29 ++++++++ 4 files changed, 122 insertions(+) create mode 100644 src/renderer/src/components/automations/automation-run-context.test.ts create mode 100644 src/renderer/src/components/automations/automation-run-context.ts diff --git a/notes/host-context-implementation-checklist.md b/notes/host-context-implementation-checklist.md index 32fa05074d2..64dd6a5d44e 100644 --- a/notes/host-context-implementation-checklist.md +++ b/notes/host-context-implementation-checklist.md @@ -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. diff --git a/src/renderer/src/components/automations/AutomationsPage.tsx b/src/renderer/src/components/automations/AutomationsPage.tsx index b6a3266d6d9..6963ebd67c9 100644 --- a/src/renderer/src/components/automations/AutomationsPage.tsx +++ b/src/renderer/src/components/automations/AutomationsPage.tsx @@ -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, diff --git a/src/renderer/src/components/automations/automation-run-context.test.ts b/src/renderer/src/components/automations/automation-run-context.test.ts new file mode 100644 index 00000000000..345b03d56f7 --- /dev/null +++ b/src/renderer/src/components/automations/automation-run-context.test.ts @@ -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 { + 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() + }) +}) diff --git a/src/renderer/src/components/automations/automation-run-context.ts b/src/renderer/src/components/automations/automation-run-context.ts new file mode 100644 index 00000000000..65bad47c9f3 --- /dev/null +++ b/src/renderer/src/components/automations/automation-run-context.ts @@ -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 + }) +}