diff --git a/frontend/src/components/ui-new/actions/index.ts b/frontend/src/components/ui-new/actions/index.ts index 02aca096..a017bf42 100644 --- a/frontend/src/components/ui-new/actions/index.ts +++ b/frontend/src/components/ui-new/actions/index.ts @@ -104,6 +104,9 @@ export interface ActionVisibilityContext { hasMultipleRepos: boolean; hasOpenPR: boolean; hasUnpushedCommits: boolean; + + // Execution state + isAttemptRunning: boolean; } // Base properties shared by all actions @@ -729,6 +732,49 @@ export const Actions = { invalidateWorkspaceQueries(ctx.queryClient, workspaceId); }, }, + + // === Script Actions === + RunSetupScript: { + id: 'run-setup-script', + label: 'Run Setup Script', + icon: TerminalIcon, + requiresTarget: true, + isVisible: (ctx) => ctx.hasWorkspace, + isEnabled: (ctx) => !ctx.isAttemptRunning, + execute: async (_ctx, workspaceId) => { + const result = await attemptsApi.runSetupScript(workspaceId); + if (!result.success) { + if (result.error?.type === 'no_script_configured') { + throw new Error('No setup script configured for this project'); + } + if (result.error?.type === 'process_already_running') { + throw new Error('Cannot run script while another process is running'); + } + throw new Error('Failed to run setup script'); + } + }, + }, + + RunCleanupScript: { + id: 'run-cleanup-script', + label: 'Run Cleanup Script', + icon: TerminalIcon, + requiresTarget: true, + isVisible: (ctx) => ctx.hasWorkspace, + isEnabled: (ctx) => !ctx.isAttemptRunning, + execute: async (_ctx, workspaceId) => { + const result = await attemptsApi.runCleanupScript(workspaceId); + if (!result.success) { + if (result.error?.type === 'no_script_configured') { + throw new Error('No cleanup script configured for this project'); + } + if (result.error?.type === 'process_already_running') { + throw new Error('Cannot run script while another process is running'); + } + throw new Error('Failed to run cleanup script'); + } + }, + }, } as const satisfies Record; // Helper to resolve dynamic label diff --git a/frontend/src/components/ui-new/actions/pages.ts b/frontend/src/components/ui-new/actions/pages.ts index f8f85ae9..1aed61d8 100644 --- a/frontend/src/components/ui-new/actions/pages.ts +++ b/frontend/src/components/ui-new/actions/pages.ts @@ -110,6 +110,14 @@ export const Pages: Record = { { type: 'action', action: Actions.DeleteWorkspace }, ], }, + { + type: 'group', + label: 'Scripts', + items: [ + { type: 'action', action: Actions.RunSetupScript }, + { type: 'action', action: Actions.RunCleanupScript }, + ], + }, ], }, diff --git a/frontend/src/components/ui-new/actions/useActionVisibility.ts b/frontend/src/components/ui-new/actions/useActionVisibility.ts index 2fed226d..a8e8e03f 100644 --- a/frontend/src/components/ui-new/actions/useActionVisibility.ts +++ b/frontend/src/components/ui-new/actions/useActionVisibility.ts @@ -6,6 +6,7 @@ import { useWorkspaceContext } from '@/contexts/WorkspaceContext'; import { useUserSystem } from '@/components/ConfigProvider'; import { useDevServer } from '@/hooks/useDevServer'; import { useBranchStatus } from '@/hooks/useBranchStatus'; +import { useExecutionProcessesContext } from '@/contexts/ExecutionProcessesContext'; import type { Workspace, Merge } from 'shared/types'; import type { ActionVisibilityContext, @@ -31,6 +32,7 @@ export function useActionVisibilityContext(): ActionVisibilityContext { const { isStarting, isStopping, runningDevServers } = useDevServer(workspaceId); const { data: branchStatus } = useBranchStatus(workspaceId); + const { isAttemptRunningVisible } = useExecutionProcessesContext(); return useMemo(() => { // Compute isAllDiffsExpanded @@ -79,6 +81,7 @@ export function useActionVisibilityContext(): ActionVisibilityContext { hasMultipleRepos: repos.length > 1, hasOpenPR, hasUnpushedCommits, + isAttemptRunning: isAttemptRunningVisible, }; }, [ layout.isChangesMode, @@ -98,6 +101,7 @@ export function useActionVisibilityContext(): ActionVisibilityContext { isStopping, runningDevServers, branchStatus, + isAttemptRunningVisible, ]); }