Skip to content

Commit 0e9fcdb

Browse files
authored
fix: check if a repo exists in task detail (#107)
1 parent a858340 commit 0e9fcdb

File tree

3 files changed

+107
-11
lines changed

3 files changed

+107
-11
lines changed

src/renderer/features/tasks/components/TaskDetail.tsx

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { GearIcon, GlobeIcon } from "@radix-ui/react-icons";
1414
import {
1515
Box,
1616
Button,
17+
Callout,
1718
Code,
1819
DataList,
1920
Flex,
@@ -24,7 +25,9 @@ import {
2425
Tooltip,
2526
} from "@radix-ui/themes";
2627
import type { Task } from "@shared/types";
28+
import { cloneStore } from "@stores/cloneStore";
2729
import { useLayoutStore } from "@stores/layoutStore";
30+
import { repositoryWorkspaceStore } from "@stores/repositoryWorkspaceStore";
2831
import { useTabStore } from "@stores/tabStore";
2932
import { expandTildePath } from "@utils/path";
3033
import { format, formatDistanceToNow } from "date-fns";
@@ -63,12 +66,13 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
6366

6467
const task = tasks.find((t) => t.id === initialTask.id) || initialTask;
6568

66-
const taskState = getTaskState(task.id);
69+
const taskState = getTaskState(task.id, task);
6770

6871
const {
6972
isRunning,
7073
logs,
7174
repoPath,
75+
repoExists,
7276
runMode,
7377
progress,
7478
planModePhase,
@@ -96,6 +100,15 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
96100
return `${expandedWorkspace}/${task.repository_config.repository}`;
97101
}, [task.repository_config, defaultWorkspace]);
98102

103+
// Check if repository is being cloned using existing cloneStore method
104+
const isCloningRepo = cloneStore((state) =>
105+
task.repository_config
106+
? state.isCloning(
107+
`${task.repository_config.organization}/${task.repository_config.repository}`,
108+
)
109+
: false,
110+
);
111+
99112
useEffect(() => {
100113
resetForm({
101114
title: task.title,
@@ -136,6 +149,21 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
136149
clearTaskLogs(task.id);
137150
};
138151

152+
const handleCloneRepository = async () => {
153+
if (!task.repository_config) return;
154+
await repositoryWorkspaceStore
155+
.getState()
156+
.selectRepository(task.repository_config);
157+
};
158+
159+
const getRunButtonLabel = () => {
160+
if (isRunning) return "Running...";
161+
if (isCloningRepo) return "Cloning...";
162+
if (runMode === "cloud") return "Run (Cloud)";
163+
if (repoExists === false) return "Clone repository";
164+
return "Run (Local)";
165+
};
166+
139167
const handleAnswersComplete = async (
140168
answers: Array<{
141169
questionId: string;
@@ -361,6 +389,17 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
361389
</Flex>
362390

363391
<Flex direction="column" gap="3" mt="4">
392+
{/* Repository status */}
393+
{repoExists === false &&
394+
task.repository_config &&
395+
runMode === "local" && (
396+
<Callout.Root color="gray" size="2">
397+
<Callout.Text size="1">
398+
Repository not in workspace. Clone to run agent locally.
399+
</Callout.Text>
400+
</Callout.Root>
401+
)}
402+
364403
{/* Task Artifacts */}
365404
{repoPath && (
366405
<TaskArtifacts
@@ -374,23 +413,23 @@ export function TaskDetail({ task: initialTask }: TaskDetailProps) {
374413
<Flex gap="2">
375414
<Button
376415
variant="classic"
377-
onClick={handleRunTask}
378-
disabled={isRunning}
416+
onClick={
417+
runMode === "local" && repoExists === false
418+
? handleCloneRepository
419+
: handleRunTask
420+
}
421+
disabled={isRunning || isCloningRepo}
379422
size="2"
380423
style={{ flex: 1 }}
381424
>
382-
{isRunning
383-
? "Running..."
384-
: runMode === "cloud"
385-
? "Run (Cloud)"
386-
: "Run (Local)"}
425+
{getRunButtonLabel()}
387426
</Button>
388427
<Tooltip content="Toggle between Local or Cloud Agent">
389428
<IconButton
390429
size="2"
391430
variant="classic"
392431
color={runMode === "cloud" ? "blue" : "gray"}
393-
disabled={isRunning}
432+
disabled={isRunning || isCloningRepo}
394433
onClick={() =>
395434
handleRunModeChange(
396435
runMode === "local" ? "cloud" : "local",

src/renderer/features/tasks/stores/taskExecutionStore.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ interface TaskExecutionState {
8383
isRunning: boolean;
8484
logs: AgentEvent[];
8585
repoPath: string | null;
86+
repoExists: boolean | null;
8687
currentTaskId: string | null;
8788
runMode: "local" | "cloud";
8889
unsubscribe: (() => void) | null;
@@ -102,7 +103,7 @@ interface TaskExecutionStore {
102103
taskStates: Record<string, TaskExecutionState>;
103104

104105
// Basic state accessors
105-
getTaskState: (taskId: string) => TaskExecutionState;
106+
getTaskState: (taskId: string, task?: Task) => TaskExecutionState;
106107
updateTaskState: (
107108
taskId: string,
108109
updates: Partial<TaskExecutionState>,
@@ -140,6 +141,7 @@ interface TaskExecutionStore {
140141

141142
// Auto-initialization and artifact processing
142143
initializeRepoPath: (taskId: string, task: Task) => void;
144+
revalidateRepo: (taskId: string) => Promise<void>;
143145
processLogsForArtifacts: (taskId: string) => void;
144146
checkPlanCompletion: (taskId: string) => Promise<void>;
145147
}
@@ -148,6 +150,7 @@ const defaultTaskState: TaskExecutionState = {
148150
isRunning: false,
149151
logs: [],
150152
repoPath: null,
153+
repoExists: null,
151154
currentTaskId: null,
152155
runMode: "local",
153156
unsubscribe: null,
@@ -166,8 +169,11 @@ export const useTaskExecutionStore = create<TaskExecutionStore>()(
166169
(set, get) => ({
167170
taskStates: {},
168171

169-
getTaskState: (taskId: string) => {
172+
getTaskState: (taskId: string, task?: Task) => {
170173
const state = get();
174+
if (task) {
175+
state.initializeRepoPath(taskId, task);
176+
}
171177
return state.taskStates[taskId] || { ...defaultTaskState };
172178
},
173179

@@ -582,6 +588,32 @@ export const useTaskExecutionStore = create<TaskExecutionStore>()(
582588
task.repository_config.repository,
583589
);
584590
store.setRepoPath(taskId, path);
591+
592+
// Validate repo exists
593+
window.electronAPI
594+
?.validateRepo(path)
595+
.then((exists) => {
596+
store.updateTaskState(taskId, { repoExists: exists });
597+
})
598+
.catch(() => {
599+
store.updateTaskState(taskId, { repoExists: false });
600+
});
601+
},
602+
603+
revalidateRepo: async (taskId: string) => {
604+
const store = get();
605+
const taskState = store.getTaskState(taskId);
606+
607+
if (!taskState.repoPath) return;
608+
609+
try {
610+
const exists = await window.electronAPI?.validateRepo(
611+
taskState.repoPath,
612+
);
613+
store.updateTaskState(taskId, { repoExists: exists });
614+
} catch {
615+
store.updateTaskState(taskId, { repoExists: false });
616+
}
585617
},
586618

587619
processLogsForArtifacts: (taskId: string) => {

src/renderer/stores/cloneStore.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useTaskExecutionStore } from "@features/tasks/stores/taskExecutionStore";
12
import type { RepositoryConfig } from "@shared/types";
23
import { toast } from "@utils/toast";
34
import { create } from "zustand";
@@ -35,12 +36,30 @@ const getRepoKey = (repo: RepositoryConfig) =>
3536
`${repo.organization}/${repo.repository}`;
3637

3738
export const cloneStore = create<CloneStore>((set, get) => {
39+
const updateTaskRepoExists = (targetPath: string, exists: boolean) => {
40+
const taskStore = useTaskExecutionStore.getState();
41+
Object.keys(taskStore.taskStates).forEach((taskId) => {
42+
const taskState = taskStore.taskStates[taskId];
43+
if (taskState?.repoPath === targetPath) {
44+
taskStore.updateTaskState(taskId, { repoExists: exists });
45+
}
46+
47+
taskStore.revalidateRepo(taskId);
48+
});
49+
};
50+
3851
const handleComplete = (
3952
cloneId: string,
4053
repoKey: string,
4154
toastId: string | number,
4255
) => {
4356
toast.success(`${repoKey} cloned successfully`, { id: toastId });
57+
58+
const operation = get().operations[cloneId];
59+
if (operation) {
60+
updateTaskRepoExists(operation.targetPath, true);
61+
}
62+
4463
window.setTimeout(
4564
() => get().removeClone(cloneId),
4665
REMOVE_DELAY_SUCCESS_MS,
@@ -57,6 +76,12 @@ export const cloneStore = create<CloneStore>((set, get) => {
5776
id: toastId,
5877
description: message,
5978
});
79+
80+
const operation = get().operations[cloneId];
81+
if (operation) {
82+
updateTaskRepoExists(operation.targetPath, false);
83+
}
84+
6085
window.setTimeout(() => get().removeClone(cloneId), REMOVE_DELAY_ERROR_MS);
6186
};
6287

0 commit comments

Comments
 (0)