feat: opt-in fresh-branch checkout for local executor task creation (#695)

This commit is contained in:
José Almeida
2026-04-25 23:53:43 +01:00
committed by GitHub
parent 54b98ed9eb
commit ec9637c517
27 changed files with 2664 additions and 617 deletions
@@ -9,6 +9,10 @@ import { AgentLogo } from "@/components/agent-logo";
import type { DialogFormState } from "@/components/task-create-dialog-types";
import type { useKeyboardShortcutHandler } from "@/hooks/use-keyboard-shortcut";
import { TaskFormInputs } from "@/components/task-create-dialog-selectors";
import {
FreshBranchToggle,
computeBranchPlaceholder,
} from "@/components/task-create-dialog-fresh-branch";
import type { JiraTicket } from "@/lib/types/jira";
type SelectorOption = {
@@ -67,94 +71,124 @@ type CreateEditSelectorsProps = {
isLocalExecutor: boolean;
useGitHubUrl: boolean;
workflowAgentLocked: boolean;
freshBranchEnabled: boolean;
onToggleFreshBranch: (enabled: boolean) => void;
currentLocalBranch: string;
};
export const CreateEditSelectors = memo(function CreateEditSelectors({
isTaskStarted,
hasRepositorySelection,
branchOptions,
branch,
onBranchChange,
branchesLoading,
localBranchesLoading,
type AgentColumnProps = Pick<
CreateEditSelectorsProps,
| "agentProfiles"
| "agentProfilesLoading"
| "agentProfileOptions"
| "agentProfileId"
| "onAgentProfileChange"
| "isCreatingSession"
| "AgentSelectorComponent"
| "workflowAgentLocked"
>;
function AgentColumn({
agentProfiles,
agentProfilesLoading,
agentProfileOptions,
agentProfileId,
onAgentProfileChange,
isCreatingSession,
executorProfileOptions,
executorProfileId,
onExecutorProfileChange,
executorsLoading,
isLocalExecutor,
useGitHubUrl,
workflowAgentLocked,
BranchSelectorComponent,
AgentSelectorComponent,
ExecutorProfileSelectorComponent,
}: CreateEditSelectorsProps) {
if (isTaskStarted) return null;
workflowAgentLocked,
}: AgentColumnProps) {
if (agentProfiles.length === 0 && !agentProfilesLoading) {
return (
<div className="flex h-7 items-center justify-center gap-2 rounded-sm border border-input px-3 text-xs text-muted-foreground">
<span>No agents found.</span>
<Link href="/settings/agents" className="cursor-pointer text-primary hover:underline">
Add agent
</Link>
</div>
);
}
const placeholder = agentProfilesLoading ? "Loading agents..." : "Select agent";
return (
<>
<AgentSelectorComponent
options={agentProfileOptions}
value={agentProfileId}
onValueChange={onAgentProfileChange}
placeholder={placeholder}
disabled={agentProfilesLoading || isCreatingSession || workflowAgentLocked}
/>
{workflowAgentLocked && (
<p className="text-[11px] text-muted-foreground mt-1">Agent set by workflow</p>
)}
</>
);
}
const agentLockedByWorkflow = workflowAgentLocked;
export const CreateEditSelectors = memo(function CreateEditSelectors(
props: CreateEditSelectorsProps,
) {
if (props.isTaskStarted) return null;
const {
hasRepositorySelection,
branchOptions,
branch,
onBranchChange,
branchesLoading,
localBranchesLoading,
executorProfileOptions,
executorProfileId,
onExecutorProfileChange,
executorsLoading,
isLocalExecutor,
useGitHubUrl,
freshBranchEnabled,
onToggleFreshBranch,
currentLocalBranch,
BranchSelectorComponent,
ExecutorProfileSelectorComponent,
} = props;
const isLocalWithoutGitHubUrl = isLocalExecutor && !useGitHubUrl;
const branchPlaceholder = (() => {
if (isLocalWithoutGitHubUrl) return "Uses current branch";
if (!hasRepositorySelection) return "Select repository first";
if (branchesLoading || localBranchesLoading) return "Loading branches...";
return branchOptions.length > 0 ? "Select branch" : "No branches found";
})();
const lockedToCurrentBranch = isLocalWithoutGitHubUrl && !freshBranchEnabled;
const branchPlaceholder = computeBranchPlaceholder({
lockedToCurrentBranch,
currentLocalBranch,
hasRepositorySelection,
loading: branchesLoading || localBranchesLoading,
optionCount: branchOptions.length,
});
const branchDisabled =
isLocalWithoutGitHubUrl ||
lockedToCurrentBranch ||
!hasRepositorySelection ||
branchesLoading ||
localBranchesLoading ||
branchOptions.length === 0;
const agentPlaceholder = (() => {
if (agentProfilesLoading) return "Loading agents...";
if (agentProfiles.length === 0) return "No agents available";
return "Select agent";
})();
// When the local executor locks to the current branch, ignore any branch
// value the user picked under a different executor — the placeholder
// should reflect the actual checked-out branch instead.
const branchValue = lockedToCurrentBranch ? "" : branch;
return (
<div className="grid gap-4 grid-cols-1 sm:grid-cols-3">
<div>
<BranchSelectorComponent
options={branchOptions}
value={branch}
onValueChange={onBranchChange}
placeholder={branchPlaceholder}
searchPlaceholder="Search branches..."
emptyMessage="No branch found."
disabled={branchDisabled}
/>
<div className="flex items-center gap-2 min-w-0">
<div className="min-w-0 flex-1">
<BranchSelectorComponent
options={branchOptions}
value={branchValue}
onValueChange={onBranchChange}
placeholder={branchPlaceholder}
searchPlaceholder="Search branches..."
emptyMessage="No branch found."
disabled={branchDisabled}
/>
</div>
{isLocalWithoutGitHubUrl && (
<FreshBranchToggle enabled={freshBranchEnabled} onToggle={onToggleFreshBranch} />
)}
</div>
<div>
{agentProfiles.length === 0 && !agentProfilesLoading ? (
<div className="flex h-7 items-center justify-center gap-2 rounded-sm border border-input px-3 text-xs text-muted-foreground">
<span>No agents found.</span>
<Link href="/settings/agents" className="text-primary hover:underline">
Add agent
</Link>
</div>
) : (
<>
<AgentSelectorComponent
options={agentProfileOptions}
value={agentProfileId}
onValueChange={onAgentProfileChange}
placeholder={agentPlaceholder}
disabled={agentProfilesLoading || isCreatingSession || agentLockedByWorkflow}
/>
{agentLockedByWorkflow && (
<p className="text-[11px] text-muted-foreground mt-1">Agent set by workflow</p>
)}
</>
)}
<AgentColumn {...props} />
</div>
<div>
<ExecutorProfileSelectorComponent