/** Unified tool starter — workspace-first, fetches real tool types and config profiles. */ import { useState, useEffect, useCallback } from "react"; import { Icon } from "../../icon"; import { listToolTypes, type ToolType } from "../../../api/tool-types"; import { listConfigProfiles, type ConfigProfile, } from "../../../api/config-profiles"; import { listSSHKeys, type SSHKey } from "../../../api/ssh-keys"; import { useSessions } from "../../../state/sessions"; import { useSessionOperations } from "../../../state/session-operations"; import type { Workspace } from "../../../types/workspace"; import type { ToolInstance } from "../../../api/sessions"; export interface ToolStarterProps { workspace: Workspace; onStarted: (instance: ToolInstance) => void; onCancel?: () => void; } export function ToolStarter({ workspace, onStarted, onCancel, }: ToolStarterProps) { const { addOrUpdateSession } = useSessions(); const { startOperation } = useSessionOperations(); const [toolTypes, setToolTypes] = useState([]); const [toolTypesLoading, setToolTypesLoading] = useState(true); const [toolTypesError, setToolTypesError] = useState(null); const [selectedToolTypeId, setSelectedToolTypeId] = useState(""); const [profiles, setProfiles] = useState([]); const [profilesLoading, setProfilesLoading] = useState(false); const [selectedProfileId, setSelectedProfileId] = useState(""); const [sshKeys, setSshKeys] = useState([]); const [sshKeysLoading, setSshKeysLoading] = useState(true); const [selectedSshKeyIds, setSelectedSshKeyIds] = useState([]); const [displayName, setDisplayName] = useState(workspace.name); const [nameEdited, setNameEdited] = useState(false); const [starting, setStarting] = useState(false); const [error, setError] = useState(null); // Fetch tool types on mount useEffect(() => { const load = async () => { try { const data = await listToolTypes(); setToolTypes(data); } catch (err) { setToolTypesError( err instanceof Error ? err.message : "Failed to load tool types", ); } finally { setToolTypesLoading(false); } }; void load(); }, []); // Fetch config profiles when tool type changes useEffect(() => { if (!selectedToolTypeId) { setProfiles([]); setSelectedProfileId(""); return; } const load = async () => { setProfilesLoading(true); try { const data = await listConfigProfiles( workspace.project_id, selectedToolTypeId, ); setProfiles(data); // Auto-select default profile if available const defaultProfile = data.find((p) => p.is_default); if (defaultProfile) { setSelectedProfileId(defaultProfile.id); } else { setSelectedProfileId(""); } } catch { setProfiles([]); } finally { setProfilesLoading(false); } }; void load(); }, [selectedToolTypeId, workspace.project_id]); // Fetch SSH keys on mount useEffect(() => { const load = async () => { try { const data = await listSSHKeys(); setSshKeys(data); // Auto-select the repository's SSH key if available if (workspace.repo_ssh_key_id) { setSelectedSshKeyIds([workspace.repo_ssh_key_id]); } } catch (err) { console.error("Failed to load SSH keys:", err); } finally { setSshKeysLoading(false); } }; void load(); }, [workspace.repo_ssh_key_id]); const repoHasSshKey = !!workspace.repo_ssh_key_id; const repoSshKey = sshKeys.find((k) => k.id === workspace.repo_ssh_key_id); const handleStart = useCallback(async () => { if (!selectedToolTypeId) { setError("Please select a tool type"); return; } setStarting(true); setError(null); try { const { createInstance, startInstance } = await import( "../../../api/sessions" ); const instance = await createInstance( workspace.project_id, workspace.repo_id, selectedToolTypeId, displayName.trim() || undefined, workspace.id, selectedProfileId || undefined, selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined, ); await startInstance( workspace.project_id, workspace.repo_id, instance.id, selectedProfileId || undefined, selectedSshKeyIds.length > 0 ? selectedSshKeyIds : undefined, ); addOrUpdateSession({ id: instance.id, display_name: instance.display_name, tool_type_name: instance.tool_type_name, tool_icon: "code", tool_type_interfaces: instance.tool_type_interfaces || [], repository_name: workspace.repo_name, repository_id: workspace.repo_id, project_name: workspace.project_name, project_id: workspace.project_id, workspace_name: workspace.name, status: instance.status || "pending", url: instance.url || null, }); startOperation("create", instance.id, instance.display_name); onStarted(instance); } catch (err) { setError(err instanceof Error ? err.message : "Failed to start tool"); } finally { setStarting(false); } }, [ selectedToolTypeId, selectedProfileId, displayName, workspace, onStarted, toolTypes, addOrUpdateSession, startOperation, ]); return (
{/* Context header — read-only workspace info */}
Project {workspace.project_name}
Repository {workspace.repo_name}
Workspace {workspace.name} {workspace.branch}
{/* Tool Type */}
{toolTypesLoading && Loading tools...} {toolTypesError && {toolTypesError}}
{/* Session Name */}
{ setDisplayName(e.target.value); setNameEdited(true); }} placeholder="My dev environment" disabled={starting} />
{/* Config Profile */} {selectedToolTypeId && (
{profilesLoading && ( Loading profiles... )} {profiles.length === 0 && !profilesLoading && ( No custom profiles for this tool. )}
)} {/* SSH Key Selection */}
{sshKeysLoading ? ( Loading SSH keys... ) : sshKeys.length === 0 ? ( No SSH keys configured. ) : (
{sshKeys.map((key) => ( ))}
)} {!sshKeysLoading && repoHasSshKey && repoSshKey && (
Repository key {repoSshKey.name} is pre-selected.
)}
{error &&

{error}

}
{onCancel && ( )}
); }