From 36fb0b8e169e7209731eb199e74a61cb48474ddd Mon Sep 17 00:00:00 2001 From: soryu Date: Thu, 22 Jan 2026 00:00:58 +0000 Subject: Add repository selection to standalone task creation in mesh.tsx - Add 'Standalone Task' option in step 1 of task creation modal - Support creating tasks without requiring a contract first - Add repository type selector (Remote/Local) for standalone tasks - Integrate with getRepositorySuggestions API for repository history - Add DirectoryInput for local path selection with daemon directories - Include optional target repository path configuration New state variables: - isStandalone: boolean for standalone mode - standaloneRepoType: 'remote' | 'local' - standaloneRepoUrl: string for remote URLs - standaloneRepoPath: string for local paths - repoSuggestions: RepositoryHistoryEntry[] for suggestions - showRepoSuggestions: boolean for dropdown visibility Modified handleCreateTask to pass contractId: undefined for standalone tasks and use the standalone repository configuration. Co-Authored-By: Claude Opus 4.5 --- makima/frontend/src/routes/mesh.tsx | 458 +++++++++++++++++++++++++++--------- 1 file changed, 352 insertions(+), 106 deletions(-) (limited to 'makima/frontend/src') diff --git a/makima/frontend/src/routes/mesh.tsx b/makima/frontend/src/routes/mesh.tsx index 453bdff..b53ec20 100644 --- a/makima/frontend/src/routes/mesh.tsx +++ b/makima/frontend/src/routes/mesh.tsx @@ -8,8 +8,8 @@ import { UnifiedMeshChatInput } from "../components/mesh/UnifiedMeshChatInput"; import { ContractCompleteQuestion } from "../components/mesh/ContractCompleteQuestion"; import { useTasks } from "../hooks/useTasks"; import { useTaskSubscription, type TaskUpdateEvent, type TaskOutputEvent } from "../hooks/useTaskSubscription"; -import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary } from "../lib/api"; -import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, continueTask as continueTaskApi, resumeSupervisor, branchTask } from "../lib/api"; +import type { TaskWithSubtasks, MeshChatContext, ContractSummary, ContractWithRelations, DaemonDirectory, TaskSummary, RepositoryHistoryEntry } from "../lib/api"; +import { startTask as startTaskApi, stopTask as stopTaskApi, getTaskOutput, listContracts, getContract, getDaemonDirectories, getRepositorySuggestions, continueTask as continueTaskApi, resumeSupervisor, branchTask } from "../lib/api"; import { DirectoryInput } from "../components/mesh/DirectoryInput"; import { useAuth } from "../contexts/AuthContext"; import { useSupervisorQuestions } from "../contexts/SupervisorQuestionsContext"; @@ -125,6 +125,13 @@ export default function MeshPage() { const [newTaskName, setNewTaskName] = useState(""); const [newTaskRepoUrl, setNewTaskRepoUrl] = useState(null); const [newTaskTargetPath, setNewTaskTargetPath] = useState(""); + // Standalone task mode state + const [isStandalone, setIsStandalone] = useState(false); + const [standaloneRepoType, setStandaloneRepoType] = useState<"remote" | "local">("remote"); + const [standaloneRepoUrl, setStandaloneRepoUrl] = useState(""); + const [standaloneRepoPath, setStandaloneRepoPath] = useState(""); + const [repoSuggestions, setRepoSuggestions] = useState([]); + const [showRepoSuggestions, setShowRepoSuggestions] = useState(false); // Track which subtask's output we're viewing (null = parent task) const [viewingSubtaskId, setViewingSubtaskId] = useState(null); const [viewingSubtaskName, setViewingSubtaskName] = useState(null); @@ -496,6 +503,13 @@ export default function MeshPage() { setNewTaskName(""); setNewTaskRepoUrl(null); setNewTaskTargetPath(""); + // Reset standalone state + setIsStandalone(false); + setStandaloneRepoType("remote"); + setStandaloneRepoUrl(""); + setStandaloneRepoPath(""); + setRepoSuggestions([]); + setShowRepoSuggestions(false); setShowContractModal(true); } catch (e) { console.error("Failed to load contracts:", e); @@ -525,18 +539,78 @@ export default function MeshPage() { } }, []); + // Handle standalone task mode selection + const handleSelectStandalone = useCallback(async () => { + setIsStandalone(true); + setNewTaskName("Standalone Task"); + setModalStep(2); + // Fetch initial repository suggestions for remote type + try { + const res = await getRepositorySuggestions("remote", undefined, 10); + setRepoSuggestions(res.entries); + setShowRepoSuggestions(res.entries.length > 0); + } catch { + setRepoSuggestions([]); + setShowRepoSuggestions(false); + } + }, []); + + // Handle standalone repo type change and fetch suggestions + const handleStandaloneRepoTypeChange = useCallback(async (type: "remote" | "local") => { + setStandaloneRepoType(type); + setStandaloneRepoUrl(""); + setStandaloneRepoPath(""); + try { + const res = await getRepositorySuggestions(type, undefined, 10); + setRepoSuggestions(res.entries); + setShowRepoSuggestions(res.entries.length > 0); + } catch { + setRepoSuggestions([]); + setShowRepoSuggestions(false); + } + }, []); + + // Apply a repository suggestion + const applyRepoSuggestion = useCallback((suggestion: RepositoryHistoryEntry) => { + if (suggestion.repositoryUrl) { + setStandaloneRepoUrl(suggestion.repositoryUrl); + } + if (suggestion.localPath) { + setStandaloneRepoPath(suggestion.localPath); + } + setShowRepoSuggestions(false); + }, []); + // Create task with configured options const handleCreateTask = useCallback(async () => { - if (creating || !selectedContract) return; + if (creating) return; + // For contract-based tasks, require a contract + if (!isStandalone && !selectedContract) return; + setShowContractModal(false); setCreating(true); try { + let repositoryUrl: string | undefined; + let targetPath: string | undefined; + + if (isStandalone) { + // Standalone task - use standalone repo configuration + repositoryUrl = standaloneRepoType === "remote" + ? (standaloneRepoUrl || undefined) + : (standaloneRepoPath || undefined); + targetPath = newTaskTargetPath || undefined; + } else { + // Contract-based task + repositoryUrl = newTaskRepoUrl || undefined; + targetPath = newTaskTargetPath || undefined; + } + const newTask = await saveTask({ - contractId: selectedContract.id, - name: newTaskName || `Task for ${selectedContract.name}`, + contractId: isStandalone ? undefined : selectedContract!.id, + name: newTaskName || (isStandalone ? "Standalone Task" : `Task for ${selectedContract!.name}`), plan: "# Plan\n\nDescribe what this task should accomplish...", - repositoryUrl: newTaskRepoUrl || undefined, - targetRepoPath: newTaskTargetPath || undefined, + repositoryUrl, + targetRepoPath: targetPath, }); if (newTask) { navigate(`/mesh/${newTask.id}`); @@ -544,7 +618,7 @@ export default function MeshPage() { } finally { setCreating(false); } - }, [creating, saveTask, navigate, selectedContract, newTaskName, newTaskRepoUrl, newTaskTargetPath]); + }, [creating, saveTask, navigate, isStandalone, selectedContract, newTaskName, newTaskRepoUrl, newTaskTargetPath, standaloneRepoType, standaloneRepoUrl, standaloneRepoPath]); // Close modal and reset state const handleCloseModal = useCallback(() => { @@ -554,6 +628,13 @@ export default function MeshPage() { setNewTaskName(""); setNewTaskRepoUrl(null); setNewTaskTargetPath(""); + // Reset standalone state + setIsStandalone(false); + setStandaloneRepoType("remote"); + setStandaloneRepoUrl(""); + setStandaloneRepoPath(""); + setRepoSuggestions([]); + setShowRepoSuggestions(false); }, []); const handleCreateSubtask = useCallback(async () => { @@ -853,9 +934,13 @@ export default function MeshPage() {
{modalStep === 2 && ( )}

- {modalStep === 1 ? "Select Contract" : "Configure Task"} + {modalStep === 1 ? "Create Task" : isStandalone ? "Standalone Task" : "Configure Task"}

+ // Step 1: Select Contract or Standalone +
+ {/* Standalone Task Option */} + + + {/* Divider */} +
+
+ Or select contract +
- ) : ( + + {/* Contract list */} + {contracts.length === 0 ? ( +
+

No contracts found.

+ +
+ ) : ( +
+ {contracts.map((contract) => ( + + ))} +
+ )} +
+ ) : isStandalone ? ( + // Step 2: Configure Standalone Task +
+ {/* Standalone badge */} +
+ + Standalone + + No contract +
+ + {/* Task name */} +
+ + setNewTaskName(e.target.value)} + className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]" + placeholder="Task name" + /> +
+ + {/* Repository type selector */}
- {contracts.map((contract) => ( + +
- ))} + +
- ) - ) : ( - // Step 2: Configure Task - selectedContract && ( -
- {/* Contract badge */} -
- Contract: - {selectedContract.name} + + {/* Repository suggestions */} + {showRepoSuggestions && repoSuggestions.length > 0 && ( +
+ +
+ {repoSuggestions.map((suggestion) => ( + + ))} +
+ )} - {/* Task name */} + {/* Repository URL/Path input */} + {standaloneRepoType === "remote" ? (
- + setNewTaskName(e.target.value)} + value={standaloneRepoUrl} + onChange={(e) => setStandaloneRepoUrl(e.target.value)} className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]" - placeholder="Task name" + placeholder="https://github.com/user/repo.git" + /> +

+ GitHub, GitLab, or any Git repository URL. +

+
+ ) : ( +
+ + +

+ Path to an existing local repository. +

+ )} + + {/* Target repo path (optional) */} +
+ + +

+ Path where the task will push/merge changes. Leave empty to configure later. +

+
- {/* Repository selection */} - {selectedContract.repositories.length > 0 && ( -
- - -

- The repository this task will work on. -

-
- )} + {/* Create button */} +
+ +
+
+ ) : selectedContract && ( + // Step 2: Configure Task (Contract-based) +
+ {/* Contract badge */} +
+ Contract: + {selectedContract.name} +
- {/* Target repo path with DirectoryInput */} - {newTaskRepoUrl && ( -
- - -

- Path where the task will push/merge changes. Leave empty to configure later. -

-
- )} + {/* Task name */} +
+ + setNewTaskName(e.target.value)} + className="w-full bg-[#0a1525] border border-[rgba(117,170,252,0.25)] text-[#dbe7ff] font-mono text-sm px-3 py-2 outline-none focus:border-[#3f6fb3]" + placeholder="Task name" + /> +
- {/* Create button */} -
-
- ) +
)}
-- cgit v1.2.3