Skip to content

Commit d7bb808

Browse files
committed
feat: add directory prompt on run and cloud mode gating
When clicking "Run (Local)" without a directory set: - Tasks WITH repository_config: Prompt "Do you have it locally?" → "I have it locally" opens folder picker → "Clone for me" triggers clone flow with progress UI - Tasks WITHOUT repository_config: Prompt "Select working directory" → Opens folder picker Cloud mode changes: - Disable Cloud mode toggle for tasks without repository_config - Show tooltip: "Cloud mode requires a connected repository" Technical changes: - Updated runTask() in taskExecutionStore to prompt before showing error - Integrated with existing clone flow and progress UI (PR #128) - Persist selected directories via taskDirectoryStore (PR #130, #131) - Removed noisy directory source badges (simplified UX) This creates a frictionless flow where engineers can easily point to existing repos or clone them on-demand, while still supporting directory-only tasks for non-git workflows.
1 parent e93427d commit d7bb808

File tree

4 files changed

+106
-8
lines changed

4 files changed

+106
-8
lines changed

src/renderer/features/task-detail/components/TaskActions.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface TaskActionsProps {
77
isCloningRepo: boolean;
88
cloneProgress: { message: string; percent: number } | null;
99
runMode: "local" | "cloud";
10+
hasRepositoryConfig: boolean;
1011
onRunTask: () => void;
1112
onCancel: () => void;
1213
onRunModeChange: (mode: "local" | "cloud") => void;
@@ -17,6 +18,7 @@ export const TaskActions: React.FC<TaskActionsProps> = ({
1718
isCloningRepo,
1819
cloneProgress,
1920
runMode,
21+
hasRepositoryConfig,
2022
onRunTask,
2123
onCancel,
2224
onRunModeChange,
@@ -58,12 +60,18 @@ export const TaskActions: React.FC<TaskActionsProps> = ({
5860
>
5961
<span className="truncate">{getRunButtonLabel()}</span>
6062
</Button>
61-
<Tooltip content="Toggle between Local or Cloud Agent">
63+
<Tooltip
64+
content={
65+
!hasRepositoryConfig
66+
? "Cloud mode requires a connected repository"
67+
: "Toggle between Local or Cloud Agent"
68+
}
69+
>
6270
<IconButton
6371
size="2"
6472
variant="classic"
6573
color={runMode === "cloud" ? "blue" : "gray"}
66-
disabled={isRunning || isCloningRepo}
74+
disabled={isRunning || isCloningRepo || !hasRepositoryConfig}
6775
onClick={() =>
6876
onRunModeChange(runMode === "local" ? "cloud" : "local")
6977
}

src/renderer/features/task-detail/components/TaskDetailPanel.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function TaskDetailPanel({ taskId, task }: TaskDetailPanelProps) {
101101
isCloningRepo={repository.isCloning}
102102
cloneProgress={taskData.cloneProgress}
103103
runMode={execution.state.runMode}
104+
hasRepositoryConfig={!!taskData.task.repository_config}
104105
onRunTask={execution.actions.run}
105106
onCancel={execution.actions.cancel}
106107
onRunModeChange={execution.actions.onRunModeChange}

src/renderer/features/task-detail/components/TaskMetadata.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
2+
import { useTaskExecutionStore } from "@features/task-detail/stores/taskExecutionStore";
13
import { Button, Code, DataList, Link, Text, Tooltip } from "@radix-ui/themes";
24
import type { Task } from "@shared/types";
35
import { format, formatDistanceToNow } from "date-fns";
46
import type React from "react";
5-
import { FolderPicker } from "@features/folder-picker/components/FolderPicker";
6-
import { useTaskExecutionStore } from "@features/task-detail/stores/taskExecutionStore";
77

88
interface TaskMetadataProps {
99
task: Task;

src/renderer/features/task-detail/stores/taskExecutionStore.ts

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,11 +461,100 @@ export const useTaskExecutionStore = create<TaskExecutionStore>()(
461461
const effectiveRepoPath = taskState.repoPath;
462462

463463
if (!effectiveRepoPath) {
464-
store.addLog(taskId, {
465-
type: "error",
466-
ts: Date.now(),
467-
message: "No repository folder selected.",
464+
// Prompt user to select directory or clone
465+
const hasRepo = !!task.repository_config;
466+
const repoConfig = task.repository_config;
467+
468+
const result = await window.electronAPI.showMessageBox({
469+
type: "question",
470+
title: "Select working directory",
471+
message:
472+
hasRepo && repoConfig
473+
? `Do you have ${repoConfig.organization}/${repoConfig.repository} locally?`
474+
: "Select a working directory for this task",
475+
detail: hasRepo
476+
? "If you have the repository locally, we'll use that. Otherwise, we can clone it for you."
477+
: "Choose a directory where the task will run.",
478+
buttons: hasRepo
479+
? ["I have it locally", "Clone for me", "Cancel"]
480+
: ["Select directory", "Cancel"],
481+
defaultId: 0,
482+
cancelId: hasRepo ? 2 : 1,
468483
});
484+
485+
if (result.response === (hasRepo ? 2 : 1)) {
486+
// User cancelled
487+
return;
488+
}
489+
490+
if (result.response === 0) {
491+
// User has repo locally or wants to select directory
492+
const selectedPath = await window.electronAPI.selectDirectory();
493+
494+
if (!selectedPath) {
495+
// User cancelled directory selection
496+
return;
497+
}
498+
499+
// Set the repo path and revalidate
500+
store.setRepoPath(taskId, selectedPath);
501+
await store.revalidateRepo(taskId);
502+
503+
// Retry running the task with the new path
504+
return store.runTask(taskId, task);
505+
}
506+
507+
if (result.response === 1 && hasRepo && repoConfig) {
508+
// User wants to clone - trigger clone and retry
509+
const { repositoryWorkspaceStore } = await import(
510+
"@stores/repositoryWorkspaceStore"
511+
);
512+
513+
// Derive default path from workspace
514+
const { defaultWorkspace } = useAuthStore.getState();
515+
if (!defaultWorkspace) {
516+
store.addLog(taskId, {
517+
type: "error",
518+
ts: Date.now(),
519+
message:
520+
"No workspace configured. Please configure a workspace in settings.",
521+
});
522+
return;
523+
}
524+
525+
const derivedPath = derivePath(
526+
defaultWorkspace,
527+
repoConfig.repository,
528+
);
529+
store.setRepoPath(taskId, derivedPath);
530+
531+
const cloneId = `clone-${Date.now()}-${Math.random().toString(36).substring(7)}`;
532+
cloneStore.getState().startClone(cloneId, repoConfig, derivedPath);
533+
534+
try {
535+
await repositoryWorkspaceStore
536+
.getState()
537+
.selectRepository(repoConfig, cloneId);
538+
539+
// Wait for clone to complete, then retry run
540+
// The clone progress will show in the UI via TaskActions
541+
// We return here and let the user manually retry after clone completes
542+
store.addLog(taskId, {
543+
type: "token",
544+
ts: Date.now(),
545+
content: `Cloning ${repoConfig.organization}/${repoConfig.repository}... Click Run again after the clone completes.`,
546+
});
547+
return;
548+
} catch (error) {
549+
store.addLog(taskId, {
550+
type: "error",
551+
ts: Date.now(),
552+
message: `Failed to clone repository: ${error instanceof Error ? error.message : "Unknown error"}`,
553+
});
554+
return;
555+
}
556+
}
557+
469558
return;
470559
}
471560

0 commit comments

Comments
 (0)